chef-infra-api 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/lib/chef-api.rb +96 -0
  4. data/lib/chef-api/aclable.rb +35 -0
  5. data/lib/chef-api/authentication.rb +300 -0
  6. data/lib/chef-api/boolean.rb +6 -0
  7. data/lib/chef-api/configurable.rb +80 -0
  8. data/lib/chef-api/connection.rb +507 -0
  9. data/lib/chef-api/defaults.rb +197 -0
  10. data/lib/chef-api/error_collection.rb +44 -0
  11. data/lib/chef-api/errors.rb +64 -0
  12. data/lib/chef-api/multipart.rb +164 -0
  13. data/lib/chef-api/resource.rb +21 -0
  14. data/lib/chef-api/resources/base.rb +960 -0
  15. data/lib/chef-api/resources/client.rb +84 -0
  16. data/lib/chef-api/resources/collection_proxy.rb +234 -0
  17. data/lib/chef-api/resources/cookbook.rb +24 -0
  18. data/lib/chef-api/resources/cookbook_version.rb +23 -0
  19. data/lib/chef-api/resources/data_bag.rb +136 -0
  20. data/lib/chef-api/resources/data_bag_item.rb +53 -0
  21. data/lib/chef-api/resources/environment.rb +16 -0
  22. data/lib/chef-api/resources/group.rb +16 -0
  23. data/lib/chef-api/resources/node.rb +20 -0
  24. data/lib/chef-api/resources/organization.rb +22 -0
  25. data/lib/chef-api/resources/partial_search.rb +44 -0
  26. data/lib/chef-api/resources/principal.rb +11 -0
  27. data/lib/chef-api/resources/role.rb +18 -0
  28. data/lib/chef-api/resources/search.rb +47 -0
  29. data/lib/chef-api/resources/user.rb +82 -0
  30. data/lib/chef-api/schema.rb +150 -0
  31. data/lib/chef-api/util.rb +119 -0
  32. data/lib/chef-api/validator.rb +16 -0
  33. data/lib/chef-api/validators/base.rb +82 -0
  34. data/lib/chef-api/validators/required.rb +11 -0
  35. data/lib/chef-api/validators/type.rb +23 -0
  36. data/lib/chef-api/version.rb +3 -0
  37. data/templates/errors/abstract_method.erb +5 -0
  38. data/templates/errors/cannot_regenerate_key.erb +1 -0
  39. data/templates/errors/chef_api_error.erb +1 -0
  40. data/templates/errors/file_not_found.erb +1 -0
  41. data/templates/errors/http_bad_request.erb +3 -0
  42. data/templates/errors/http_forbidden_request.erb +3 -0
  43. data/templates/errors/http_gateway_timeout.erb +3 -0
  44. data/templates/errors/http_method_not_allowed.erb +3 -0
  45. data/templates/errors/http_not_acceptable.erb +3 -0
  46. data/templates/errors/http_not_found.erb +3 -0
  47. data/templates/errors/http_server_unavailable.erb +1 -0
  48. data/templates/errors/http_unauthorized_request.erb +3 -0
  49. data/templates/errors/insufficient_file_permissions.erb +1 -0
  50. data/templates/errors/invalid_resource.erb +1 -0
  51. data/templates/errors/invalid_validator.erb +1 -0
  52. data/templates/errors/missing_url_parameter.erb +1 -0
  53. data/templates/errors/not_a_directory.erb +1 -0
  54. data/templates/errors/resource_already_exists.erb +1 -0
  55. data/templates/errors/resource_not_found.erb +1 -0
  56. data/templates/errors/resource_not_mutable.erb +1 -0
  57. data/templates/errors/unknown_attribute.erb +1 -0
  58. metadata +130 -0
@@ -0,0 +1,53 @@
1
+ module ChefAPI
2
+ class Resource::DataBagItem < Resource::Base
3
+ collection_path '/data/:bag'
4
+
5
+ schema do
6
+ attribute :id, type: String, primary: true, required: true
7
+ attribute :data, type: Hash, default: {}
8
+ end
9
+
10
+ class << self
11
+ def from_file(path, bag = File.basename(File.dirname(path)))
12
+ id, contents = Util.safe_read(path)
13
+ data = JSON.parse(contents)
14
+ data[:id] = id
15
+
16
+ bag = bag.is_a?(Resource::DataBag) ? bag : Resource::DataBag.new(name: bag)
17
+
18
+ new(data, { bag: bag.name }, bag)
19
+ end
20
+ end
21
+
22
+ attr_reader :bag
23
+
24
+ #
25
+ # Override the initialize method to move any attributes into the +data+
26
+ # hash.
27
+ #
28
+ def initialize(attributes = {}, prefix = {}, bag = nil)
29
+ @bag = bag || Resource::DataBag.fetch(prefix[:bag])
30
+
31
+ id = attributes.delete(:id) || attributes.delete('id')
32
+ super({ id: id, data: attributes }, prefix)
33
+ end
34
+
35
+
36
+ #
37
+ # Override the to_hash method to move data to the upper scope.
38
+ #
39
+ # @see (Resource::Base#to_hash)
40
+ #
41
+ def to_hash
42
+ {}.tap do |hash|
43
+ _attributes.each do |key, value|
44
+ if key == :data
45
+ hash.merge!(value)
46
+ else
47
+ hash[key] = value.respond_to?(:to_hash) ? value.to_hash : value
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,16 @@
1
+ module ChefAPI
2
+ class Resource::Environment < Resource::Base
3
+ collection_path '/environments'
4
+
5
+ schema do
6
+ attribute :name, type: String, primary: true, required: true
7
+ attribute :description, type: String
8
+ attribute :default_attributes, type: Hash, default: {}
9
+ attribute :override_attributes, type: Hash, default: {}
10
+ attribute :cookbook_versions, type: Hash, default: {}
11
+ end
12
+
13
+ has_many :cookbooks
14
+ has_many :nodes
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module ChefAPI
2
+ class Resource::Group < Resource::Base
3
+ collection_path '/groups'
4
+
5
+ schema do
6
+ attribute :groupname, type: String, primary: true, required: true
7
+ attribute :name, type: String
8
+ attribute :orgname, type: String
9
+ attribute :actors, type: Array, default: []
10
+ attribute :users, type: Array, default: []
11
+ attribute :clients, type: Array, default: []
12
+ attribute :groups, type: Array, default: []
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,20 @@
1
+ module ChefAPI
2
+ class Resource::Node < Resource::Base
3
+ include ChefAPI::AclAble
4
+ collection_path '/nodes'
5
+
6
+ schema do
7
+ attribute :name, type: String, primary: true, required: true
8
+ attribute :automatic, type: Hash, default: {}
9
+ attribute :default, type: Hash, default: {}
10
+ attribute :normal, type: Hash, default: {}
11
+ attribute :override, type: Hash, default: {}
12
+ attribute :run_list, type: Array, default: []
13
+ attribute :policy_name, type: String
14
+ attribute :policy_group, type: String
15
+
16
+ # Enterprise Chef attributes
17
+ attribute :chef_environment, type: String, default: '_default'
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ module ChefAPI
2
+ class Resource::Organization < Resource::Base
3
+ collection_path '/organizations'
4
+
5
+ schema do
6
+ attribute :name, type: String, primary: true, required: true
7
+ attribute :org_type, type: String
8
+ attribute :full_name, type: String
9
+ attribute :clientname, type: String
10
+ attribute :guid, type: String
11
+
12
+ ignore :_id
13
+ ignore :_rev
14
+ ignore :chargify_subscription_id
15
+ ignore :chargify_customer_id
16
+ ignore :billing_plan
17
+ ignore :requester_id
18
+ ignore :assigned_at
19
+ ignore 'couchrest-type'
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,44 @@
1
+ module ChefAPI
2
+ class Resource::PartialSearch < Resource::Base
3
+ collection_path '/search/:index'
4
+
5
+ schema do
6
+ attribute :total, type: Integer
7
+ attribute :start, type: Integer
8
+ attribute :rows, type: Array
9
+ end
10
+
11
+ class << self
12
+ #
13
+ # About search : https://docs.chef.io/chef_search.html
14
+ #
15
+ # @param [String] index
16
+ # the name of the index to search
17
+ # @param [Hash] keys
18
+ # key paths for the attributes to be returned
19
+ # @param [String] query
20
+ # the query string
21
+ # @param [Hash] options
22
+ # the query string
23
+ #
24
+ # @return [self]
25
+ # the current resource
26
+ #
27
+ def query(index, keys, query = '*:*', options = {})
28
+ return nil if index.nil?
29
+
30
+ params = {}.tap do |o|
31
+ o[:q] = query
32
+ o[:rows] = options[:rows] || 1000
33
+ o[:sort] = options[:sort] || 'X_CHEF_id_CHEF_X'
34
+ o[:start] = options[:start] || 0
35
+ end
36
+
37
+ path = expanded_collection_path(index: index.to_s)
38
+ response = connection.post(path, keys.to_json, params)
39
+ response['rows'].map! { |row| row['data'] }
40
+ from_json(response, index: index.to_s)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,11 @@
1
+ module ChefAPI
2
+ class Resource::Principal < Resource::Base
3
+ collection_path '/principals'
4
+
5
+ schema do
6
+ attribute :name, type: String, primary: true, required: true
7
+ attribute :type, type: String
8
+ attribute :public_key, type: String
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ module ChefAPI
2
+ class Resource::Role < Resource::Base
3
+ include ChefAPI::AclAble
4
+ collection_path '/roles'
5
+
6
+ schema do
7
+ attribute :name, type: String, primary: true, required: true
8
+ attribute :json_class, type: String, default: "Chef::Role"
9
+ attribute :description, type: String
10
+ attribute :default_attributes, type: Hash, default: {}
11
+ attribute :override_attributes, type: Hash, default: {}
12
+ attribute :run_list, type: Array, default: []
13
+ attribute :env_run_lists, type: Hash, default: {}
14
+ end
15
+
16
+ has_many :environments
17
+ end
18
+ end
@@ -0,0 +1,47 @@
1
+ module ChefAPI
2
+ class Resource::Search < Resource::Base
3
+ collection_path '/search/:index'
4
+
5
+ schema do
6
+ attribute :total, type: Integer
7
+ attribute :start, type: Integer
8
+ attribute :rows, type: Array
9
+ end
10
+
11
+ class << self
12
+ #
13
+ # About search : https://docs.chef.io/chef_search.html
14
+ #
15
+ # @param [String] index
16
+ # the name of the index to search
17
+ # @param [String] query
18
+ # the query string
19
+ # @param [Hash] options
20
+ # the query string
21
+ #
22
+ # @return [self]
23
+ # the current resource
24
+ #
25
+ def query(index, query = '*:*', options = {})
26
+ return nil if index.nil?
27
+
28
+ params = {}.tap do |o|
29
+ o[:q] = query
30
+ o[:rows] = options[:rows] || 1000
31
+ o[:sort] = options[:sort] || 'X_CHEF_id_CHEF_X'
32
+ o[:start] = options[:start] || 0
33
+ end
34
+
35
+ path = expanded_collection_path(index: index.to_s)
36
+
37
+ response = if filter_result = options[:filter_result]
38
+ connection.post(path, filter_result.to_json, params)
39
+ else
40
+ connection.get(path, params)
41
+ end
42
+
43
+ from_json(response, index: index.to_s)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,82 @@
1
+ module ChefAPI
2
+ class Resource::User < Resource::Base
3
+ collection_path '/users'
4
+
5
+ schema do
6
+ flavor :enterprise do
7
+ attribute :username, type: String, primary: true, required: true
8
+
9
+ # "Vanity" attributes
10
+ attribute :first_name, type: String
11
+ attribute :middle_name, type: String
12
+ attribute :last_name, type: String
13
+ attribute :display_name, type: String
14
+ attribute :email, type: String
15
+ attribute :city, type: String
16
+ attribute :country, type: String
17
+ attribute :twitter_account, type: String
18
+ end
19
+
20
+ flavor :open_source do
21
+ attribute :name, type: String, primary: true, required: true
22
+ end
23
+
24
+ attribute :admin, type: Boolean, default: false
25
+ attribute :public_key, type: String
26
+ attribute :private_key, type: [String, Boolean], default: false
27
+ end
28
+
29
+ has_many :organizations
30
+
31
+ class << self
32
+ #
33
+ # @see Base.each
34
+ #
35
+ def each(prefix = {}, &block)
36
+ users = collection(prefix)
37
+
38
+ # HEC/EC returns a slightly different response than OSC/CZ
39
+ if users.is_a?(Array)
40
+ users.each do |info|
41
+ name = URI.escape(info['user']['username'])
42
+ response = connection.get("/users/#{name}")
43
+ result = from_json(response, prefix)
44
+
45
+ block.call(result) if block
46
+ end
47
+ else
48
+ users.each do |_, path|
49
+ response = connection.get(path)
50
+ result = from_json(response, prefix)
51
+
52
+ block.call(result) if block
53
+ end
54
+ end
55
+ end
56
+
57
+ #
58
+ # Authenticate a user with the given +username+ and +password+.
59
+ #
60
+ # @note Requires Enterprise Chef
61
+ #
62
+ # @example Authenticate a user
63
+ # User.authenticate(username: 'user', password: 'pass')
64
+ # #=> { "status" => "linked", "user" => { ... } }
65
+ #
66
+ # @param [Hash] options
67
+ # the list of options to authenticate with
68
+ #
69
+ # @option options [String] username
70
+ # the username to authenticate with
71
+ # @option options [String] password
72
+ # the plain-text password to authenticate with
73
+ #
74
+ # @return [Hash]
75
+ # the parsed JSON response from the server
76
+ #
77
+ def authenticate(options = {})
78
+ connection.post('/authenticate_user', options.to_json)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,150 @@
1
+ module ChefAPI
2
+ #
3
+ # A wrapper class that describes a remote schema (such as the Chef Server
4
+ # API layer), with validation and other magic spinkled on top.
5
+ #
6
+ class Schema
7
+
8
+ #
9
+ # The full list of attributes defined on this schema.
10
+ #
11
+ # @return [Hash]
12
+ #
13
+ attr_reader :attributes
14
+
15
+ attr_reader :ignored_attributes
16
+
17
+ #
18
+ # The list of defined validators for this schema.
19
+ #
20
+ # @return [Array]
21
+ #
22
+ attr_reader :validators
23
+
24
+ #
25
+ # Create a new schema and evaulte the block contents in a clean room.
26
+ #
27
+ def initialize(&block)
28
+ @attributes = {}
29
+ @ignored_attributes = {}
30
+ @flavor_attributes = {}
31
+ @validators = []
32
+
33
+ unlock { instance_eval(&block) } if block
34
+ end
35
+
36
+ #
37
+ # The defined primary key for this schema. If no primary key is given, it
38
+ # is assumed to be the first item in the list.
39
+ #
40
+ # @return [Symbol]
41
+ #
42
+ def primary_key
43
+ @primary_key ||= @attributes.first[0]
44
+ end
45
+
46
+ #
47
+ # Create a lazy-loaded block for a given flavor.
48
+ #
49
+ # @example Create a block for Enterprise Chef
50
+ # flavor :enterprise do
51
+ # attribute :custom_value
52
+ # end
53
+ #
54
+ # @param [Symbol] id
55
+ # the id of the flavor to target
56
+ # @param [Proc] block
57
+ # the block to capture
58
+ #
59
+ # @return [Proc]
60
+ # the given block
61
+ #
62
+ def flavor(id, &block)
63
+ @flavor_attributes[id] = block
64
+ block
65
+ end
66
+
67
+ #
68
+ # Load the flavor block for the given id.
69
+ #
70
+ # @param [Symbol] id
71
+ # the id of the flavor to target
72
+ #
73
+ # @return [true, false]
74
+ # true if the flavor existed and was evaluted, false otherwise
75
+ #
76
+ def load_flavor(id)
77
+ if block = @flavor_attributes[id]
78
+ unlock { instance_eval(&block) }
79
+ true
80
+ else
81
+ false
82
+ end
83
+ end
84
+
85
+ #
86
+ # DSL method for defining an attribute.
87
+ #
88
+ # @param [Symbol] key
89
+ # the key to use
90
+ # @param [Hash] options
91
+ # a list of options to create the attribute with
92
+ #
93
+ # @return [Symbol]
94
+ # the attribute
95
+ #
96
+ def attribute(key, options = {})
97
+ if primary_key = options.delete(:primary)
98
+ @primary_key = key.to_sym
99
+ end
100
+
101
+ @attributes[key] = options.delete(:default)
102
+
103
+ # All remaining options are assumed to be validations
104
+ options.each do |validation, options|
105
+ if options
106
+ @validators << Validator.find(validation).new(key, options)
107
+ end
108
+ end
109
+
110
+ key
111
+ end
112
+
113
+ #
114
+ # Ignore an attribute. This is handy if you know there's an attribute that
115
+ # the remote server will return, but you don't want that information
116
+ # exposed to the user (or the data is sensitive).
117
+ #
118
+ # @param [Array<Symbol>] keys
119
+ # the list of attributes to ignore
120
+ #
121
+ def ignore(*keys)
122
+ keys.each do |key|
123
+ @ignored_attributes[key.to_sym] = true
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ #
130
+ # @private
131
+ #
132
+ # Helper method to duplicate and unfreeze all the attributes in the schema,
133
+ # yield control to the user for modification in the current context, and
134
+ # then re-freeze the variables for modification.
135
+ #
136
+ def unlock
137
+ @attributes = @attributes.dup
138
+ @ignored_attributes = @ignored_attributes.dup
139
+ @flavor_attributes = @flavor_attributes.dup
140
+ @validators = @validators.dup
141
+
142
+ yield
143
+
144
+ @attributes.freeze
145
+ @ignored_attributes.freeze
146
+ @flavor_attributes.freeze
147
+ @validators.freeze
148
+ end
149
+ end
150
+ end