ridley 0.7.0.rc1 → 0.7.0.rc3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|