ridley 0.7.0.rc1 → 0.7.0.rc3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.rbenv-version +2 -1
- data/lib/ridley.rb +2 -1
- data/lib/ridley/client.rb +17 -15
- data/lib/ridley/connection.rb +50 -3
- data/lib/ridley/errors.rb +13 -0
- data/lib/ridley/middleware/chef_auth.rb +48 -10
- data/lib/ridley/middleware/chef_response.rb +5 -3
- data/lib/ridley/middleware/parse_json.rb +3 -3
- data/lib/ridley/resource.rb +8 -6
- data/lib/ridley/resources/client_resource.rb +1 -1
- data/lib/ridley/resources/cookbook_resource.rb +276 -24
- data/lib/ridley/resources/data_bag_item_resource.rb +9 -10
- data/lib/ridley/resources/encrypted_data_bag_item_resource.rb +1 -1
- data/lib/ridley/sandbox_uploader.rb +1 -1
- data/lib/ridley/version.rb +1 -1
- data/ridley.gemspec +4 -3
- data/spec/spec_helper.rb +4 -0
- data/spec/support/shared_examples/ridley_resource.rb +6 -7
- data/spec/support/spec_helpers.rb +9 -0
- data/spec/unit/ridley/connection_spec.rb +23 -0
- data/spec/unit/ridley/middleware/chef_auth_spec.rb +18 -2
- data/spec/unit/ridley/resource_spec.rb +8 -22
- data/spec/unit/ridley/resources/cookbook_resource_spec.rb +255 -1
- data/spec/unit/ridley/resources/sandbox_resource_spec.rb +1 -1
- data/spec/unit/ridley/sandbox_uploader_spec.rb +2 -1
- metadata +25 -11
- data/spec/acceptance/cookbook_resource_spec.rb +0 -28
data/.rbenv-version
CHANGED
@@ -1 +1,2 @@
|
|
1
|
-
1.9.3-
|
1
|
+
1.9.3-p327
|
2
|
+
|
data/lib/ridley.rb
CHANGED
@@ -3,6 +3,7 @@ require 'celluloid'
|
|
3
3
|
require 'faraday'
|
4
4
|
require 'addressable/uri'
|
5
5
|
require 'multi_json'
|
6
|
+
require 'solve'
|
6
7
|
require 'active_support/inflector'
|
7
8
|
require 'forwardable'
|
8
9
|
require 'thread'
|
@@ -21,7 +22,7 @@ JSON.create_id = nil
|
|
21
22
|
|
22
23
|
# @author Jamie Winsor <jamie@vialstudios.com>
|
23
24
|
module Ridley
|
24
|
-
CHEF_VERSION = '
|
25
|
+
CHEF_VERSION = '11.4.0'.freeze
|
25
26
|
|
26
27
|
autoload :Bootstrapper, 'ridley/bootstrapper'
|
27
28
|
autoload :Client, 'ridley/client'
|
data/lib/ridley/client.rb
CHANGED
@@ -71,6 +71,8 @@ module Ridley
|
|
71
71
|
def_delegator :connection, :client_name
|
72
72
|
def_delegator :connection, :client_name=
|
73
73
|
|
74
|
+
attr_reader :options
|
75
|
+
|
74
76
|
attr_accessor :validator_client
|
75
77
|
attr_accessor :validator_path
|
76
78
|
attr_accessor :encrypted_data_bag_secret_path
|
@@ -103,34 +105,34 @@ module Ridley
|
|
103
105
|
# URI, String, or Hash of HTTP proxy options
|
104
106
|
def initialize(options = {})
|
105
107
|
log.info { "Ridley starting..." }
|
106
|
-
options = options.reverse_merge(
|
108
|
+
@options = options.reverse_merge(
|
107
109
|
ssh: Hash.new
|
108
110
|
).deep_symbolize_keys
|
109
|
-
self.class.validate_options(options)
|
111
|
+
self.class.validate_options(@options)
|
110
112
|
|
111
|
-
@ssh = options[:ssh]
|
112
|
-
@validator_client = options[:validator_client]
|
113
|
+
@ssh = @options[:ssh]
|
114
|
+
@validator_client = @options[:validator_client]
|
113
115
|
|
114
|
-
options[:client_key] = File.expand_path(options[:client_key])
|
116
|
+
@options[:client_key] = File.expand_path(@options[:client_key])
|
115
117
|
|
116
|
-
if options[:validator_path]
|
117
|
-
@validator_path = File.expand_path(options[:validator_path])
|
118
|
+
if @options[:validator_path]
|
119
|
+
@validator_path = File.expand_path(@options[:validator_path])
|
118
120
|
end
|
119
121
|
|
120
|
-
if options[:encrypted_data_bag_secret_path]
|
121
|
-
@encrypted_data_bag_secret_path = File.expand_path(options[:encrypted_data_bag_secret_path])
|
122
|
+
if @options[:encrypted_data_bag_secret_path]
|
123
|
+
@encrypted_data_bag_secret_path = File.expand_path(@options[:encrypted_data_bag_secret_path])
|
122
124
|
end
|
123
125
|
|
124
|
-
unless options[:client_key].present? && File.exist?(options[:client_key])
|
125
|
-
raise Errors::ClientKeyFileNotFound, "client key not found at: '#{options[:client_key]}'"
|
126
|
+
unless @options[:client_key].present? && File.exist?(@options[:client_key])
|
127
|
+
raise Errors::ClientKeyFileNotFound, "client key not found at: '#{@options[:client_key]}'"
|
126
128
|
end
|
127
129
|
|
128
130
|
super(Celluloid::Registry.new)
|
129
131
|
pool(Ridley::Connection, size: 4, args: [
|
130
|
-
options[:server_url],
|
131
|
-
options[:client_name],
|
132
|
-
options[:client_key],
|
133
|
-
options.slice(*Connection::VALID_OPTIONS)
|
132
|
+
@options[:server_url],
|
133
|
+
@options[:client_name],
|
134
|
+
@options[:client_key],
|
135
|
+
@options.slice(*Connection::VALID_OPTIONS)
|
134
136
|
], as: :connection_pool)
|
135
137
|
end
|
136
138
|
|
data/lib/ridley/connection.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'tempfile'
|
3
|
+
|
1
4
|
module Ridley
|
2
5
|
# @author Jamie Winsor <jamie@vialstudios.com>
|
3
6
|
class Connection < Faraday::Connection
|
@@ -15,9 +18,9 @@ module Ridley
|
|
15
18
|
attr_reader :client_key
|
16
19
|
attr_reader :client_name
|
17
20
|
|
18
|
-
# @param [String]
|
19
|
-
# @param [String]
|
20
|
-
# @param [String]
|
21
|
+
# @param [String] server_url
|
22
|
+
# @param [String] client_name
|
23
|
+
# @param [String] client_key
|
21
24
|
#
|
22
25
|
# @option options [Hash] :params
|
23
26
|
# URI query unencoded key/value pairs
|
@@ -77,8 +80,52 @@ module Ridley
|
|
77
80
|
api_type == :foss
|
78
81
|
end
|
79
82
|
|
83
|
+
# Override Faraday::Connection#run_request to catch exceptions from {Ridley::Middleware} that
|
84
|
+
# we expect. Caught exceptions are re-raised with Celluloid#abort so we don't crash the connection.
|
85
|
+
def run_request(*args)
|
86
|
+
super
|
87
|
+
rescue Errors::HTTPError => ex
|
88
|
+
abort(ex)
|
89
|
+
end
|
90
|
+
|
80
91
|
def server_url
|
81
92
|
self.url_prefix.to_s
|
82
93
|
end
|
94
|
+
|
95
|
+
# Stream the response body of a remote URL to a file on the local file system
|
96
|
+
#
|
97
|
+
# @param [String] target
|
98
|
+
# a URL to stream the response body from
|
99
|
+
# @param [String] destination
|
100
|
+
# a location on disk to stream the content of the response body to
|
101
|
+
def stream(target, destination)
|
102
|
+
FileUtils.mkdir_p(File.dirname(destination))
|
103
|
+
|
104
|
+
target = Addressable::URI.parse(target)
|
105
|
+
headers = Middleware::ChefAuth.authentication_headers(
|
106
|
+
client_name,
|
107
|
+
client_key,
|
108
|
+
http_method: "GET",
|
109
|
+
host: target.host,
|
110
|
+
path: target.path
|
111
|
+
)
|
112
|
+
|
113
|
+
unless ssl[:verify]
|
114
|
+
headers.merge!(ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE)
|
115
|
+
end
|
116
|
+
|
117
|
+
local = Tempfile.new('ridley-stream')
|
118
|
+
local.binmode
|
119
|
+
|
120
|
+
open(target, 'rb', headers) do |remote|
|
121
|
+
local.write(remote.read)
|
122
|
+
end
|
123
|
+
|
124
|
+
FileUtils.mv(local.path, destination)
|
125
|
+
rescue OpenURI::HTTPError => ex
|
126
|
+
abort(ex)
|
127
|
+
ensure
|
128
|
+
local.close(true) unless local.nil?
|
129
|
+
end
|
83
130
|
end
|
84
131
|
end
|
data/lib/ridley/errors.rb
CHANGED
@@ -5,6 +5,7 @@ module Ridley
|
|
5
5
|
class InternalError < RidleyError; end
|
6
6
|
class ArgumentError < InternalError; end
|
7
7
|
|
8
|
+
class ResourceNotFound < RidleyError; end
|
8
9
|
class ValidatorNotFound < RidleyError; end
|
9
10
|
|
10
11
|
class InvalidResource < RidleyError
|
@@ -20,6 +21,18 @@ module Ridley
|
|
20
21
|
alias_method :to_s, :message
|
21
22
|
end
|
22
23
|
|
24
|
+
class UnknownCookbookFileType < RidleyError
|
25
|
+
attr_reader :type
|
26
|
+
|
27
|
+
def initialize(type)
|
28
|
+
@type = type
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
"filetype: '#{type}'"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
23
36
|
class BootstrapError < RidleyError; end
|
24
37
|
class ClientKeyFileNotFound < BootstrapError; end
|
25
38
|
class EncryptedDataBagSecretNotFound < BootstrapError; end
|
@@ -4,6 +4,45 @@ module Ridley
|
|
4
4
|
module Middleware
|
5
5
|
# @author Jamie Winsor <jamie@vialstudios.com>
|
6
6
|
class ChefAuth < Faraday::Middleware
|
7
|
+
class << self
|
8
|
+
include Mixlib::Authentication
|
9
|
+
|
10
|
+
# Generate authentication headers for a request to a Chef Server
|
11
|
+
#
|
12
|
+
# @param [String] client_name
|
13
|
+
# @param [String] client_key
|
14
|
+
#
|
15
|
+
# @option options [String] :host
|
16
|
+
#
|
17
|
+
# @see {#signing_object} for options
|
18
|
+
def authentication_headers(client_name, client_key, options = {})
|
19
|
+
rsa_key = OpenSSL::PKey::RSA.new(File.read(client_key))
|
20
|
+
headers = signing_object(client_name, options).sign(rsa_key).merge(host: options[:host])
|
21
|
+
headers.inject({}) { |memo, kv| memo["#{kv[0].to_s.upcase}"] = kv[1];memo }
|
22
|
+
end
|
23
|
+
|
24
|
+
# Create a signing object for a Request to a Chef Server
|
25
|
+
#
|
26
|
+
# @param [String] client_name
|
27
|
+
#
|
28
|
+
# @option options [String] :http_method
|
29
|
+
# @option options [String] :path
|
30
|
+
# @option options [String] :body
|
31
|
+
# @option options [Time] :timestamp
|
32
|
+
#
|
33
|
+
# @return [SigningObject]
|
34
|
+
def signing_object(client_name, options = {})
|
35
|
+
options = options.reverse_merge(
|
36
|
+
body: String.new,
|
37
|
+
timestamp: Time.now.utc.iso8601
|
38
|
+
)
|
39
|
+
options[:user_id] = client_name
|
40
|
+
options[:proto_version] = "1.0"
|
41
|
+
|
42
|
+
SignedHeaderAuth.signing_object(options)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
7
46
|
include Ridley::Logging
|
8
47
|
|
9
48
|
attr_reader :client_name
|
@@ -12,24 +51,23 @@ module Ridley
|
|
12
51
|
def initialize(app, client_name, client_key)
|
13
52
|
super(app)
|
14
53
|
@client_name = client_name
|
15
|
-
@client_key
|
54
|
+
@client_key = client_key
|
16
55
|
end
|
17
56
|
|
18
57
|
def call(env)
|
19
|
-
|
58
|
+
signing_options = {
|
20
59
|
http_method: env[:method],
|
21
|
-
host: env[:url].host,
|
60
|
+
host: env[:url].host || "localhost",
|
22
61
|
path: env[:url].path,
|
23
|
-
body: env[:body] || ''
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
authentication_headers = sign_obj.sign(client_key)
|
62
|
+
body: env[:body] || ''
|
63
|
+
}
|
64
|
+
authentication_headers = self.class.authentication_headers(client_name, client_key, signing_options)
|
65
|
+
|
28
66
|
env[:request_headers] = default_headers.merge(env[:request_headers]).merge(authentication_headers)
|
29
67
|
env[:request_headers] = env[:request_headers].merge('Content-Length' => env[:body].bytesize.to_s) if env[:body]
|
30
68
|
|
31
|
-
log.debug { "
|
32
|
-
log.debug { env }
|
69
|
+
log.debug { "==> performing authenticated Chef request as '#{client_name}'"}
|
70
|
+
log.debug { "request env: #{env}"}
|
33
71
|
|
34
72
|
@app.call(env)
|
35
73
|
end
|
@@ -14,12 +14,14 @@ module Ridley
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
+
include Ridley::Logging
|
18
|
+
|
17
19
|
def on_complete(env)
|
18
|
-
|
19
|
-
|
20
|
+
log.debug { "==> handling Chef response" }
|
21
|
+
log.debug { "request env: #{env}" }
|
20
22
|
|
21
23
|
unless self.class.success?(env)
|
22
|
-
|
24
|
+
log.debug { "** error encounted in Chef response" }
|
23
25
|
raise Errors::HTTPError.fabricate(env)
|
24
26
|
end
|
25
27
|
end
|
@@ -51,7 +51,7 @@ module Ridley
|
|
51
51
|
# @return [String]
|
52
52
|
def response_type(env)
|
53
53
|
if env[:response_headers][CONTENT_TYPE].nil?
|
54
|
-
log.debug "
|
54
|
+
log.debug { "response did not specify a content type" }
|
55
55
|
return "text/html"
|
56
56
|
end
|
57
57
|
|
@@ -98,10 +98,10 @@ module Ridley
|
|
98
98
|
log.debug(env)
|
99
99
|
|
100
100
|
if self.class.json_response?(env)
|
101
|
-
log.debug
|
101
|
+
log.debug { "==> parsing Chef response body as JSON" }
|
102
102
|
env[:body] = self.class.parse(env[:body])
|
103
103
|
else
|
104
|
-
log.debug
|
104
|
+
log.debug { "==> Chef response did not contain a JSON body" }
|
105
105
|
end
|
106
106
|
end
|
107
107
|
end
|
data/lib/ridley/resource.rb
CHANGED
@@ -67,7 +67,7 @@ module Ridley
|
|
67
67
|
# @return [nil, Object]
|
68
68
|
def find(client, object)
|
69
69
|
find!(client, object)
|
70
|
-
rescue Errors::
|
70
|
+
rescue Errors::ResourceNotFound
|
71
71
|
nil
|
72
72
|
end
|
73
73
|
|
@@ -81,6 +81,8 @@ module Ridley
|
|
81
81
|
def find!(client, object)
|
82
82
|
chef_id = object.respond_to?(:chef_id) ? object.chef_id : object
|
83
83
|
new(client, client.connection.get("#{self.resource_path}/#{chef_id}").body)
|
84
|
+
rescue Errors::HTTPNotFound => ex
|
85
|
+
raise Errors::ResourceNotFound, ex
|
84
86
|
end
|
85
87
|
|
86
88
|
# @param [Ridley::Client] client
|
@@ -90,7 +92,7 @@ module Ridley
|
|
90
92
|
def create(client, object)
|
91
93
|
resource = new(client, object.to_hash)
|
92
94
|
new_attributes = client.connection.post(self.resource_path, resource.to_json).body
|
93
|
-
resource.
|
95
|
+
resource.mass_assign(resource._attributes_.deep_merge(new_attributes))
|
94
96
|
resource
|
95
97
|
end
|
96
98
|
|
@@ -147,7 +149,7 @@ module Ridley
|
|
147
149
|
def save
|
148
150
|
raise Errors::InvalidResource.new(self.errors) unless valid?
|
149
151
|
|
150
|
-
mass_assign(self.class.create(client, self).
|
152
|
+
mass_assign(self.class.create(client, self)._attributes_)
|
151
153
|
true
|
152
154
|
rescue Errors::HTTPConflict
|
153
155
|
self.update
|
@@ -164,7 +166,7 @@ module Ridley
|
|
164
166
|
def update
|
165
167
|
raise Errors::InvalidResource.new(self.errors) unless valid?
|
166
168
|
|
167
|
-
mass_assign(self.class.update(client, self).
|
169
|
+
mass_assign(self.class.update(client, self)._attributes_)
|
168
170
|
true
|
169
171
|
end
|
170
172
|
|
@@ -172,7 +174,7 @@ module Ridley
|
|
172
174
|
#
|
173
175
|
# @return [Object]
|
174
176
|
def reload
|
175
|
-
mass_assign(self.class.find(client, self).
|
177
|
+
mass_assign(self.class.find(client, self)._attributes_)
|
176
178
|
self
|
177
179
|
end
|
178
180
|
|
@@ -182,7 +184,7 @@ module Ridley
|
|
182
184
|
end
|
183
185
|
|
184
186
|
def to_s
|
185
|
-
self.
|
187
|
+
"#{self.chef_id}: #{self._attributes_}"
|
186
188
|
end
|
187
189
|
|
188
190
|
# @param [Object] other
|
@@ -2,16 +2,87 @@ module Ridley
|
|
2
2
|
# @author Jamie Winsor <jamie@vialstudios.com>
|
3
3
|
class CookbookResource < Ridley::Resource
|
4
4
|
class << self
|
5
|
+
# List all of the cookbooks and their versions present on the remote
|
6
|
+
#
|
7
|
+
# @example return value
|
8
|
+
# {
|
9
|
+
# "ant" => [
|
10
|
+
# "0.10.1"
|
11
|
+
# ],
|
12
|
+
# "apache2" => [
|
13
|
+
# "1.4.0"
|
14
|
+
# ]
|
15
|
+
# }
|
16
|
+
#
|
17
|
+
# @param [Ridley::Client] client
|
18
|
+
#
|
19
|
+
# @return [Hash]
|
20
|
+
# a hash containing keys which represent cookbook names and values which contain
|
21
|
+
# an array of strings representing the available versions
|
22
|
+
def all(client)
|
23
|
+
response = client.connection.get(self.resource_path).body
|
24
|
+
|
25
|
+
{}.tap do |cookbooks|
|
26
|
+
response.each do |name, details|
|
27
|
+
cookbooks[name] = details["versions"].collect { |version| version["version"] }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
5
32
|
def create(*args)
|
6
33
|
raise NotImplementedError
|
7
34
|
end
|
8
35
|
|
9
|
-
|
10
|
-
|
36
|
+
# Delete a cookbook of the given name and version on the remote Chef server
|
37
|
+
#
|
38
|
+
# @param [Ridley::Client] client
|
39
|
+
# @param [String] name
|
40
|
+
# @param [String] version
|
41
|
+
#
|
42
|
+
# @option options [Boolean] purge (false)
|
43
|
+
#
|
44
|
+
# @return [Boolean]
|
45
|
+
def delete(client, name, version, options = {})
|
46
|
+
options = options.reverse_merge(purge: false)
|
47
|
+
url = "#{self.resource_path}/#{name}/#{version}"
|
48
|
+
url += "?purge=true" if options[:purge]
|
49
|
+
|
50
|
+
client.connection.delete(url).body
|
51
|
+
true
|
52
|
+
rescue Errors::HTTPNotFound
|
53
|
+
true
|
11
54
|
end
|
12
55
|
|
13
|
-
|
14
|
-
|
56
|
+
# Delete all of the versions of a given cookbook on the remote Chef server
|
57
|
+
#
|
58
|
+
# @param [Ridley::Client] client
|
59
|
+
# @param [String] name
|
60
|
+
# name of the cookbook to delete
|
61
|
+
#
|
62
|
+
# @option options [Boolean] purge (false)
|
63
|
+
def delete_all(client, name, options = {})
|
64
|
+
versions(client, name).each do |version|
|
65
|
+
delete(client, name, version, options)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Download the entire cookbook
|
70
|
+
#
|
71
|
+
# @param [Ridley::Client] client
|
72
|
+
# @param [String] name
|
73
|
+
# @param [String] version
|
74
|
+
# @param [String] destination (Dir.mktmpdir)
|
75
|
+
# the place to download the cookbook too. If no value is provided the cookbook
|
76
|
+
# will be downloaded to a temporary location
|
77
|
+
#
|
78
|
+
# @return [String]
|
79
|
+
# the path to the directory the cookbook was downloaded to
|
80
|
+
def download(client, name, version, destination = Dir.mktmpdir)
|
81
|
+
cookbook = find(client, name, version)
|
82
|
+
|
83
|
+
unless cookbook.nil?
|
84
|
+
cookbook.download(destination)
|
85
|
+
end
|
15
86
|
end
|
16
87
|
|
17
88
|
# @param [Ridley::Client] client
|
@@ -19,7 +90,7 @@ module Ridley
|
|
19
90
|
# @param [String] version
|
20
91
|
#
|
21
92
|
# @return [nil, CookbookResource]
|
22
|
-
def find(client, object, version
|
93
|
+
def find(client, object, version)
|
23
94
|
find!(client, object, version)
|
24
95
|
rescue Errors::HTTPNotFound
|
25
96
|
nil
|
@@ -33,15 +104,40 @@ module Ridley
|
|
33
104
|
# if a resource with the given chef_id is not found
|
34
105
|
#
|
35
106
|
# @return [CookbookResource]
|
36
|
-
def find!(client, object, version
|
37
|
-
chef_id
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
107
|
+
def find!(client, object, version)
|
108
|
+
chef_id = object.respond_to?(:chef_id) ? object.chef_id : object
|
109
|
+
new(client, client.connection.get("#{self.resource_path}/#{chef_id}/#{version}").body)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Return the latest version of the given cookbook found on the remote Chef server
|
113
|
+
#
|
114
|
+
# @param [Ridley::Client] client
|
115
|
+
# @param [String] name
|
116
|
+
#
|
117
|
+
# @return [String, nil]
|
118
|
+
def latest_version(client, name)
|
119
|
+
ver = versions(client, name).collect do |version|
|
120
|
+
Solve::Version.new(version)
|
121
|
+
end.sort.last
|
43
122
|
|
44
|
-
|
123
|
+
ver.nil? ? nil : ver.to_s
|
124
|
+
end
|
125
|
+
|
126
|
+
# Return the version of the given cookbook which best stasifies the given constraint
|
127
|
+
#
|
128
|
+
# @param [Ridley::Client] client
|
129
|
+
# @param [String] name
|
130
|
+
# name of the cookbook
|
131
|
+
# @param [String, Solve::Constraint] constraint
|
132
|
+
# constraint to solve for
|
133
|
+
#
|
134
|
+
# @return [CookbookResource, nil]
|
135
|
+
# returns the cookbook resource for the best solution or nil if no solution exists
|
136
|
+
def satisfy(client, name, constraint)
|
137
|
+
version = Solve::Solver.satisfy_best(constraint, versions(client, name)).to_s
|
138
|
+
find(client, name, version)
|
139
|
+
rescue Solve::Errors::NoSolutionError
|
140
|
+
nil
|
45
141
|
end
|
46
142
|
|
47
143
|
# Save a new Cookbook Version of the given name, version with the
|
@@ -71,8 +167,39 @@ module Ridley
|
|
71
167
|
def update(*args)
|
72
168
|
raise NotImplementedError
|
73
169
|
end
|
170
|
+
|
171
|
+
# Return a list of versions for the given cookbook present on the remote Chef server
|
172
|
+
#
|
173
|
+
# @param [Ridley::Client] client
|
174
|
+
# @param [String] name
|
175
|
+
#
|
176
|
+
# @example
|
177
|
+
# versions(client, "nginx") => [ "1.0.0", "1.2.0" ]
|
178
|
+
#
|
179
|
+
# @return [Array<String>]
|
180
|
+
def versions(client, name)
|
181
|
+
response = client.connection.get("#{self.resource_path}/#{name}").body
|
182
|
+
|
183
|
+
response[name]["versions"].collect do |cb_ver|
|
184
|
+
cb_ver["version"]
|
185
|
+
end
|
186
|
+
end
|
74
187
|
end
|
75
188
|
|
189
|
+
include Ridley::Logging
|
190
|
+
|
191
|
+
FILE_TYPES = [
|
192
|
+
:resources,
|
193
|
+
:providers,
|
194
|
+
:recipes,
|
195
|
+
:definitions,
|
196
|
+
:libraries,
|
197
|
+
:attributes,
|
198
|
+
:files,
|
199
|
+
:templates,
|
200
|
+
:root_files
|
201
|
+
].freeze
|
202
|
+
|
76
203
|
set_chef_id "name"
|
77
204
|
set_chef_type "cookbook"
|
78
205
|
set_chef_json_class "Chef::Cookbook"
|
@@ -81,41 +208,166 @@ module Ridley
|
|
81
208
|
attribute :name,
|
82
209
|
required: true
|
83
210
|
|
84
|
-
|
85
|
-
|
86
|
-
|
211
|
+
attribute :attributes,
|
212
|
+
type: Array,
|
213
|
+
default: Array.new
|
87
214
|
|
88
215
|
attribute :cookbook_name,
|
89
216
|
type: String
|
90
217
|
|
91
218
|
attribute :definitions,
|
92
|
-
type: Array
|
219
|
+
type: Array,
|
220
|
+
default: Array.new
|
93
221
|
|
94
222
|
attribute :files,
|
95
|
-
type: Array
|
223
|
+
type: Array,
|
224
|
+
default: Array.new
|
96
225
|
|
97
226
|
attribute :libraries,
|
98
|
-
type: Array
|
227
|
+
type: Array,
|
228
|
+
default: Array.new
|
99
229
|
|
100
230
|
attribute :metadata,
|
101
231
|
type: Hashie::Mash
|
102
232
|
|
103
233
|
attribute :providers,
|
104
|
-
type: Array
|
234
|
+
type: Array,
|
235
|
+
default: Array.new
|
105
236
|
|
106
237
|
attribute :recipes,
|
107
|
-
type: Array
|
238
|
+
type: Array,
|
239
|
+
default: Array.new
|
108
240
|
|
109
241
|
attribute :resources,
|
110
|
-
type: Array
|
242
|
+
type: Array,
|
243
|
+
default: Array.new
|
111
244
|
|
112
245
|
attribute :root_files,
|
113
|
-
type: Array
|
246
|
+
type: Array,
|
247
|
+
default: Array.new
|
114
248
|
|
115
249
|
attribute :templates,
|
116
|
-
type: Array
|
250
|
+
type: Array,
|
251
|
+
default: Array.new
|
117
252
|
|
118
253
|
attribute :version,
|
119
254
|
type: String
|
255
|
+
|
256
|
+
# Download the entire cookbook
|
257
|
+
#
|
258
|
+
# @param [String] destination (Dir.mktmpdir)
|
259
|
+
# the place to download the cookbook too. If no value is provided the cookbook
|
260
|
+
# will be downloaded to a temporary location
|
261
|
+
#
|
262
|
+
# @return [String]
|
263
|
+
# the path to the directory the cookbook was downloaded to
|
264
|
+
def download(destination = Dir.mktmpdir)
|
265
|
+
destination = File.expand_path(destination)
|
266
|
+
log.debug { "downloading cookbook: '#{name}'" }
|
267
|
+
|
268
|
+
FILE_TYPES.each do |filetype|
|
269
|
+
next unless manifest.has_key?(filetype)
|
270
|
+
|
271
|
+
manifest[filetype].each do |file|
|
272
|
+
file_destination = File.join(destination, file[:path].gsub('/', File::SEPARATOR))
|
273
|
+
FileUtils.mkdir_p(File.dirname(file_destination))
|
274
|
+
download_file(filetype, file[:name], file_destination)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
destination
|
279
|
+
end
|
280
|
+
|
281
|
+
# Download a single file from a cookbook
|
282
|
+
#
|
283
|
+
# @param [#to_sym] filetype
|
284
|
+
# the type of file to download. These are broken up into the following types in Chef:
|
285
|
+
# - attribute (unsupported until resolved https://github.com/reset/chozo/issues/17)
|
286
|
+
# - definition
|
287
|
+
# - file
|
288
|
+
# - library
|
289
|
+
# - provider
|
290
|
+
# - recipe
|
291
|
+
# - resource
|
292
|
+
# - root_file
|
293
|
+
# - template
|
294
|
+
# these types are where the files are stored in your cookbook's structure. For example, a
|
295
|
+
# recipe would be stored in the recipes directory while a root_file is stored at the root
|
296
|
+
# of your cookbook
|
297
|
+
# @param [String] name
|
298
|
+
# name of the file to download
|
299
|
+
# @param [String] destination
|
300
|
+
# where to download the file to
|
301
|
+
#
|
302
|
+
# @return [nil]
|
303
|
+
def download_file(filetype, name, destination)
|
304
|
+
download_fun(filetype).call(name, destination)
|
305
|
+
end
|
306
|
+
|
307
|
+
# A hash containing keys for all of the different cookbook filetypes with values
|
308
|
+
# representing each file of that type this cookbook contains
|
309
|
+
#
|
310
|
+
# @example
|
311
|
+
# {
|
312
|
+
# root_files: [
|
313
|
+
# {
|
314
|
+
# :name => "afile.rb",
|
315
|
+
# :path => "files/ubuntu-9.10/afile.rb",
|
316
|
+
# :checksum => "2222",
|
317
|
+
# :specificity => "ubuntu-9.10"
|
318
|
+
# },
|
319
|
+
# ],
|
320
|
+
# templates: [ manifest_record1, ... ],
|
321
|
+
# ...
|
322
|
+
# }
|
323
|
+
#
|
324
|
+
# @return [Hash]
|
325
|
+
def manifest
|
326
|
+
{}.tap do |manifest|
|
327
|
+
FILE_TYPES.each do |filetype|
|
328
|
+
manifest[filetype] = get_attribute(filetype)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
def to_s
|
334
|
+
"#{name}: #{manifest}"
|
335
|
+
end
|
336
|
+
|
337
|
+
private
|
338
|
+
|
339
|
+
# Return a lambda for downloading a file from the cookbook of the given type
|
340
|
+
#
|
341
|
+
# @param [#to_sym] filetype
|
342
|
+
#
|
343
|
+
# @return [lambda]
|
344
|
+
# a lambda which takes to parameters: target and path. Target is the URL to download from
|
345
|
+
# and path is the location on disk to steam the contents of the remote URL to.
|
346
|
+
def download_fun(filetype)
|
347
|
+
collection = case filetype.to_sym
|
348
|
+
when :attribute, :attributes; method(:attributes)
|
349
|
+
when :definition, :definitions; method(:definitions)
|
350
|
+
when :file, :files; method(:files)
|
351
|
+
when :library, :libraries; method(:libraries)
|
352
|
+
when :provider, :providers; method(:providers)
|
353
|
+
when :recipe, :recipes; method(:recipes)
|
354
|
+
when :resource, :resources; method(:resources)
|
355
|
+
when :root_file, :root_files; method(:root_files)
|
356
|
+
when :template, :templates; method(:templates)
|
357
|
+
else
|
358
|
+
raise Errors::UnknownCookbookFileType.new(filetype)
|
359
|
+
end
|
360
|
+
|
361
|
+
->(target, destination) {
|
362
|
+
files = collection.call # JW: always chaining .call.find results in a nil value. WHY?
|
363
|
+
file = files.find { |f| f[:name] == target }
|
364
|
+
return nil if file.nil?
|
365
|
+
|
366
|
+
destination = File.expand_path(destination)
|
367
|
+
log.debug { "downloading '#{filetype}' file: #{file} to: '#{destination}'" }
|
368
|
+
|
369
|
+
client.connection.stream(file[:url], destination)
|
370
|
+
}
|
371
|
+
end
|
120
372
|
end
|
121
373
|
end
|