diff --git a/lib/chef/resource/chef_acl.rb b/lib/chef/resource/chef_acl.rb index 20b30a0..5849045 100644 --- a/lib/chef/resource/chef_acl.rb +++ b/lib/chef/resource/chef_acl.rb @@ -9,27 +9,15 @@ class Resource class ChefAcl < Cheffish::BaseResource resource_name :chef_acl - def initialize(*args) - super - chef_server run_context.cheffish.current_chef_server - end - # Path of the thing being secured, e.g. nodes, nodes/*, nodes/mynode, # */*, **, roles/base, data/secrets, cookbooks/apache2, /users/*, # /organizations/foo/nodes/x - property :path, :kind_of => String, :name_attribute => true + property :path, String, name_property: true # Whether to change things recursively. true means it will descend all children # and make the same modifications to them. :on_change will only descend if # the parent has changed. :on_change is the default. - property :recursive, :equal_to => [ true, false, :on_change ], :default => :on_change - - # Specifies that this is a complete specification for the acl (i.e. rights - # you don't specify will be reset to their defaults) - property :complete, :kind_of => [TrueClass, FalseClass] - - property :raw_json, :kind_of => Hash - property :chef_server, :kind_of => Hash + property :recursive, [ true, false, :on_change ], default: :on_change # rights :read, :users => 'jkeiser', :groups => [ 'admins', 'users' ] # rights [ :create, :read ], :users => [ 'jkeiser', 'adam' ] diff --git a/lib/chef/resource/chef_client.rb b/lib/chef/resource/chef_client.rb index ed9b98b..00d2c41 100644 --- a/lib/chef/resource/chef_client.rb +++ b/lib/chef/resource/chef_client.rb @@ -6,30 +6,19 @@ class Resource class ChefClient < Cheffish::ChefActorBase resource_name :chef_client - def initialize(*args) - super - chef_server run_context.cheffish.current_chef_server - end - # Client attributes - property :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true - property :admin, :kind_of => [TrueClass, FalseClass] - property :validator, :kind_of => [TrueClass, FalseClass] + property :name, Cheffish::NAME_REGEX, name_property: true + property :admin, Boolean + property :validator, Boolean # Input key property :source_key # String or OpenSSL::PKey::* - property :source_key_path, :kind_of => String + property :source_key_path, String property :source_key_pass_phrase # Output public key (if so desired) - property :output_key_path, :kind_of => String - property :output_key_format, :kind_of => Symbol, :default => :openssh, :equal_to => [ :pem, :der, :openssh ] - - # If this is set, client is not patchy - property :complete, :kind_of => [TrueClass, FalseClass] - - property :raw_json, :kind_of => Hash - property :chef_server, :kind_of => Hash + property :output_key_path, String + property :output_key_format, Symbol, default: :openssh, equal_to: [ :pem, :der, :openssh ] # Proc that runs just before the resource executes. Called with (resource) def before(&block) diff --git a/lib/chef/resource/chef_container.rb b/lib/chef/resource/chef_container.rb index c613bbe..e2176ba 100644 --- a/lib/chef/resource/chef_container.rb +++ b/lib/chef/resource/chef_container.rb @@ -7,15 +7,7 @@ class Resource class ChefContainer < Cheffish::BaseResource resource_name :chef_container - # Grab environment from with_environment - def initialize(*args) - super - chef_server run_context.cheffish.current_chef_server - end - - property :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true - property :chef_server, :kind_of => Hash - + property :name, Cheffish::NAME_REGEX, name_property: true action :create do if !@current_exists diff --git a/lib/chef/resource/chef_data_bag.rb b/lib/chef/resource/chef_data_bag.rb index b8eb50d..21bdbf0 100644 --- a/lib/chef/resource/chef_data_bag.rb +++ b/lib/chef/resource/chef_data_bag.rb @@ -6,15 +6,7 @@ class Resource class ChefDataBag < Cheffish::BaseResource resource_name :chef_data_bag - def initialize(*args) - super - chef_server run_context.cheffish.current_chef_server - end - - property :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true - - property :chef_server, :kind_of => Hash - + property :name, Cheffish::NAME_REGEX, name_property: true action :create do if !current_resource_exists? diff --git a/lib/chef/resource/chef_data_bag_item.rb b/lib/chef/resource/chef_data_bag_item.rb index 414074b..9382b99 100644 --- a/lib/chef/resource/chef_data_bag_item.rb +++ b/lib/chef/resource/chef_data_bag_item.rb @@ -11,92 +11,40 @@ class ChefDataBagItem < Cheffish::BaseResource def initialize(*args) super - name @name - if !data_bag + if !property_is_set?(:data_bag) && run_context.cheffish.current_data_bag data_bag run_context.cheffish.current_data_bag end - if run_context.cheffish.current_data_bag_item_encryption - @encrypt = true if run_context.cheffish.current_data_bag_item_encryption[:encrypt_all] - @secret = run_context.cheffish.current_data_bag_item_encryption[:secret] - @secret_path = run_context.cheffish.current_data_bag_item_encryption[:secret_path] || run_context.config[:encrypted_data_bag_secret] - @encryption_cipher = run_context.cheffish.current_data_bag_item_encryption[:encryption_cipher] - @encryption_version = run_context.cheffish.current_data_bag_item_encryption[:encryption_version] || run_context.config[:data_bag_encrypt_version] - @old_secret = run_context.cheffish.current_data_bag_item_encryption[:old_secret] - @old_secret_path = run_context.cheffish.current_data_bag_item_encryption[:old_secret_path] + encryption = run_context.cheffish.current_data_bag_item_encryption + if encryption + encrypt true if encryption[:encrypt_all] + secret encryption[:secret] if encryption[:secret] + secret_path encryption[:secret_path] || run_context.config[:encrypted_data_bag_secret] if encryption[:secret_path] || run_context.config[:encrypted_data_bag_secret] + encryption_cipher encryption[:encryption_cipher] if encryption[:encryption_cipher] + encryption_version encryption[:encryption_version] || run_context.config[:data_bag_encrypt_version] if encryption[:encryption_version] || run_context.config[:data_bag_encrypt_version] + old_secret encryption[:old_secret] if encryption[:old_secret] + old_secret_path encryption[:old_secret_path] if encryption[:old_secret_path] end - chef_server run_context.cheffish.current_chef_server end - def name(*args) - result = super(*args) - if args.size == 1 - parts = name.split('/') - if parts.size == 1 - @id = parts[0] - elsif parts.size == 2 - @data_bag = parts[0] - @id = parts[1] - else - raise "Name #{args[0].inspect} must be a string with 1 or 2 parts, either 'id' or 'data_bag/id" - end - end - result - end + # If data_bag and id are not specified, take them from name. + # name can either be id, or data_bag/id + property :id, String, default: lazy { name.split('/', 2)[-1] } + property :data_bag, String, default: lazy { + split = name.split('/', 2)[0] + split.size >= 2 ? split[0] : nil + } - # `NOT_PASSED` is defined in chef-12.5.0, this guard will ensure we - # don't redefine it if it's already there - NOT_PASSED = Object.new unless defined?(NOT_PASSED) - - def id(value = NOT_PASSED) - if value == NOT_PASSED - @id - else - @id = value - name data_bag ? "#{data_bag}/#{id}" : id - end - end - def data_bag(value = NOT_PASSED) - if value == NOT_PASSED - @data_bag - else - @data_bag = value - name data_bag ? "#{data_bag}/#{id}" : id - end - end - property :raw_data, :kind_of => Hash + property :raw_data, Hash # If secret or secret_path are set, encrypt is assumed true. encrypt exists mainly for with_secret and with_secret_path - property :encrypt, :kind_of => [TrueClass, FalseClass] - #property :secret, :kind_of => String - def secret(new_secret = nil) - if !new_secret - @secret - else - @secret = new_secret - @encrypt = true if @encrypt.nil? - end - end - #property :secret_path, :kind_of => String - def secret_path(new_secret_path = nil) - if !new_secret_path - @secret_path - else - @secret_path = new_secret_path - @encrypt = true if @encrypt.nil? - end - end - property :encryption_version, :kind_of => Integer + property :encrypt, Boolean, default: lazy { secret || secret_path } + property :secret, String + property :secret_path, String + property :encryption_version, Integer # Old secret (or secrets) to read the old data bag when we are changing keys and re-encrypting data - property :old_secret, :kind_of => [String, Array] - property :old_secret_path, :kind_of => [String, Array] - - # Specifies that this is a complete specification for the environment (i.e. attributes you don't specify will be - # reset to their defaults) - property :complete, :kind_of => [TrueClass, FalseClass] - - property :raw_json, :kind_of => Hash - property :chef_server, :kind_of => Hash + property :old_secret, [String, Array] + property :old_secret_path, [String, Array] # value 'ip_address', '127.0.0.1' # value [ 'pushy', 'port' ], '9000' diff --git a/lib/chef/resource/chef_environment.rb b/lib/chef/resource/chef_environment.rb index e9ba99c..76bf9b6 100644 --- a/lib/chef/resource/chef_environment.rb +++ b/lib/chef/resource/chef_environment.rb @@ -8,29 +8,13 @@ class Resource class ChefEnvironment < Cheffish::BaseResource resource_name :chef_environment - def initialize(*args) - super - chef_server run_context.cheffish.current_chef_server - end - - property :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true - property :description, :kind_of => String - property :cookbook_versions, :kind_of => Hash, :callbacks => { + property :name, Cheffish::NAME_REGEX, name_property: true + property :description, String + property :cookbook_versions, Hash, callbacks: { "should have valid cookbook versions" => lambda { |value| Chef::Environment.validate_cookbook_versions(value) } } - property :default_attributes, :kind_of => Hash - property :override_attributes, :kind_of => Hash - - # Specifies that this is a complete specification for the environment (i.e. attributes you don't specify will be - # reset to their defaults) - property :complete, :kind_of => [TrueClass, FalseClass] - - property :raw_json, :kind_of => Hash - property :chef_server, :kind_of => Hash - - # `NOT_PASSED` is defined in chef-12.5.0, this guard will ensure we - # don't redefine it if it's already there - NOT_PASSED=Object.new unless defined?(NOT_PASSED) + property :default_attributes, Hash + property :override_attributes, Hash # default 'ip_address', '127.0.0.1' # default [ 'pushy', 'port' ], '9000' @@ -69,7 +53,7 @@ def override(attribute_path, value=NOT_PASSED, &block) end alias :attributes :default_attributes - alias :property :default + alias :attribute :default action :create do diff --git a/lib/chef/resource/chef_group.rb b/lib/chef/resource/chef_group.rb index cd82637..85bd704 100644 --- a/lib/chef/resource/chef_group.rb +++ b/lib/chef/resource/chef_group.rb @@ -8,45 +8,13 @@ class Resource class ChefGroup < Cheffish::BaseResource resource_name :chef_group - # Grab environment from with_environment - def initialize(*args) - super - chef_server run_context.cheffish.current_chef_server - @users = [] - @clients = [] - @groups = [] - @remove_users = [] - @remove_clients = [] - @remove_groups = [] - end - - property :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true - def users(*users) - users.size == 0 ? @users : (@users |= users.flatten) - end - def clients(*clients) - clients.size == 0 ? @clients : (@clients |= clients.flatten) - end - def groups(*groups) - groups.size == 0 ? @groups : (@groups |= groups.flatten) - end - def remove_users(*remove_users) - remove_users.size == 0 ? @remove_users : (@remove_users |= remove_users.flatten) - end - def remove_clients(*remove_clients) - remove_clients.size == 0 ? @remove_clients : (@remove_clients |= remove_clients.flatten) - end - def remove_groups(*remove_groups) - remove_groups.size == 0 ? @remove_groups : (@remove_groups |= remove_groups.flatten) - end - - # Specifies that this is a complete specification for the environment (i.e. attributes you don't specify will be - # reset to their defaults) - property :complete, :kind_of => [TrueClass, FalseClass] - - property :raw_json, :kind_of => Hash - property :chef_server, :kind_of => Hash - + property :name, Cheffish::NAME_REGEX, name_property: true + property :users, ArrayType + property :clients, ArrayType + property :groups, ArrayType + property :remove_users, ArrayType + property :remove_clients, ArrayType + property :remove_groups, ArrayType action :create do differences = json_differences(current_json, new_json) diff --git a/lib/chef/resource/chef_mirror.rb b/lib/chef/resource/chef_mirror.rb index 6557e3d..496846e 100644 --- a/lib/chef/resource/chef_mirror.rb +++ b/lib/chef/resource/chef_mirror.rb @@ -11,44 +11,36 @@ class Resource class ChefMirror < Cheffish::BaseResource resource_name :chef_mirror - def initialize(*args) - super - chef_server run_context.cheffish.current_chef_server - end - # Path of the data to mirror, e.g. nodes, nodes/*, nodes/mynode, # */*, **, roles/base, data/secrets, cookbooks/apache2, etc. - property :path, :kind_of => String, :name_attribute => true + property :path, String, name_property: true # Local path. Can be a string (top level of repository) or hash # (:chef_repo_path, :node_path, etc.) # If neither chef_repo_path nor versioned_cookbooks are set, they default to their # Chef::Config values. If chef_repo_path is set but versioned_cookbooks is not, # versioned_cookbooks defaults to true. - property :chef_repo_path, :kind_of => [ String, Hash ] + property :chef_repo_path, [ String, Hash ] # Whether the repo path should contain cookbooks with versioned names, # i.e. cookbooks/mysql-1.0.0, cookbooks/mysql-1.2.0, etc. # Defaults to true if chef_repo_path is specified, or to Chef::Config.versioned_cookbooks otherwise. - property :versioned_cookbooks, :kind_of => [ TrueClass, FalseClass ] - - # Chef server - property :chef_server, :kind_of => Hash + property :versioned_cookbooks, Boolean # Whether to purge deleted things: if we do not have cookbooks/x locally and we # *do* have cookbooks/x remotely, then :upload with purge will delete it. # Defaults to false. - property :purge, :kind_of => [ TrueClass, FalseClass ] + property :purge, Boolean # Whether to freeze cookbooks on upload - property :freeze, :kind_of => [ TrueClass, FalseClass ] + property :freeze, Boolean # If this is true, only new files will be copied. File contents will not be # diffed, so changed files will never be uploaded. - property :no_diff, :kind_of => [ TrueClass, FalseClass ] + property :no_diff, Boolean # Number of parallel threads to list/upload/download with. Defaults to 10. - property :concurrency, :kind_of => Integer + property :concurrency, Integer, default: 10, desired_state: false action :upload do @@ -87,11 +79,11 @@ def with_modified_config end def copy_to(src_root, dest_root) - if new_resource.concurrency && new_resource.concurrency <= 0 + if new_resource.concurrency <= 0 raise "chef_mirror.concurrency must be above 0! Was set to #{new_resource.concurrency}" end # Honor concurrency - Chef::ChefFS::Parallelizer.threads = (new_resource.concurrency || 10) - 1 + Chef::ChefFS::Parallelizer.threads = new_resource.concurrency - 1 # We don't let the user pass absolute paths; we want to reserve those for # multi-org support (/organizations/foo). diff --git a/lib/chef/resource/chef_node.rb b/lib/chef/resource/chef_node.rb index e504b95..f8c1465 100644 --- a/lib/chef/resource/chef_node.rb +++ b/lib/chef/resource/chef_node.rb @@ -1,20 +1,14 @@ require 'cheffish' require 'cheffish/base_resource' require 'chef/chef_fs/data_handler/node_data_handler' +require 'cheffish/node_properties' class Chef class Resource class ChefNode < Cheffish::BaseResource resource_name :chef_node - # Grab environment from with_environment - def initialize(*args) - super - chef_environment run_context.cheffish.current_environment - chef_server run_context.cheffish.current_chef_server - end - - Cheffish.node_attributes(self) + include Cheffish::NodeProperties action :create do differences = json_differences(current_json, new_json) diff --git a/lib/chef/resource/chef_organization.rb b/lib/chef/resource/chef_organization.rb index 74419c1..80f8595 100644 --- a/lib/chef/resource/chef_organization.rb +++ b/lib/chef/resource/chef_organization.rb @@ -8,60 +8,23 @@ class Resource class ChefOrganization < Cheffish::BaseResource resource_name :chef_organization - # Grab environment from with_environment - def initialize(*args) - super - chef_server run_context.cheffish.current_chef_server - @invites = nil - @members = nil - @remove_members = [] - end - - property :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true - property :full_name, :kind_of => String + property :name, Cheffish::NAME_REGEX, name_property: true + property :full_name, String # A list of users who must at least be invited to the org (but may already be # members). Invites will be sent to users who are not already invited/in the org. - def invites(*users) - if users.size == 0 - @invites || [] - else - @invites ||= [] - @invites |= users.flatten - end - end - - def invites_specified? - !!@invites - end + property :invites, ArrayType # A list of users who must be members of the org. This will use the # new Chef 12 POST /organizations/ORG/users endpoint to add them # directly to the org. If you do not have permission to perform # this operation, and the users are not a part of the org, the # resource update will fail. - def members(*users) - if users.size == 0 - @members || [] - else - @members ||= [] - @members |= users.flatten - end - end - - def members_specified? - !!@members - end + property :members, ArrayType # A list of users who must not be members of the org. These users will be removed # from the org and invites will be revoked (if any). - def remove_members(*users) - users.size == 0 ? @remove_members : (@remove_members |= users.flatten) - end - - property :complete, :kind_of => [ TrueClass, FalseClass ] - property :raw_json, :kind_of => Hash - property :chef_server, :kind_of => Hash + property :remove_members, ArrayType action :create do @@ -131,8 +94,11 @@ def outstanding_invites def invites_to_remove if new_resource.complete - if new_resource.invites_specified? || new_resource.members_specified? - outstanding_invites.keys - (new_resource.invites | new_resource.members) + if new_resource.property_is_set?(:invites) || new_resource.property_is_set?(:members) + result = outstanding_invites.keys + result -= new_resource.invites if new_resource.property_is_set?(:invites) + result -= new_resource.members if new_resource.property_is_set?(:members) + result else [] end @@ -143,7 +109,7 @@ def invites_to_remove def members_to_remove if new_resource.complete - if new_resource.members_specified? + if new_resource.property_is_set?(:members) existing_members - (new_resource.invites | new_resource.members) else [] diff --git a/lib/chef/resource/chef_resolved_cookbooks.rb b/lib/chef/resource/chef_resolved_cookbooks.rb index 4e59f9d..123a7db 100644 --- a/lib/chef/resource/chef_resolved_cookbooks.rb +++ b/lib/chef/resource/chef_resolved_cookbooks.rb @@ -10,7 +10,6 @@ def initialize(*args) super require 'berkshelf' berksfile Berkshelf::Berksfile.new('/tmp/Berksfile') - chef_server run_context.cheffish.current_chef_server @cookbooks_from = [] end @@ -27,8 +26,6 @@ def cookbooks_from(path = nil) end property :berksfile - property :chef_server - action :resolve do new_resource.cookbooks_from.each do |path| diff --git a/lib/chef/resource/chef_role.rb b/lib/chef/resource/chef_role.rb index 0217a6e..d624c39 100644 --- a/lib/chef/resource/chef_role.rb +++ b/lib/chef/resource/chef_role.rb @@ -8,29 +8,12 @@ class Resource class ChefRole < Cheffish::BaseResource resource_name :chef_role - # Grab environment from with_environment - def initialize(*args) - super - chef_server run_context.cheffish.current_chef_server - end - - property :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true - property :description, :kind_of => String - property :run_list, :kind_of => Array # We should let them specify it as a series of parameters too - property :env_run_lists, :kind_of => Hash - property :default_attributes, :kind_of => Hash - property :override_attributes, :kind_of => Hash - - # Specifies that this is a complete specification for the environment (i.e. attributes you don't specify will be - # reset to their defaults) - property :complete, :kind_of => [TrueClass, FalseClass] - - property :raw_json, :kind_of => Hash - property :chef_server, :kind_of => Hash - - # `NOT_PASSED` is defined in chef-12.5.0, this guard will ensure we - # don't redefine it if it's already there - NOT_PASSED=Object.new unless defined?(NOT_PASSED) + property :name, Cheffish::NAME_REGEX, name_property: true + property :description, String + property :run_list, Array # We should let them specify it as a series of parameters too + property :env_run_lists, Hash + property :default_attributes, Hash + property :override_attributes, Hash # default_attribute 'ip_address', '127.0.0.1' # default_attribute [ 'pushy', 'port' ], '9000' diff --git a/lib/chef/resource/chef_user.rb b/lib/chef/resource/chef_user.rb index b2212ce..c1ae11e 100644 --- a/lib/chef/resource/chef_user.rb +++ b/lib/chef/resource/chef_user.rb @@ -6,38 +6,26 @@ class Resource class ChefUser < Cheffish::ChefActorBase resource_name :chef_user - # Grab environment from with_environment - def initialize(*args) - super - chef_server run_context.cheffish.current_chef_server - end - # Client attributes - property :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true - property :display_name, :kind_of => String - property :admin, :kind_of => [TrueClass, FalseClass] - property :email, :kind_of => String + property :name, Cheffish::NAME_REGEX, name_property: true + property :display_name, String + property :admin, Boolean + property :email, String property :external_authentication_uid - property :recovery_authentication_enabled, :kind_of => [TrueClass, FalseClass] - property :password, :kind_of => String # Hmm. There is no way to idempotentize this. + property :recovery_authentication_enabled, Boolean + property :password, String # Hmm. There is no way to idempotentize this. #property :salt # TODO server doesn't support sending or receiving these, but it's the only way to backup / restore a user #property :hashed_password #property :hash_type # Input key property :source_key # String or OpenSSL::PKey::* - property :source_key_path, :kind_of => String + property :source_key_path, String property :source_key_pass_phrase # Output public key (if so desired) - property :output_key_path, :kind_of => String - property :output_key_format, :kind_of => Symbol, :default => :openssh, :equal_to => [ :pem, :der, :openssh ] - - # If this is set, client is not patchy - property :complete, :kind_of => [TrueClass, FalseClass] - - property :raw_json, :kind_of => Hash - property :chef_server, :kind_of => Hash + property :output_key_path, String + property :output_key_format, [ :pem, :der, :openssh ], default: :openssh # Proc that runs just before the resource executes. Called with (resource) def before(&block) diff --git a/lib/chef/resource/private_key.rb b/lib/chef/resource/private_key.rb index 8428373..5fc052a 100644 --- a/lib/chef/resource/private_key.rb +++ b/lib/chef/resource/private_key.rb @@ -12,29 +12,29 @@ class PrivateKey < Cheffish::BaseResource default_action :create # Path to private key. Set to :none to create the key in memory and not on disk. - property :path, :kind_of => [ String, Symbol ], :name_attribute => true - property :format, :kind_of => Symbol, :default => :pem, :equal_to => [ :pem, :der ] - property :type, :kind_of => Symbol, :default => :rsa, :equal_to => [ :rsa, :dsa ] # TODO support :ec + property :path, [ String, :none ], name_property: true + property :format, [ :pem, :der ], default: :pem + property :type, [ :rsa, :dsa ], default: :rsa # TODO support :ec # These specify an optional public_key you can spit out if you want. - property :public_key_path, :kind_of => String - property :public_key_format, :kind_of => Symbol, :default => :openssh, :equal_to => [ :openssh, :pem, :der ] + property :public_key_path, String + property :public_key_format, [ :openssh, :pem, :der ], default: :openssh # Specify this if you want to copy another private key but give it a different format / password property :source_key - property :source_key_path, :kind_of => String + property :source_key_path, String property :source_key_pass_phrase # RSA and DSA - property :size, :kind_of => Integer, :default => 2048 + property :size, Integer, default: 2048 # RSA-only - property :exponent, :kind_of => Integer # For RSA + property :exponent, Integer # For RSA # PEM-only - property :pass_phrase, :kind_of => String - property :cipher, :kind_of => String, :default => 'DES-EDE3-CBC', :equal_to => OpenSSL::Cipher.ciphers + property :pass_phrase, String + property :cipher, OpenSSL::Cipher.ciphers, default: 'DES-EDE3-CBC' # Set this to regenerate the key if it does not have the desired characteristics (like size, type, etc.) - property :regenerate_if_different, :kind_of => [TrueClass, FalseClass] + property :regenerate_if_different, Boolean # Proc that runs after the resource completes. Called with (resource, private_key) def after(&block) diff --git a/lib/chef/resource/public_key.rb b/lib/chef/resource/public_key.rb index 4e661ae..e0f58de 100644 --- a/lib/chef/resource/public_key.rb +++ b/lib/chef/resource/public_key.rb @@ -11,11 +11,11 @@ class PublicKey < Cheffish::BaseResource allowed_actions :create, :delete, :nothing default_action :create - property :path, :kind_of => String, :name_attribute => true - property :format, :kind_of => Symbol, :default => :openssh, :equal_to => [ :pem, :der, :openssh ] + property :path, String, name_property: true + property :format, [ :pem, :der, :openssh ], default: :openssh property :source_key - property :source_key_path, :kind_of => String + property :source_key_path, String property :source_key_pass_phrase # We are not interested in Chef's cloning behavior here. diff --git a/lib/cheffish.rb b/lib/cheffish.rb index 1ce019a..adbd0c0 100644 --- a/lib/cheffish.rb +++ b/lib/cheffish.rb @@ -113,113 +113,8 @@ def self.get_private_key_with_path(name, config = profiled_config) nil end - # `NOT_PASSED` is defined in chef-12.5.0, this guard will ensure we - # don't redefine it if it's already there - NOT_PASSED=Object.new unless defined?(NOT_PASSED) - def self.node_attributes(klass) - klass.class_eval do - property :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true - property :chef_environment, :kind_of => String, :regex => Cheffish::NAME_REGEX - property :run_list, :kind_of => Array # We should let them specify it as a series of parameters too - property :attributes, :kind_of => Hash - - # Specifies that this is a complete specification for the environment (i.e. attributes you don't specify will be - # reset to their defaults) - property :complete, :kind_of => [TrueClass, FalseClass] - - property :raw_json, :kind_of => Hash - property :chef_server, :kind_of => Hash - - # attribute 'ip_address', '127.0.0.1' - # attribute [ 'pushy', 'port' ], '9000' - # attribute 'ip_addresses' do |existing_value| - # (existing_value || []) + [ '127.0.0.1' ] - # end - # attribute 'ip_address', :delete - attr_accessor :attribute_modifiers - def attribute(attribute_path, value=NOT_PASSED, &block) - @attribute_modifiers ||= [] - if value != NOT_PASSED - @attribute_modifiers << [ attribute_path, value ] - elsif block - @attribute_modifiers << [ attribute_path, block ] - else - raise "attribute requires either a value or a block" - end - end - - # Patchy tags - # tag 'webserver', 'apache', 'myenvironment' - def tag(*tags) - attribute 'tags' do |existing_tags| - existing_tags ||= [] - tags.each do |tag| - if !existing_tags.include?(tag.to_s) - existing_tags << tag.to_s - end - end - existing_tags - end - end - def remove_tag(*tags) - attribute 'tags' do |existing_tags| - if existing_tags - tags.each do |tag| - existing_tags.delete(tag.to_s) - end - end - existing_tags - end - end - - # NON-patchy tags - # tags :a, :b, :c # removes all other tags - def tags(*tags) - if tags.size == 0 - attribute('tags') - else - tags = tags[0] if tags.size == 1 && tags[0].kind_of?(Array) - attribute 'tags', tags.map { |tag| tag.to_s } - end - end - - # Order matters--if two things here are in the wrong order, they will be flipped in the run list - # recipe 'apache', 'mysql' - # recipe 'recipe@version' - # recipe 'recipe' - # role '' - attr_accessor :run_list_modifiers - attr_accessor :run_list_removers - def recipe(*recipes) - if recipes.size == 0 - raise ArgumentError, "At least one recipe must be specified" - end - @run_list_modifiers ||= [] - @run_list_modifiers += recipes.map { |recipe| Chef::RunList::RunListItem.new("recipe[#{recipe}]") } - end - def role(*roles) - if roles.size == 0 - raise ArgumentError, "At least one role must be specified" - end - @run_list_modifiers ||= [] - @run_list_modifiers += roles.map { |role| Chef::RunList::RunListItem.new("role[#{role}]") } - end - def remove_recipe(*recipes) - if recipes.size == 0 - raise ArgumentError, "At least one recipe must be specified" - end - @run_list_removers ||= [] - @run_list_removers += recipes.map { |recipe| Chef::RunList::RunListItem.new("recipe[#{recipe}]") } - end - def remove_role(*roles) - if roles.size == 0 - raise ArgumentError, "At least one role must be specified" - end - @run_list_removers ||= [] - @run_list_removers += roles.map { |role| Chef::RunList::RunListItem.new("role[#{role}]") } - end - end + klass.include Cheffish::NodeProperties end end @@ -233,3 +128,4 @@ def remove_role(*roles) require 'chef/log' require 'chef/application' require 'cheffish/recipe_dsl' +require 'cheffish/node_properties' diff --git a/lib/cheffish/array_property.rb b/lib/cheffish/array_property.rb new file mode 100644 index 0000000..52f451f --- /dev/null +++ b/lib/cheffish/array_property.rb @@ -0,0 +1,29 @@ +require 'chef_compat/property' + +module Cheffish + # A typical array property. Defaults to [], accepts multiple args to setter, accumulates values. + class ArrayProperty < ChefCompat::Property + def initialize(**options) + options[:is] ||= Array + options[:default] ||= [] + options[:coerce] ||= proc { |v| v.is_a?(Array) ? v : [ v ] } + super + end + + # Support my_property 'a', 'b', 'c'; my_property 'a'; and my_property ['a', 'b'] + def emit_dsl + declared_in.class_eval(<<-EOM, __FILE__, __LINE__+1) + def #{name}(*values) + property = self.class.properties[#{name.inspect}] + if values.empty? + property.get(self) + elsif property.is_set?(self) + property.set(self, property.get(self) + values.flatten) + else + property.set(self, values.flatten) + end + end + EOM + end + end +end diff --git a/lib/cheffish/base_resource.rb b/lib/cheffish/base_resource.rb index 6c0fe7d..7486cb5 100644 --- a/lib/cheffish/base_resource.rb +++ b/lib/cheffish/base_resource.rb @@ -1,7 +1,20 @@ require 'chef_compat/resource' +require 'cheffish/array_property' module Cheffish class BaseResource < ChefCompat::Resource + def initialize(*args) + super + chef_server run_context.cheffish.current_chef_server + end + + Boolean = property_type(is: [ true, false ]) + ArrayType = ArrayProperty.new + + property :chef_server, Hash + property :raw_json, Hash + property :complete, Boolean + declare_action_class.class_eval do def rest @rest ||= Cheffish.chef_server_api(new_resource.chef_server) diff --git a/lib/cheffish/node_properties.rb b/lib/cheffish/node_properties.rb new file mode 100644 index 0000000..38e58e1 --- /dev/null +++ b/lib/cheffish/node_properties.rb @@ -0,0 +1,107 @@ +require 'chef_compat/mixin/properties' + +module Cheffish + module NodeProperties + include ChefCompat::Mixin::Properties + + # Grab environment from with_environment + def initialize(*args) + super + chef_environment run_context.cheffish.current_environment + end + + property :name, Cheffish::NAME_REGEX, name_property: true + property :chef_environment, Cheffish::NAME_REGEX + property :run_list, Array # We should let them specify it as a series of parameters too + property :attributes, Hash + + # attribute 'ip_address', '127.0.0.1' + # attribute [ 'pushy', 'port' ], '9000' + # attribute 'ip_addresses' do |existing_value| + # (existing_value || []) + [ '127.0.0.1' ] + # end + # attribute 'ip_address', :delete + attr_accessor :attribute_modifiers + def attribute(attribute_path, value=Chef::NOT_PASSED, &block) + @attribute_modifiers ||= [] + if value != Chef::NOT_PASSED + @attribute_modifiers << [ attribute_path, value ] + elsif block + @attribute_modifiers << [ attribute_path, block ] + else + raise "attribute requires either a value or a block" + end + end + + # Patchy tags + # tag 'webserver', 'apache', 'myenvironment' + def tag(*tags) + attribute 'tags' do |existing_tags| + existing_tags ||= [] + tags.each do |tag| + if !existing_tags.include?(tag.to_s) + existing_tags << tag.to_s + end + end + existing_tags + end + end + def remove_tag(*tags) + attribute 'tags' do |existing_tags| + if existing_tags + tags.each do |tag| + existing_tags.delete(tag.to_s) + end + end + existing_tags + end + end + + # NON-patchy tags + # tags :a, :b, :c # removes all other tags + def tags(*tags) + if tags.size == 0 + attribute('tags') + else + tags = tags[0] if tags.size == 1 && tags[0].kind_of?(Array) + attribute 'tags', tags.map { |tag| tag.to_s } + end + end + + # Order matters--if two things here are in the wrong order, they will be flipped in the run list + # recipe 'apache', 'mysql' + # recipe 'recipe@version' + # recipe 'recipe' + # role '' + attr_accessor :run_list_modifiers + attr_accessor :run_list_removers + def recipe(*recipes) + if recipes.size == 0 + raise ArgumentError, "At least one recipe must be specified" + end + @run_list_modifiers ||= [] + @run_list_modifiers += recipes.map { |recipe| Chef::RunList::RunListItem.new("recipe[#{recipe}]") } + end + def role(*roles) + if roles.size == 0 + raise ArgumentError, "At least one role must be specified" + end + @run_list_modifiers ||= [] + @run_list_modifiers += roles.map { |role| Chef::RunList::RunListItem.new("role[#{role}]") } + end + def remove_recipe(*recipes) + if recipes.size == 0 + raise ArgumentError, "At least one recipe must be specified" + end + @run_list_removers ||= [] + @run_list_removers += recipes.map { |recipe| Chef::RunList::RunListItem.new("recipe[#{recipe}]") } + end + def remove_role(*roles) + if roles.size == 0 + raise ArgumentError, "At least one role must be specified" + end + @run_list_removers ||= [] + @run_list_removers += roles.map { |role| Chef::RunList::RunListItem.new("role[#{role}]") } + end + end +end diff --git a/spec/integration/chef_organization_spec.rb b/spec/integration/chef_organization_spec.rb index 43de7f9..3db7d58 100644 --- a/spec/integration/chef_organization_spec.rb +++ b/spec/integration/chef_organization_spec.rb @@ -193,7 +193,7 @@ expect(get('/organizations/x/users').map { |u| u['user']['username'] }).to eq([]) end - it 'chef_organization "x" with members [] and "complete true" removes invites but not members' do + it 'chef_organization "x" with invites [] and "complete true" removes invites but not members' do expect_recipe { chef_organization 'x' do invites []