ridley 0.10.2 → 0.11.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. data/README.md +147 -216
  2. data/lib/ridley.rb +2 -0
  3. data/lib/ridley/bootstrap_bindings/unix_template_binding.rb +21 -25
  4. data/lib/ridley/bootstrap_bindings/windows_template_binding.rb +29 -34
  5. data/lib/ridley/bootstrapper.rb +2 -2
  6. data/lib/ridley/bootstrapper/context.rb +5 -5
  7. data/lib/ridley/chef.rb +0 -1
  8. data/lib/ridley/chef/cookbook.rb +0 -9
  9. data/lib/ridley/chef_object.rb +128 -0
  10. data/lib/ridley/chef_objects.rb +3 -0
  11. data/lib/ridley/chef_objects/client_object.rb +55 -0
  12. data/lib/ridley/chef_objects/cookbook_object.rb +190 -0
  13. data/lib/ridley/chef_objects/data_bag_item_obect.rb +104 -0
  14. data/lib/ridley/chef_objects/data_bag_object.rb +31 -0
  15. data/lib/ridley/chef_objects/environment_object.rb +59 -0
  16. data/lib/ridley/chef_objects/node_object.rb +161 -0
  17. data/lib/ridley/chef_objects/role_object.rb +62 -0
  18. data/lib/ridley/chef_objects/sandbox_object.rb +58 -0
  19. data/lib/ridley/client.rb +76 -45
  20. data/lib/ridley/connection.rb +1 -1
  21. data/lib/ridley/errors.rb +8 -1
  22. data/lib/ridley/host_connector.rb +26 -6
  23. data/lib/ridley/host_connector/ssh.rb +3 -3
  24. data/lib/ridley/host_connector/ssh/worker.rb +7 -9
  25. data/lib/ridley/host_connector/winrm/worker.rb +4 -5
  26. data/lib/ridley/mixin/bootstrap_binding.rb +1 -12
  27. data/lib/ridley/resource.rb +51 -171
  28. data/lib/ridley/resources/client_resource.rb +18 -68
  29. data/lib/ridley/resources/cookbook_resource.rb +181 -381
  30. data/lib/ridley/resources/data_bag_item_resource.rb +55 -161
  31. data/lib/ridley/resources/data_bag_resource.rb +20 -61
  32. data/lib/ridley/resources/environment_resource.rb +9 -64
  33. data/lib/ridley/resources/node_resource.rb +135 -311
  34. data/lib/ridley/resources/role_resource.rb +1 -57
  35. data/lib/ridley/resources/sandbox_resource.rb +80 -65
  36. data/lib/ridley/resources/search_resource.rb +99 -0
  37. data/lib/ridley/sandbox_uploader.rb +12 -52
  38. data/lib/ridley/version.rb +1 -1
  39. data/spec/acceptance/bootstrapping_spec.rb +1 -1
  40. data/spec/acceptance/client_resource_spec.rb +15 -37
  41. data/spec/acceptance/data_bag_item_resource_spec.rb +8 -14
  42. data/spec/acceptance/data_bag_resource_spec.rb +1 -1
  43. data/spec/acceptance/environment_resource_spec.rb +13 -22
  44. data/spec/acceptance/node_resource_spec.rb +10 -29
  45. data/spec/acceptance/role_resource_spec.rb +14 -13
  46. data/spec/acceptance/sandbox_resource_spec.rb +2 -2
  47. data/spec/support/shared_examples/ridley_resource.rb +2 -23
  48. data/spec/unit/ridley/bootstrap_bindings/unix_template_binding_spec.rb +3 -4
  49. data/spec/unit/ridley/bootstrap_bindings/windows_template_binding_spec.rb +3 -5
  50. data/spec/unit/ridley/bootstrapper/context_spec.rb +2 -3
  51. data/spec/unit/ridley/bootstrapper_spec.rb +1 -1
  52. data/spec/unit/ridley/chef_object_spec.rb +240 -0
  53. data/spec/unit/ridley/chef_objects/client_object_spec.rb +11 -0
  54. data/spec/unit/ridley/chef_objects/cookbook_object_spec.rb +93 -0
  55. data/spec/unit/ridley/chef_objects/data_bag_item_object_spec.rb +74 -0
  56. data/spec/unit/ridley/chef_objects/data_bag_object_spec.rb +9 -0
  57. data/spec/unit/ridley/chef_objects/environment_object_spec.rb +57 -0
  58. data/spec/unit/ridley/chef_objects/node_object_spec.rb +252 -0
  59. data/spec/unit/ridley/chef_objects/role_object_spec.rb +57 -0
  60. data/spec/unit/ridley/chef_objects/sandbox_object_spec.rb +66 -0
  61. data/spec/unit/ridley/client_spec.rb +51 -51
  62. data/spec/unit/ridley/host_connector/ssh/worker_spec.rb +4 -4
  63. data/spec/unit/ridley/host_connector/ssh_spec.rb +26 -24
  64. data/spec/unit/ridley/host_connector/winrm/worker_spec.rb +3 -4
  65. data/spec/unit/ridley/host_connector/winrm_spec.rb +4 -4
  66. data/spec/unit/ridley/host_connector_spec.rb +40 -3
  67. data/spec/unit/ridley/mixin/bootstrap_binding_spec.rb +1 -1
  68. data/spec/unit/ridley/resource_spec.rb +81 -109
  69. data/spec/unit/ridley/resources/client_resource_spec.rb +18 -33
  70. data/spec/unit/ridley/resources/cookbook_resource_spec.rb +56 -230
  71. data/spec/unit/ridley/resources/data_bag_item_resource_spec.rb +2 -57
  72. data/spec/unit/ridley/resources/data_bag_resource_spec.rb +12 -7
  73. data/spec/unit/ridley/resources/environment_resource_spec.rb +10 -118
  74. data/spec/unit/ridley/resources/node_resource_spec.rb +83 -394
  75. data/spec/unit/ridley/resources/role_resource_spec.rb +2 -56
  76. data/spec/unit/ridley/resources/sandbox_resource_spec.rb +139 -136
  77. data/spec/unit/ridley/resources/search_resource_spec.rb +234 -0
  78. data/spec/unit/ridley/sandbox_uploader_spec.rb +13 -58
  79. metadata +36 -17
  80. data/lib/ridley/chef/chefignore.rb +0 -76
  81. data/lib/ridley/resources/encrypted_data_bag_item_resource.rb +0 -55
  82. data/lib/ridley/resources/search.rb +0 -101
  83. data/spec/fixtures/chefignore +0 -8
  84. data/spec/unit/ridley/chef/chefignore_spec.rb +0 -40
  85. data/spec/unit/ridley/resources/search_spec.rb +0 -221
@@ -0,0 +1,190 @@
1
+ module Ridley
2
+ # @author Jamie Winsor <reset@riotgames.com>
3
+ class CookbookObject < Ridley::ChefObject
4
+ include Ridley::Logging
5
+
6
+ FILE_TYPES = [
7
+ :resources,
8
+ :providers,
9
+ :recipes,
10
+ :definitions,
11
+ :libraries,
12
+ :attributes,
13
+ :files,
14
+ :templates,
15
+ :root_files
16
+ ].freeze
17
+
18
+ set_chef_id "name"
19
+ set_chef_type "cookbook"
20
+ set_chef_json_class "Chef::Cookbook"
21
+
22
+ attribute :name,
23
+ required: true
24
+
25
+ attribute :attributes,
26
+ type: Array,
27
+ default: Array.new
28
+
29
+ attribute :cookbook_name,
30
+ type: String
31
+
32
+ attribute :definitions,
33
+ type: Array,
34
+ default: Array.new
35
+
36
+ attribute :files,
37
+ type: Array,
38
+ default: Array.new
39
+
40
+ attribute :libraries,
41
+ type: Array,
42
+ default: Array.new
43
+
44
+ attribute :metadata,
45
+ type: Hashie::Mash
46
+
47
+ attribute :providers,
48
+ type: Array,
49
+ default: Array.new
50
+
51
+ attribute :recipes,
52
+ type: Array,
53
+ default: Array.new
54
+
55
+ attribute :resources,
56
+ type: Array,
57
+ default: Array.new
58
+
59
+ attribute :root_files,
60
+ type: Array,
61
+ default: Array.new
62
+
63
+ attribute :templates,
64
+ type: Array,
65
+ default: Array.new
66
+
67
+ attribute :version,
68
+ type: String
69
+
70
+ attribute :frozen?,
71
+ type: Boolean
72
+
73
+ # Download the entire cookbook
74
+ #
75
+ # @param [String] destination (Dir.mktmpdir)
76
+ # the place to download the cookbook too. If no value is provided the cookbook
77
+ # will be downloaded to a temporary location
78
+ #
79
+ # @return [String]
80
+ # the path to the directory the cookbook was downloaded to
81
+ def download(destination = Dir.mktmpdir)
82
+ destination = File.expand_path(destination)
83
+ log.debug { "downloading cookbook: '#{name}'" }
84
+
85
+ FILE_TYPES.each do |filetype|
86
+ next unless manifest.has_key?(filetype)
87
+
88
+ manifest[filetype].each do |file|
89
+ file_destination = File.join(destination, file[:path].gsub('/', File::SEPARATOR))
90
+ FileUtils.mkdir_p(File.dirname(file_destination))
91
+ download_file(filetype, file[:path], file_destination)
92
+ end
93
+ end
94
+
95
+ destination
96
+ end
97
+
98
+ # Download a single file from a cookbook
99
+ #
100
+ # @param [#to_sym] filetype
101
+ # the type of file to download. These are broken up into the following types in Chef:
102
+ # - attribute (unsupported until resolved https://github.com/reset/chozo/issues/17)
103
+ # - definition
104
+ # - file
105
+ # - library
106
+ # - provider
107
+ # - recipe
108
+ # - resource
109
+ # - root_file
110
+ # - template
111
+ # these types are where the files are stored in your cookbook's structure. For example, a
112
+ # recipe would be stored in the recipes directory while a root_file is stored at the root
113
+ # of your cookbook
114
+ # @param [String] path
115
+ # path of the file to download
116
+ # @param [String] destination
117
+ # where to download the file to
118
+ #
119
+ # @return [nil]
120
+ def download_file(filetype, path, destination)
121
+ download_fun(filetype).call(path, destination)
122
+ end
123
+
124
+ # A hash containing keys for all of the different cookbook filetypes with values
125
+ # representing each file of that type this cookbook contains
126
+ #
127
+ # @example
128
+ # {
129
+ # root_files: [
130
+ # {
131
+ # :name => "afile.rb",
132
+ # :path => "files/ubuntu-9.10/afile.rb",
133
+ # :checksum => "2222",
134
+ # :specificity => "ubuntu-9.10"
135
+ # },
136
+ # ],
137
+ # templates: [ manifest_record1, ... ],
138
+ # ...
139
+ # }
140
+ #
141
+ # @return [Hash]
142
+ def manifest
143
+ {}.tap do |manifest|
144
+ FILE_TYPES.each do |filetype|
145
+ manifest[filetype] = get_attribute(filetype)
146
+ end
147
+ end
148
+ end
149
+
150
+ def to_s
151
+ "#{name}: #{manifest}"
152
+ end
153
+
154
+ private
155
+
156
+ # Return a lambda for downloading a file from the cookbook of the given type
157
+ #
158
+ # @param [#to_sym] filetype
159
+ #
160
+ # @return [lambda]
161
+ # a lambda which takes to parameters: target and path. Target is the URL to download from
162
+ # and path is the location on disk to steam the contents of the remote URL to.
163
+ def download_fun(filetype)
164
+ collection = case filetype.to_sym
165
+ when :attribute, :attributes; method(:attributes)
166
+ when :definition, :definitions; method(:definitions)
167
+ when :file, :files; method(:files)
168
+ when :library, :libraries; method(:libraries)
169
+ when :provider, :providers; method(:providers)
170
+ when :recipe, :recipes; method(:recipes)
171
+ when :resource, :resources; method(:resources)
172
+ when :root_file, :root_files; method(:root_files)
173
+ when :template, :templates; method(:templates)
174
+ else
175
+ raise Errors::UnknownCookbookFileType.new(filetype)
176
+ end
177
+
178
+ ->(target, destination) {
179
+ files = collection.call # JW: always chaining .call.find results in a nil value. WHY?
180
+ file = files.find { |f| f[:path] == target }
181
+ return nil if file.nil?
182
+
183
+ destination = File.expand_path(destination)
184
+ log.debug { "downloading '#{filetype}' file: #{file} to: '#{destination}'" }
185
+
186
+ connection.stream(file[:url], destination)
187
+ }
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,104 @@
1
+ module Ridley
2
+ # @author Jamie Winsor <reset@riotgames.com>
3
+ class DataBagItemObject < ChefObject
4
+ set_chef_id "id"
5
+ set_assignment_mode :carefree
6
+
7
+ # @return [Ridley::DataBagObject]
8
+ attr_reader :data_bag
9
+
10
+ attribute :id,
11
+ type: String,
12
+ required: true
13
+
14
+ alias_method :attributes=, :mass_assign
15
+ alias_method :attributes, :_attributes_
16
+
17
+ # @param [Ridley::DataBagItemResource] resource
18
+ # @param [Ridley::DataBagObject] data_bag
19
+ # @param [#to_hash] new_attrs
20
+ def initialize(resource, data_bag, new_attrs = {})
21
+ super(resource, new_attrs)
22
+ @data_bag = data_bag
23
+ end
24
+
25
+ # Creates a resource on the target remote or updates one if the resource
26
+ # already exists.
27
+ #
28
+ # @raise [Errors::InvalidResource]
29
+ # if the resource does not pass validations
30
+ #
31
+ # @return [Boolean]
32
+ # true if successful and false for failure
33
+ def save
34
+ raise Errors::InvalidResource.new(self.errors) unless valid?
35
+
36
+ mass_assign(resource.create(data_bag, self)._attributes_)
37
+ true
38
+ rescue Errors::HTTPConflict
39
+ self.update
40
+ true
41
+ end
42
+
43
+ # Decrypts this data bag item.
44
+ #
45
+ # @return [Hash] decrypted attributes
46
+ def decrypt
47
+ decrypted_hash = Hash[_attributes_.map { |key, value| [key, key == "id" ? value : decrypt_value(value)] }]
48
+ mass_assign(decrypted_hash)
49
+ end
50
+
51
+ def decrypt_value(value)
52
+ if encrypted_data_bag_secret.nil?
53
+ raise Errors::EncryptedDataBagSecretNotSet
54
+ end
55
+
56
+ decoded_value = Base64.decode64(value)
57
+
58
+ cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
59
+ cipher.decrypt
60
+ cipher.pkcs5_keyivgen(encrypted_data_bag_secret)
61
+ decrypted_value = cipher.update(decoded_value) + cipher.final
62
+
63
+ YAML.load(decrypted_value)
64
+ end
65
+
66
+ # Reload the attributes of the instantiated resource
67
+ #
68
+ # @return [Object]
69
+ def reload
70
+ mass_assign(resource.find(data_bag, self)._attributes_)
71
+ self
72
+ end
73
+
74
+ # Updates the instantiated resource on the target remote with any changes made
75
+ # to self
76
+ #
77
+ # @raise [Errors::InvalidResource]
78
+ # if the resource does not pass validations
79
+ #
80
+ # @return [Boolean]
81
+ def update
82
+ raise Errors::InvalidResource.new(self.errors) unless valid?
83
+
84
+ mass_assign(resource.update(data_bag, self)._attributes_)
85
+ true
86
+ end
87
+
88
+ # @param [#to_hash] hash
89
+ #
90
+ # @return [Object]
91
+ def from_hash(hash)
92
+ hash = Hashie::Mash.new(hash.to_hash)
93
+
94
+ mass_assign(hash.has_key?(:raw_data) ? hash[:raw_data] : hash)
95
+ self
96
+ end
97
+
98
+ private
99
+
100
+ def encrypted_data_bag_secret
101
+ resource.encrypted_data_bag_secret
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,31 @@
1
+ module Ridley
2
+ # @author Jamie Winsor <reset@riotgames.com>
3
+ class DataBagObject < ChefObject
4
+ set_chef_id "name"
5
+
6
+ attribute :name,
7
+ required: true
8
+
9
+ def item
10
+ DataBagItemProxy.new(self, resource.item_resource)
11
+ end
12
+
13
+ # @author Jamie Winsor <reset@riotgames.com>
14
+ # @api private
15
+ class DataBagItemProxy
16
+ attr_reader :data_bag_object
17
+ attr_reader :item_resource
18
+
19
+ # @param [Ridley::DataBagObject] data_bag_object
20
+ # @param [Ridley::DataBagItemResource] item_resource
21
+ def initialize(data_bag_object, item_resource)
22
+ @data_bag_object = data_bag_object
23
+ @item_resource = item_resource
24
+ end
25
+
26
+ def method_missing(fun, *args, &block)
27
+ @item_resource.send(fun, data_bag_object, *args, &block)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,59 @@
1
+ module Ridley
2
+ # @author Jamie Winsor <reset@riotgames.com>
3
+ class EnvironmentObject < Ridley::ChefObject
4
+ set_chef_id "name"
5
+ set_chef_type "environment"
6
+ set_chef_json_class "Chef::Environment"
7
+
8
+ attribute :name,
9
+ required: true
10
+
11
+ attribute :description,
12
+ default: String.new
13
+
14
+ attribute :default_attributes,
15
+ default: Hashie::Mash.new
16
+
17
+ attribute :override_attributes,
18
+ default: Hashie::Mash.new
19
+
20
+ attribute :cookbook_versions,
21
+ default: Hashie::Mash.new
22
+
23
+ # Set an environment level default attribute given the dotted path representation of
24
+ # the Chef attribute and value
25
+ #
26
+ # @example setting and saving an environment level default attribute
27
+ #
28
+ # obj = environment.find("production")
29
+ # obj.set_default_attribute("my_app.billing.enabled", false)
30
+ # obj.save
31
+ #
32
+ # @param [String] key
33
+ # @param [Object] value
34
+ #
35
+ # @return [HashWithIndifferentAccess]
36
+ def set_default_attribute(key, value)
37
+ attr_hash = HashWithIndifferentAccess.from_dotted_path(key, value)
38
+ self.default_attributes = self.default_attributes.deep_merge(attr_hash)
39
+ end
40
+
41
+ # Set an environment level override attribute given the dotted path representation of
42
+ # the Chef attribute and value
43
+ #
44
+ # @example setting and saving an environment level override attribute
45
+ #
46
+ # obj = environment.find("production")
47
+ # obj.set_override_attribute("my_app.billing.enabled", false)
48
+ # obj.save
49
+ #
50
+ # @param [String] key
51
+ # @param [Object] value
52
+ #
53
+ # @return [HashWithIndifferentAccess]
54
+ def set_override_attribute(key, value)
55
+ attr_hash = HashWithIndifferentAccess.from_dotted_path(key, value)
56
+ self.override_attributes = self.override_attributes.deep_merge(attr_hash)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,161 @@
1
+ module Ridley
2
+ # @author Jamie Winsor <reset@riotgames.com>
3
+ class NodeObject < Ridley::ChefObject
4
+ set_chef_id "name"
5
+ set_chef_type "node"
6
+ set_chef_json_class "Chef::Node"
7
+
8
+ attribute :name,
9
+ required: true
10
+
11
+ attribute :chef_environment,
12
+ default: "_default"
13
+
14
+ attribute :automatic,
15
+ default: Hashie::Mash.new
16
+
17
+ attribute :normal,
18
+ default: Hashie::Mash.new
19
+
20
+ attribute :default,
21
+ default: Hashie::Mash.new
22
+
23
+ attribute :override,
24
+ default: Hashie::Mash.new
25
+
26
+ attribute :run_list,
27
+ default: Array.new
28
+
29
+ alias_method :normal_attributes, :normal
30
+ alias_method :automatic_attributes, :automatic
31
+ alias_method :default_attributes, :default
32
+ alias_method :override_attributes, :override
33
+
34
+ alias_method :normal_attributes=, :normal=
35
+ alias_method :automatic_attributes=, :automatic=
36
+ alias_method :default_attributes=, :default=
37
+ alias_method :override_attributes=, :override=
38
+
39
+ # Set a node level normal attribute given the dotted path representation of the Chef
40
+ # attribute and value.
41
+ #
42
+ # @note It is not possible to set any other attribute level on a node and have it persist after
43
+ # a Chef Run. This is because all other attribute levels are truncated at the start of a Chef Run.
44
+ #
45
+ # @example setting and saving a node level normal attribute
46
+ #
47
+ # obj = node.find("jwinsor-1")
48
+ # obj.set_chef_attribute("my_app.billing.enabled", false)
49
+ # obj.save
50
+ #
51
+ # @param [String] key
52
+ # @param [Object] value
53
+ #
54
+ # @return [Hashie::Mash]
55
+ def set_chef_attribute(key, value)
56
+ attr_hash = Hashie::Mash.from_dotted_path(key, value)
57
+ self.normal = self.normal.deep_merge(attr_hash)
58
+ end
59
+
60
+ # Returns the public hostname of the instantiated node. This hostname should be used for
61
+ # public communications to the node.
62
+ #
63
+ # @example
64
+ # node.public_hostname => "reset.riotgames.com"
65
+ #
66
+ # @return [String]
67
+ def public_hostname
68
+ self.cloud? ? self.automatic[:cloud][:public_hostname] : self.automatic[:fqdn]
69
+ end
70
+
71
+ # Returns the public IPv4 address of the instantiated node. This ip address should be
72
+ # used for public communications to the node.
73
+ #
74
+ # @example
75
+ # node.public_ipv4 => "10.33.33.1"
76
+ #
77
+ # @return [String]
78
+ def public_ipv4
79
+ self.cloud? ? self.automatic[:cloud][:public_ipv4] : self.automatic[:ipaddress]
80
+ end
81
+ alias_method :public_ipaddress, :public_ipv4
82
+
83
+ # Returns the cloud provider of the instantiated node. If the node is not identified as
84
+ # a cloud node, then nil is returned.
85
+ #
86
+ # @example
87
+ # node_1.cloud_provider => "eucalyptus"
88
+ # node_2.cloud_provider => "ec2"
89
+ # node_3.cloud_provider => "rackspace"
90
+ # node_4.cloud_provider => nil
91
+ #
92
+ # @return [nil, String]
93
+ def cloud_provider
94
+ self.cloud? ? self.automatic[:cloud][:provider] : nil
95
+ end
96
+
97
+ # Returns true if the node is identified as a cloud node.
98
+ #
99
+ # @return [Boolean]
100
+ def cloud?
101
+ self.automatic.has_key?(:cloud)
102
+ end
103
+
104
+ # Returns true if the node is identified as a cloud node using the eucalyptus provider.
105
+ #
106
+ # @return [Boolean]
107
+ def eucalyptus?
108
+ self.cloud_provider == "eucalyptus"
109
+ end
110
+
111
+ # Returns true if the node is identified as a cloud node using the ec2 provider.
112
+ #
113
+ # @return [Boolean]
114
+ def ec2?
115
+ self.cloud_provider == "ec2"
116
+ end
117
+
118
+ # Returns true if the node is identified as a cloud node using the rackspace provider.
119
+ #
120
+ # @return [Boolean]
121
+ def rackspace?
122
+ self.cloud_provider == "rackspace"
123
+ end
124
+
125
+ # Executes a Chef run on the node
126
+ #
127
+ # @return [HostConnector::Response]
128
+ def chef_run
129
+ resource.chef_run(self.public_hostname)
130
+ end
131
+
132
+ # Puts the configured encrypted data bag secret on the node
133
+ #
134
+ # @return [HostConnector::Response]
135
+ def put_secret
136
+ resource.put_secret(self.public_hostname)
137
+ end
138
+
139
+ # Merges the instaniated nodes data with the given data and updates
140
+ # the remote with the merged results
141
+ #
142
+ # @option options [Array] :run_list
143
+ # run list items to merge
144
+ # @option options [Hash] :attributes
145
+ # attributes of normal precedence to merge
146
+ #
147
+ # @return [Ridley::NodeResource]
148
+ def merge_data(options = {})
149
+ unless options[:run_list].nil?
150
+ self.run_list = (self.run_list + Array(options[:run_list])).uniq
151
+ end
152
+
153
+ unless options[:attributes].nil?
154
+ self.normal = self.normal.deep_merge(options[:attributes])
155
+ end
156
+
157
+ self.update
158
+ self
159
+ end
160
+ end
161
+ end