chef-infra-api 0.9.1
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.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/lib/chef-api.rb +96 -0
- data/lib/chef-api/aclable.rb +35 -0
- data/lib/chef-api/authentication.rb +300 -0
- data/lib/chef-api/boolean.rb +6 -0
- data/lib/chef-api/configurable.rb +80 -0
- data/lib/chef-api/connection.rb +507 -0
- data/lib/chef-api/defaults.rb +197 -0
- data/lib/chef-api/error_collection.rb +44 -0
- data/lib/chef-api/errors.rb +64 -0
- data/lib/chef-api/multipart.rb +164 -0
- data/lib/chef-api/resource.rb +21 -0
- data/lib/chef-api/resources/base.rb +960 -0
- data/lib/chef-api/resources/client.rb +84 -0
- data/lib/chef-api/resources/collection_proxy.rb +234 -0
- data/lib/chef-api/resources/cookbook.rb +24 -0
- data/lib/chef-api/resources/cookbook_version.rb +23 -0
- data/lib/chef-api/resources/data_bag.rb +136 -0
- data/lib/chef-api/resources/data_bag_item.rb +53 -0
- data/lib/chef-api/resources/environment.rb +16 -0
- data/lib/chef-api/resources/group.rb +16 -0
- data/lib/chef-api/resources/node.rb +20 -0
- data/lib/chef-api/resources/organization.rb +22 -0
- data/lib/chef-api/resources/partial_search.rb +44 -0
- data/lib/chef-api/resources/principal.rb +11 -0
- data/lib/chef-api/resources/role.rb +18 -0
- data/lib/chef-api/resources/search.rb +47 -0
- data/lib/chef-api/resources/user.rb +82 -0
- data/lib/chef-api/schema.rb +150 -0
- data/lib/chef-api/util.rb +119 -0
- data/lib/chef-api/validator.rb +16 -0
- data/lib/chef-api/validators/base.rb +82 -0
- data/lib/chef-api/validators/required.rb +11 -0
- data/lib/chef-api/validators/type.rb +23 -0
- data/lib/chef-api/version.rb +3 -0
- data/templates/errors/abstract_method.erb +5 -0
- data/templates/errors/cannot_regenerate_key.erb +1 -0
- data/templates/errors/chef_api_error.erb +1 -0
- data/templates/errors/file_not_found.erb +1 -0
- data/templates/errors/http_bad_request.erb +3 -0
- data/templates/errors/http_forbidden_request.erb +3 -0
- data/templates/errors/http_gateway_timeout.erb +3 -0
- data/templates/errors/http_method_not_allowed.erb +3 -0
- data/templates/errors/http_not_acceptable.erb +3 -0
- data/templates/errors/http_not_found.erb +3 -0
- data/templates/errors/http_server_unavailable.erb +1 -0
- data/templates/errors/http_unauthorized_request.erb +3 -0
- data/templates/errors/insufficient_file_permissions.erb +1 -0
- data/templates/errors/invalid_resource.erb +1 -0
- data/templates/errors/invalid_validator.erb +1 -0
- data/templates/errors/missing_url_parameter.erb +1 -0
- data/templates/errors/not_a_directory.erb +1 -0
- data/templates/errors/resource_already_exists.erb +1 -0
- data/templates/errors/resource_not_found.erb +1 -0
- data/templates/errors/resource_not_mutable.erb +1 -0
- data/templates/errors/unknown_attribute.erb +1 -0
- metadata +130 -0
@@ -0,0 +1,197 @@
|
|
1
|
+
require 'chef-api/version'
|
2
|
+
require 'pathname'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module ChefAPI
|
6
|
+
module Defaults
|
7
|
+
# Default API endpoint
|
8
|
+
ENDPOINT = 'https://api.opscode.com/'.freeze
|
9
|
+
|
10
|
+
# Default User Agent header string
|
11
|
+
USER_AGENT = "ChefAPI Ruby Gem #{ChefAPI::VERSION}".freeze
|
12
|
+
|
13
|
+
class << self
|
14
|
+
#
|
15
|
+
# The list of calculated default options for the configuration.
|
16
|
+
#
|
17
|
+
# @return [Hash]
|
18
|
+
#
|
19
|
+
def options
|
20
|
+
Hash[Configurable.keys.map { |key| [key, send(key)] }]
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# The Chef API configuration
|
25
|
+
#
|
26
|
+
# @return [Hash]
|
27
|
+
def config
|
28
|
+
path = config_path
|
29
|
+
@config ||= path.exist? ? JSON.parse(path.read) : {}
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Pathname to configuration file, or a blank Pathname.
|
34
|
+
#
|
35
|
+
# @return [Pathname] an expanded Pathname or a non-existent Pathname
|
36
|
+
def config_path
|
37
|
+
if result = chef_api_config_path
|
38
|
+
Pathname(result).expand_path
|
39
|
+
else
|
40
|
+
Pathname('')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# String representation of path to configuration file
|
46
|
+
#
|
47
|
+
# @return [String, nil] Path to config file, or nil
|
48
|
+
def chef_api_config_path
|
49
|
+
ENV['CHEF_API_CONFIG'] || if ENV.key?('HOME')
|
50
|
+
'~/.chef-api'
|
51
|
+
else
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# The endpoint where the Chef Server lives. This is equivalent to the
|
58
|
+
# +chef_server_url+ in Chef terminology. If you are using Enterprise
|
59
|
+
# Hosted Chef or Enterprise Chef on premise, this endpoint should include
|
60
|
+
# your organization name. For example:
|
61
|
+
#
|
62
|
+
# https://api.opscode.com/organizations/bacon
|
63
|
+
#
|
64
|
+
# If you are running Open Source Chef Server or Chef Zero, this is the
|
65
|
+
# full URL to your Chef Server instance, including the server port and
|
66
|
+
# FQDN.
|
67
|
+
#
|
68
|
+
# https://chef.server.local:4567/
|
69
|
+
#
|
70
|
+
# @return [String] (default: +https://api.opscode.com/+)
|
71
|
+
#
|
72
|
+
def endpoint
|
73
|
+
ENV['CHEF_API_ENDPOINT'] || config['CHEF_API_ENDPOINT'] || ENDPOINT
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# The flavor of the target Chef Server. There are two possible values:
|
78
|
+
#
|
79
|
+
# - enterprise
|
80
|
+
# - open_source
|
81
|
+
#
|
82
|
+
# "Enterprise" covers both Hosted Chef and Enterprise Chef. "Open Source"
|
83
|
+
# covers both Chef Zero and Open Source Chef Server.
|
84
|
+
#
|
85
|
+
# @return [true, false]
|
86
|
+
#
|
87
|
+
def flavor
|
88
|
+
if ENV['CHEF_API_FLAVOR']
|
89
|
+
ENV['CHEF_API_FLAVOR'].to_sym
|
90
|
+
elsif config['CHEF_API_FLAVOR']
|
91
|
+
config['CHEF_API_FLAVOR']
|
92
|
+
else
|
93
|
+
endpoint.include?('/organizations') ? :enterprise : :open_source
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
#
|
98
|
+
# The User Agent header to send along.
|
99
|
+
#
|
100
|
+
# @return [String]
|
101
|
+
#
|
102
|
+
def user_agent
|
103
|
+
ENV['CHEF_API_USER_AGENT'] || config['CHEF_API_USER_AGENT'] || USER_AGENT
|
104
|
+
end
|
105
|
+
|
106
|
+
#
|
107
|
+
# The name of the Chef API client. This is the equivalent of
|
108
|
+
# +client_name+ in Chef terminology. In most cases, this is your Chef
|
109
|
+
# username.
|
110
|
+
#
|
111
|
+
# @return [String, nil]
|
112
|
+
#
|
113
|
+
def client
|
114
|
+
ENV['CHEF_API_CLIENT'] || config['CHEF_API_CLIENT']
|
115
|
+
end
|
116
|
+
|
117
|
+
#
|
118
|
+
# The private key to authentication against the Chef Server. This is
|
119
|
+
# equivalent to the +client_key+ in Chef terminology. This value can
|
120
|
+
# be the client key in plain text or a path to the key on disk.
|
121
|
+
#
|
122
|
+
# @return [String, nil]
|
123
|
+
#
|
124
|
+
def key
|
125
|
+
ENV['CHEF_API_KEY'] || config['CHEF_API_KEY']
|
126
|
+
end
|
127
|
+
#
|
128
|
+
# The HTTP Proxy server address as a string
|
129
|
+
#
|
130
|
+
# @return [String, nil]
|
131
|
+
#
|
132
|
+
def proxy_address
|
133
|
+
ENV['CHEF_API_PROXY_ADDRESS'] || config['CHEF_API_PROXY_ADDRESS']
|
134
|
+
end
|
135
|
+
|
136
|
+
#
|
137
|
+
# The HTTP Proxy user password as a string
|
138
|
+
#
|
139
|
+
# @return [String, nil]
|
140
|
+
#
|
141
|
+
def proxy_password
|
142
|
+
ENV['CHEF_API_PROXY_PASSWORD'] || config['CHEF_API_PROXY_PASSWORD']
|
143
|
+
end
|
144
|
+
|
145
|
+
#
|
146
|
+
# The HTTP Proxy server port as a string
|
147
|
+
#
|
148
|
+
# @return [String, nil]
|
149
|
+
#
|
150
|
+
def proxy_port
|
151
|
+
ENV['CHEF_API_PROXY_PORT'] || config['CHEF_API_PROXY_PORT']
|
152
|
+
end
|
153
|
+
|
154
|
+
#
|
155
|
+
# The HTTP Proxy server username as a string
|
156
|
+
#
|
157
|
+
# @return [String, nil]
|
158
|
+
#
|
159
|
+
def proxy_username
|
160
|
+
ENV['CHEF_API_PROXY_USERNAME'] || config['CHEF_API_PROXY_USERNAME']
|
161
|
+
end
|
162
|
+
|
163
|
+
#
|
164
|
+
# The path to a pem file on disk for use with a custom SSL verification
|
165
|
+
#
|
166
|
+
# @return [String, nil]
|
167
|
+
#
|
168
|
+
def ssl_pem_file
|
169
|
+
ENV['CHEF_API_SSL_PEM_FILE'] || config['CHEF_API_SSL_PEM_FILE']
|
170
|
+
end
|
171
|
+
|
172
|
+
#
|
173
|
+
# Verify SSL requests (default: true)
|
174
|
+
#
|
175
|
+
# @return [true, false]
|
176
|
+
#
|
177
|
+
def ssl_verify
|
178
|
+
if ENV['CHEF_API_SSL_VERIFY'].nil? && config['CHEF_API_SSL_VERIFY'].nil?
|
179
|
+
true
|
180
|
+
else
|
181
|
+
%w[t y].include?(ENV['CHEF_API_SSL_VERIFY'].downcase[0]) || config['CHEF_API_SSL_VERIFY']
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
#
|
186
|
+
# Network request read timeout in seconds (default: 60)
|
187
|
+
#
|
188
|
+
# @return [Integer, nil]
|
189
|
+
#
|
190
|
+
def read_timeout
|
191
|
+
timeout_from_env = ENV['CHEF_API_READ_TIMEOUT'] || config['CHEF_API_READ_TIMEOUT']
|
192
|
+
|
193
|
+
Integer(timeout_from_env) unless timeout_from_env.nil?
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module ChefAPI
|
2
|
+
#
|
3
|
+
# Private internal class for managing the error collection.
|
4
|
+
#
|
5
|
+
class ErrorCollection < Hash
|
6
|
+
#
|
7
|
+
# The default proc for the hash needs to be an empty Array.
|
8
|
+
#
|
9
|
+
# @return [Proc]
|
10
|
+
#
|
11
|
+
def initialize
|
12
|
+
super { |h, k| h[k] = [] }
|
13
|
+
end
|
14
|
+
|
15
|
+
#
|
16
|
+
# Add a new error to the hash.
|
17
|
+
#
|
18
|
+
# @param [Symbol] key
|
19
|
+
# the attribute key
|
20
|
+
# @param [String] error
|
21
|
+
# the error message to push
|
22
|
+
#
|
23
|
+
# @return [self]
|
24
|
+
#
|
25
|
+
def add(key, error)
|
26
|
+
self[key].push(error)
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Output the full messages for each error. This is useful for displaying
|
32
|
+
# information about validation to the user when something goes wrong.
|
33
|
+
#
|
34
|
+
# @return [Array<String>]
|
35
|
+
#
|
36
|
+
def full_messages
|
37
|
+
self.map do |key, errors|
|
38
|
+
errors.map do |error|
|
39
|
+
"`#{key}' #{error}"
|
40
|
+
end
|
41
|
+
end.flatten
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module ChefAPI
|
4
|
+
module Error
|
5
|
+
class ErrorBinding
|
6
|
+
def initialize(options = {})
|
7
|
+
options.each do |key, value|
|
8
|
+
instance_variable_set(:"@#{key}", value)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_binding
|
13
|
+
binding
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class ChefAPIError < StandardError
|
18
|
+
def initialize(options = {})
|
19
|
+
@options = options
|
20
|
+
@filename = options.delete(:_template)
|
21
|
+
|
22
|
+
super()
|
23
|
+
end
|
24
|
+
|
25
|
+
def message
|
26
|
+
erb = ERB.new(File.read(template))
|
27
|
+
erb.result(ErrorBinding.new(@options).get_binding)
|
28
|
+
end
|
29
|
+
alias_method :to_s, :message
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def template
|
34
|
+
class_name = self.class.to_s.split('::').last
|
35
|
+
filename = @filename || Util.underscore(class_name)
|
36
|
+
ChefAPI.root.join('templates', 'errors', "#{filename}.erb")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class AbstractMethod < ChefAPIError; end
|
41
|
+
class CannotRegenerateKey < ChefAPIError; end
|
42
|
+
class FileNotFound < ChefAPIError; end
|
43
|
+
|
44
|
+
class HTTPError < ChefAPIError; end
|
45
|
+
class HTTPBadRequest < HTTPError; end
|
46
|
+
class HTTPForbiddenRequest < HTTPError; end
|
47
|
+
class HTTPGatewayTimeout < HTTPError; end
|
48
|
+
class HTTPNotAcceptable < HTTPError; end
|
49
|
+
class HTTPNotFound < HTTPError; end
|
50
|
+
class HTTPMethodNotAllowed < HTTPError; end
|
51
|
+
class HTTPServerUnavailable < HTTPError; end
|
52
|
+
|
53
|
+
class HTTPUnauthorizedRequest < ChefAPIError; end
|
54
|
+
class InsufficientFilePermissions < ChefAPIError; end
|
55
|
+
class InvalidResource < ChefAPIError; end
|
56
|
+
class InvalidValidator < ChefAPIError; end
|
57
|
+
class MissingURLParameter < ChefAPIError; end
|
58
|
+
class NotADirectory < ChefAPIError; end
|
59
|
+
class ResourceAlreadyExists < ChefAPIError; end
|
60
|
+
class ResourceNotFound < ChefAPIError; end
|
61
|
+
class ResourceNotMutable < ChefAPIError; end
|
62
|
+
class UnknownAttribute < ChefAPIError; end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'mime/types'
|
3
|
+
|
4
|
+
module ChefAPI
|
5
|
+
module Multipart
|
6
|
+
BOUNDARY = '------ChefAPIMultipartBoundary'.freeze
|
7
|
+
|
8
|
+
class Body
|
9
|
+
def initialize(params = {})
|
10
|
+
params.each do |key, value|
|
11
|
+
if value.respond_to?(:read)
|
12
|
+
parts << FilePart.new(key, value)
|
13
|
+
else
|
14
|
+
parts << ParamPart.new(key, value)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
parts << EndingPart.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def stream
|
22
|
+
MultiIO.new(*parts.map(&:io))
|
23
|
+
end
|
24
|
+
|
25
|
+
def content_type
|
26
|
+
"multipart/form-data; boundary=#{BOUNDARY}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def content_length
|
30
|
+
parts.map(&:size).inject(:+)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def parts
|
36
|
+
@parts ||= []
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class MultiIO
|
41
|
+
attr_reader :ios
|
42
|
+
|
43
|
+
def initialize(*ios)
|
44
|
+
@ios = ios
|
45
|
+
@index = 0
|
46
|
+
end
|
47
|
+
|
48
|
+
# Read from IOs in order until `length` bytes have been received.
|
49
|
+
def read(length = nil, outbuf = nil)
|
50
|
+
got_result = false
|
51
|
+
outbuf = outbuf ? outbuf.replace('') : ''
|
52
|
+
|
53
|
+
while io = current_io
|
54
|
+
if result = io.read(length)
|
55
|
+
got_result ||= !result.nil?
|
56
|
+
result.force_encoding('BINARY') if result.respond_to?(:force_encoding)
|
57
|
+
outbuf << result
|
58
|
+
length -= result.length if length
|
59
|
+
break if length == 0
|
60
|
+
end
|
61
|
+
advance_io
|
62
|
+
end
|
63
|
+
|
64
|
+
(!got_result && length) ? nil : outbuf
|
65
|
+
end
|
66
|
+
|
67
|
+
def rewind
|
68
|
+
@ios.each { |io| io.rewind }
|
69
|
+
@index = 0
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def current_io
|
75
|
+
@ios[@index]
|
76
|
+
end
|
77
|
+
|
78
|
+
def advance_io
|
79
|
+
@index += 1
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# A generic key => value part.
|
85
|
+
#
|
86
|
+
class ParamPart
|
87
|
+
def initialize(name, value)
|
88
|
+
@part = build(name, value)
|
89
|
+
end
|
90
|
+
|
91
|
+
def io
|
92
|
+
@io ||= StringIO.new(@part)
|
93
|
+
end
|
94
|
+
|
95
|
+
def size
|
96
|
+
@part.bytesize
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def build(name, value)
|
102
|
+
part = %|--#{BOUNDARY}\r\n|
|
103
|
+
part << %|Content-Disposition: form-data; name="#{CGI.escape(name)}"\r\n\r\n|
|
104
|
+
part << %|#{value}\r\n|
|
105
|
+
part
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
#
|
110
|
+
# A File part
|
111
|
+
#
|
112
|
+
class FilePart
|
113
|
+
def initialize(name, file)
|
114
|
+
@file = file
|
115
|
+
@head = build(name, file)
|
116
|
+
@foot = "\r\n"
|
117
|
+
end
|
118
|
+
|
119
|
+
def io
|
120
|
+
@io ||= MultiIO.new(
|
121
|
+
StringIO.new(@head),
|
122
|
+
@file,
|
123
|
+
StringIO.new(@foot)
|
124
|
+
)
|
125
|
+
end
|
126
|
+
|
127
|
+
def size
|
128
|
+
@head.bytesize + @file.size + @foot.bytesize
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def build(name, file)
|
134
|
+
filename = File.basename(file.path)
|
135
|
+
mime_type = MIME::Types.type_for(filename)[0] || MIME::Types['application/octet-stream'][0]
|
136
|
+
|
137
|
+
part = %|--#{BOUNDARY}\r\n|
|
138
|
+
part << %|Content-Disposition: form-data; name="#{CGI.escape(name)}"; filename="#{filename}"\r\n|
|
139
|
+
part << %|Content-Length: #{file.size}\r\n|
|
140
|
+
part << %|Content-Type: #{mime_type.simplified}\r\n|
|
141
|
+
part << %|Content-Transfer-Encoding: binary\r\n|
|
142
|
+
part << %|\r\n|
|
143
|
+
part
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
#
|
148
|
+
# The end of the entire request
|
149
|
+
#
|
150
|
+
class EndingPart
|
151
|
+
def initialize
|
152
|
+
@part = "--#{BOUNDARY}--\r\n\r\n"
|
153
|
+
end
|
154
|
+
|
155
|
+
def io
|
156
|
+
@io ||= StringIO.new(@part)
|
157
|
+
end
|
158
|
+
|
159
|
+
def size
|
160
|
+
@part.bytesize
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|