chef-zero 2.2.1 → 3.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -0
  3. data/Rakefile +5 -1
  4. data/bin/chef-zero +17 -0
  5. data/lib/chef_zero/chef_data/acl_path.rb +139 -0
  6. data/lib/chef_zero/chef_data/cookbook_data.rb +240 -0
  7. data/lib/chef_zero/chef_data/data_normalizer.rb +207 -0
  8. data/lib/chef_zero/chef_data/default_creator.rb +446 -0
  9. data/lib/chef_zero/data_store/default_facade.rb +149 -0
  10. data/lib/chef_zero/data_store/interface_v1.rb +18 -0
  11. data/lib/chef_zero/data_store/memory_store.rb +2 -1
  12. data/lib/chef_zero/data_store/memory_store_v2.rb +3 -36
  13. data/lib/chef_zero/data_store/raw_file_store.rb +147 -0
  14. data/lib/chef_zero/data_store/v1_to_v2_adapter.rb +39 -103
  15. data/lib/chef_zero/data_store/v2_to_v1_adapter.rb +1 -1
  16. data/lib/chef_zero/endpoints/acl_endpoint.rb +38 -0
  17. data/lib/chef_zero/endpoints/acls_endpoint.rb +29 -0
  18. data/lib/chef_zero/endpoints/actor_endpoint.rb +36 -10
  19. data/lib/chef_zero/endpoints/actors_endpoint.rb +38 -6
  20. data/lib/chef_zero/endpoints/authenticate_user_endpoint.rb +15 -9
  21. data/lib/chef_zero/endpoints/container_endpoint.rb +22 -0
  22. data/lib/chef_zero/endpoints/containers_endpoint.rb +13 -0
  23. data/lib/chef_zero/endpoints/cookbook_endpoint.rb +1 -1
  24. data/lib/chef_zero/endpoints/cookbook_version_endpoint.rb +15 -14
  25. data/lib/chef_zero/endpoints/cookbooks_base.rb +2 -2
  26. data/lib/chef_zero/endpoints/data_bag_endpoint.rb +4 -4
  27. data/lib/chef_zero/endpoints/data_bag_item_endpoint.rb +5 -5
  28. data/lib/chef_zero/endpoints/data_bags_endpoint.rb +5 -4
  29. data/lib/chef_zero/endpoints/environment_cookbook_endpoint.rb +2 -2
  30. data/lib/chef_zero/endpoints/environment_cookbook_versions_endpoint.rb +6 -6
  31. data/lib/chef_zero/endpoints/environment_cookbooks_endpoint.rb +2 -2
  32. data/lib/chef_zero/endpoints/environment_endpoint.rb +5 -5
  33. data/lib/chef_zero/endpoints/environment_nodes_endpoint.rb +2 -2
  34. data/lib/chef_zero/endpoints/environment_recipes_endpoint.rb +3 -3
  35. data/lib/chef_zero/endpoints/environment_role_endpoint.rb +2 -2
  36. data/lib/chef_zero/endpoints/file_store_file_endpoint.rb +1 -1
  37. data/lib/chef_zero/endpoints/group_endpoint.rb +20 -0
  38. data/lib/chef_zero/endpoints/groups_endpoint.rb +13 -0
  39. data/lib/chef_zero/endpoints/license_endpoint.rb +25 -0
  40. data/lib/chef_zero/endpoints/node_endpoint.rb +5 -5
  41. data/lib/chef_zero/endpoints/not_found_endpoint.rb +2 -2
  42. data/lib/chef_zero/endpoints/organization_association_request_endpoint.rb +22 -0
  43. data/lib/chef_zero/endpoints/organization_association_requests_endpoint.rb +29 -0
  44. data/lib/chef_zero/endpoints/organization_authenticate_user_endpoint.rb +26 -0
  45. data/lib/chef_zero/endpoints/organization_endpoint.rb +41 -0
  46. data/lib/chef_zero/endpoints/organization_user_endpoint.rb +48 -0
  47. data/lib/chef_zero/endpoints/organization_users_endpoint.rb +14 -0
  48. data/lib/chef_zero/endpoints/organization_validator_key_endpoint.rb +20 -0
  49. data/lib/chef_zero/endpoints/organizations_endpoint.rb +55 -0
  50. data/lib/chef_zero/endpoints/principal_endpoint.rb +15 -3
  51. data/lib/chef_zero/endpoints/rest_list_endpoint.rb +8 -6
  52. data/lib/chef_zero/endpoints/rest_object_endpoint.rb +12 -10
  53. data/lib/chef_zero/endpoints/role_endpoint.rb +5 -5
  54. data/lib/chef_zero/endpoints/role_environments_endpoint.rb +2 -2
  55. data/lib/chef_zero/endpoints/sandbox_endpoint.rb +2 -2
  56. data/lib/chef_zero/endpoints/sandboxes_endpoint.rb +4 -4
  57. data/lib/chef_zero/endpoints/search_endpoint.rb +10 -10
  58. data/lib/chef_zero/endpoints/system_recovery_endpoint.rb +30 -0
  59. data/lib/chef_zero/endpoints/user_association_request_endpoint.rb +40 -0
  60. data/lib/chef_zero/endpoints/user_association_requests_count_endpoint.rb +19 -0
  61. data/lib/chef_zero/endpoints/user_association_requests_endpoint.rb +19 -0
  62. data/lib/chef_zero/endpoints/user_organizations_endpoint.rb +22 -0
  63. data/lib/chef_zero/rest_base.rb +79 -13
  64. data/lib/chef_zero/rest_error_response.rb +1 -1
  65. data/lib/chef_zero/rest_request.rb +4 -0
  66. data/lib/chef_zero/rest_router.rb +1 -0
  67. data/lib/chef_zero/rspec.rb +55 -8
  68. data/lib/chef_zero/server.rb +87 -21
  69. data/lib/chef_zero/version.rb +1 -1
  70. data/spec/run_oc_pedant.rb +53 -0
  71. data/spec/{run.rb → run_pedant.rb} +13 -4
  72. data/spec/server_spec.rb +54 -0
  73. data/spec/support/oc_pedant.rb +134 -0
  74. data/spec/support/pedant.rb +1 -1
  75. metadata +54 -13
  76. data/lib/chef_zero/cookbook_data.rb +0 -236
  77. data/lib/chef_zero/data_normalizer.rb +0 -146
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 34e265dc59ee4777d6ba4efdebaaf76a64c578f4
4
- data.tar.gz: 35725b7a8b56e88b423602d8f437b610f0e9ea7c
3
+ metadata.gz: 8c3b229916ca80eb41cf861bc986a0bbc8198b7f
4
+ data.tar.gz: b4579df1962991a5f8377528e8e296ac2b25a22c
5
5
  SHA512:
6
- metadata.gz: 052d0eefadf4f57e5cdc6de67f66707f8417af32bda63dee6287616ff0e940e67b491eb8b50b2903a3bbaf98b2a85cc4f2df303218f80d5d1e03a083ab287514
7
- data.tar.gz: 1654216d04708f136e8d84b2ead621cd1eb061f039b8d9499eac3b4c29bcf10e834790ed6c7622c37528a66f5187994b08099e1416ea591bd2f00ededc271fa6
6
+ metadata.gz: 96d614e76573211f42078149a7db53d41e455c211f9278166b8a2c4318dd68ef9d13eb00349a23fe980f54ee5dd5761fa12098ffdea89eee0cce1249d96f8ae9
7
+ data.tar.gz: e0ae0d2811a84bb800f2f46135f6279cb2eddef045491ed7764c42f137b208fc9d4b02bf10f8a9940e202aad81b87e7095435d49d8c37029d4659dbc502f5b9c
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![Stories in Ready](https://badge.waffle.io/opscode/chef-zero.png?label=ready&title=Ready)](https://waffle.io/opscode/chef-zero)[![Stories in Progress](https://badge.waffle.io/opscode/chef-zero.png?label=in+progress&title=In+Progress)](https://waffle.io/opscode/chef-zero)
2
+
1
3
  Chef Zero
2
4
  =========
3
5
 
data/Rakefile CHANGED
@@ -10,7 +10,11 @@ task :spec do
10
10
  end
11
11
 
12
12
  task :pedant do
13
- require File.expand_path('spec/run')
13
+ require File.expand_path('spec/run_pedant')
14
+ end
15
+
16
+ task :oc_pedant do
17
+ require File.expand_path('spec/run_oc_pedant')
14
18
  end
15
19
 
16
20
  task :chef_spec do
@@ -8,6 +8,7 @@ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
8
8
 
9
9
  require 'chef_zero/version'
10
10
  require 'chef_zero/server'
11
+ require 'chef_zero/data_store/raw_file_store'
11
12
  require 'optparse'
12
13
 
13
14
  def parse_port(port)
@@ -49,6 +50,18 @@ OptionParser.new do |opts|
49
50
  options[:log_level] = value
50
51
  end
51
52
 
53
+ opts.on("--multi-org", "Whether to run in multi-org mode") do |value|
54
+ options[:single_org] = nil
55
+ end
56
+
57
+ opts.on("--file-store PATH", "Persist data to files at the given path") do |value|
58
+ options[:data_store] = ChefZero::DataStore::RawFileStore.new(value)
59
+ end
60
+
61
+ opts.on("--[no-]ssl", "Use SSL with self-signed certificate(Auto generate before every run). Default: false.") do |value|
62
+ options[:ssl] = value
63
+ end
64
+
52
65
  opts.on_tail("-h", "--help", "Show this message") do
53
66
  puts opts
54
67
  exit
@@ -60,6 +73,10 @@ OptionParser.new do |opts|
60
73
  end
61
74
  end.parse!
62
75
 
76
+ if options[:data_store]
77
+ options[:data_store] = ChefZero::DataStore::DefaultFacade.new(options[:data_store], options[:single_org], false)
78
+ end
79
+
63
80
  server = ChefZero::Server.new(options)
64
81
 
65
82
  if options[:daemon]
@@ -0,0 +1,139 @@
1
+ module ChefZero
2
+ module ChefData
3
+ # Manages translations between REST and ACL data paths
4
+ # and parent paths.
5
+ #
6
+ # Suggestions
7
+ # - make /organizations/ORG/_acl and deprecate organization/_acl and organizations/_acl
8
+ # - add endpoints for /containers/(users|organizations|containers)(/_acl)
9
+ # - add PUT for */_acl
10
+ # - add endpoints for /organizations/ORG/data/containers and /organizations/ORG/cookbooks/containers
11
+ # - sane, fully documented ACL model
12
+ # - sane inheritance / override model: if actors or groups are explicitly
13
+ # specified on X, they are not inherited from X's parent
14
+ # - stop adding pivotal to acls (he already has access to what he needs)
15
+ module AclPath
16
+ ORG_DATA_TYPES = %w(clients cookbooks containers data environments groups nodes roles sandboxes)
17
+ TOP_DATA_TYPES = %w(containers organizations users)
18
+
19
+ # ACL data paths for a partition are:
20
+ # / -> /acls/root
21
+ # /TYPE -> /acls/containers/TYPE
22
+ # /TYPE/NAME -> /acls/TYPE/NAME
23
+ #
24
+ # The root partition "/" has its own acls, so it looks like this:
25
+ #
26
+ # / -> /acls/root
27
+ # /users -> /acls/containers/users
28
+ # /organizations -> /acls/containers/organizations
29
+ # /users/schlansky -> /acls/users/schlansky
30
+ #
31
+ # Each organization is its own partition, so it looks like this:
32
+ #
33
+ # /organizations/blah -> /organizations/blah/acls/root
34
+ # /organizations/blah/roles -> /organizations/blah/acls/containers/roles
35
+ # /organizations/blah/roles/web -> /organizations/blah/acls/roles/web
36
+ # /organizations/ORG is its own partition. ACLs for anything under it follow
37
+
38
+ # This method takes a Chef REST path and returns the chef-zero path
39
+ # used to look up the ACL. If an object does not have an ACL directly,
40
+ # it will return nil. Paths like /organizations/ORG/data/bag/item will
41
+ # return nil, because it is the parent path (data/bag) that has an ACL.
42
+ def self.get_acl_data_path(path)
43
+ # Things under organizations have their own acls hierarchy
44
+ if path[0] == 'organizations' && path.size >= 2
45
+ under_org = partition_acl_data_path(path[2..-1], ORG_DATA_TYPES)
46
+ if under_org
47
+ path[0..1] + under_org
48
+ end
49
+ else
50
+ partition_acl_data_path(path, TOP_DATA_TYPES)
51
+ end
52
+ end
53
+
54
+ #
55
+ # Reverse transform from acl_data_path to path.
56
+ # /acls/root -> /
57
+ # /acls/** -> /**
58
+ # /organizations/ORG/acls/root -> /organizations/ORG
59
+ # /organizations/ORG/acls/** -> /organizations/ORG/**
60
+ #
61
+ # This means that /acls/containers/nodes maps to
62
+ # /containers/nodes, not /nodes.
63
+ #
64
+ def self.get_object_path(acl_data_path)
65
+ if acl_data_path[0] == 'acls'
66
+ if acl_data_path[1] == 'root'
67
+ []
68
+ else
69
+ acl_data_path[1..-1]
70
+ end
71
+ elsif acl_data_path[0] == 'organizations' && acl_data_path[2] == 'acls'
72
+ if acl_data_path[3] == 'root'
73
+ acl_data_path[0..1]
74
+ else
75
+ acl_data_path[0..1] + acl_data_path[3..-1]
76
+ end
77
+ end
78
+ end
79
+
80
+ # Method *assumes* acl_data_path is valid.
81
+ # /organizations/BLAH's parent is /organizations
82
+ #
83
+ # An example traversal up the whole tree:
84
+ # /organizations/foo/acls/nodes/mario ->
85
+ # /organizations/foo/acls/containers/nodes ->
86
+ # /organizations/foo/acls/containers/containers ->
87
+ # /organizations/foo/acls/root ->
88
+ # /acls/containers/organizations ->
89
+ # /acls/containers/containers ->
90
+ # /acls/root ->
91
+ # nil
92
+ def self.parent_acl_data_path(acl_data_path)
93
+ if acl_data_path[0] == 'organizations'
94
+ under_org = partition_parent_acl_data_path(acl_data_path[2..-1])
95
+ if under_org
96
+ acl_data_path[0..1] + under_org
97
+ else
98
+ # ACL data path is /organizations/X/acls/root; therefore parent is "/organizations"
99
+ [ 'acls', 'containers', 'organizations' ]
100
+ end
101
+ else
102
+ partition_parent_acl_data_path(acl_data_path)
103
+ end
104
+ end
105
+
106
+ private
107
+
108
+ # /acls/root -> nil
109
+ # /acls/containers/containers -> /acls/root
110
+ # /acls/TYPE/X -> /acls/containers/TYPE
111
+ #
112
+ # Method *assumes* acl_data_path is valid.
113
+ # Returns nil if the path is /acls/root
114
+ def self.partition_parent_acl_data_path(acl_data_path)
115
+ if acl_data_path.size == 3
116
+ if acl_data_path == %w(acls containers containers)
117
+ [ 'acls', 'root' ]
118
+ else
119
+ [ 'acls', 'containers', acl_data_path[1]]
120
+ end
121
+ else
122
+ nil
123
+ end
124
+ end
125
+
126
+ def self.partition_acl_data_path(path, data_types)
127
+ if path.size == 0
128
+ [ 'acls', 'root']
129
+ elsif data_types.include?(path[0])
130
+ if path.size == 0
131
+ [ 'acls', 'containers', path[0] ]
132
+ elsif path.size == 2
133
+ [ 'acls', path[0], path[1] ]
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,240 @@
1
+ require 'digest/md5'
2
+ require 'hashie/mash'
3
+
4
+ module ChefZero
5
+ module ChefData
6
+ module CookbookData
7
+ def self.to_hash(cookbook, name, version=nil)
8
+ frozen = false
9
+ if cookbook.has_key?(:frozen)
10
+ frozen = cookbook[:frozen]
11
+ cookbook = cookbook.dup
12
+ cookbook.delete(:frozen)
13
+ end
14
+
15
+ result = files_from(cookbook)
16
+ recipe_names = result[:recipes].map do |recipe|
17
+ recipe_name = recipe[:name][0..-2]
18
+ recipe_name == 'default' ? name : "#{name}::#{recipe_name}"
19
+ end
20
+ result[:metadata] = metadata_from(cookbook, name, version, recipe_names)
21
+ result[:name] = "#{name}-#{result[:metadata][:version]}"
22
+ result[:json_class] = 'Chef::CookbookVersion'
23
+ result[:cookbook_name] = name
24
+ result[:version] = result[:metadata][:version]
25
+ result[:chef_type] = 'cookbook_version'
26
+ result[:frozen?] = true if frozen
27
+ result
28
+ end
29
+
30
+ def self.metadata_from(directory, name, version, recipe_names)
31
+ metadata = PretendCookbookMetadata.new(PretendCookbook.new(name, recipe_names))
32
+ # If both .rb and .json exist, read .rb
33
+ # TODO if recipes has 3 recipes in it, and the Ruby/JSON has only one, should
34
+ # the resulting recipe list have 1, or 3-4 recipes in it?
35
+ if has_child(directory, 'metadata.rb')
36
+ begin
37
+ file = filename(directory, 'metadata.rb') || "(#{name}/metadata.rb)"
38
+ metadata.instance_eval(read_file(directory, 'metadata.rb'), file)
39
+ rescue
40
+ ChefZero::Log.error("Error loading cookbook #{name}: #{$!}\n #{$!.backtrace.join("\n ")}")
41
+ end
42
+ elsif has_child(directory, 'metadata.json')
43
+ metadata.from_json(read_file(directory, 'metadata.json'))
44
+ end
45
+ result = {}
46
+ metadata.to_hash.each_pair do |key,value|
47
+ result[key.to_sym] = value
48
+ end
49
+ result[:version] = version if version
50
+ result
51
+ end
52
+
53
+ private
54
+
55
+ # Just enough cookbook to make a Metadata object
56
+ class PretendCookbook
57
+ def initialize(name, fully_qualified_recipe_names)
58
+ @name = name
59
+ @fully_qualified_recipe_names = fully_qualified_recipe_names
60
+ end
61
+ attr_reader :name, :fully_qualified_recipe_names
62
+ end
63
+
64
+ # Handles loading configuration values from a Chef config file
65
+ #
66
+ # @author Justin Campbell <justin.campbell@riotgames.com>
67
+ class PretendCookbookMetadata < Hash
68
+ # @param [String] path
69
+ def initialize(cookbook)
70
+ self.name(cookbook.name)
71
+ self.recipes(cookbook.fully_qualified_recipe_names)
72
+ %w(attributes grouping dependencies supports recommendations suggestions conflicting providing replacing recipes).each do |hash_arg|
73
+ self[hash_arg.to_sym] = Hashie::Mash.new
74
+ end
75
+ end
76
+
77
+ def from_json(json)
78
+ self.merge!(JSON.parse(json))
79
+ end
80
+
81
+ private
82
+
83
+ def depends(cookbook, *version_constraints)
84
+ cookbook_arg(:dependencies, cookbook, version_constraints)
85
+ end
86
+
87
+ def supports(cookbook, *version_constraints)
88
+ cookbook_arg(:supports, cookbook, version_constraints)
89
+ end
90
+
91
+ def recommends(cookbook, *version_constraints)
92
+ cookbook_arg(:recommendations, cookbook, version_constraints)
93
+ end
94
+
95
+ def suggests(cookbook, *version_constraints)
96
+ cookbook_arg(:suggestions, cookbook, version_constraints)
97
+ end
98
+
99
+ def conflicts(cookbook, *version_constraints)
100
+ cookbook_arg(:conflicting, cookbook, version_constraints)
101
+ end
102
+
103
+ def provides(cookbook, *version_constraints)
104
+ cookbook_arg(:providing, cookbook, version_constraints)
105
+ end
106
+
107
+ def replaces(cookbook, *version_constraints)
108
+ cookbook_arg(:replacing, cookbook, version_constraints)
109
+ end
110
+
111
+ def recipe(recipe, description)
112
+ self[:recipes][recipe] = description
113
+ end
114
+
115
+ def attribute(name, options)
116
+ self[:attributes][name] = options
117
+ end
118
+
119
+ def grouping(name, options)
120
+ self[:grouping][name] = options
121
+ end
122
+
123
+ def cookbook_arg(key, cookbook, version_constraints)
124
+ self[key][cookbook] = version_constraints.first || ">= 0.0.0"
125
+ end
126
+
127
+ def method_missing(key, value = nil)
128
+ if value.nil?
129
+ self[key.to_sym]
130
+ else
131
+ store key.to_sym, value
132
+ end
133
+ end
134
+ end
135
+
136
+ def self.files_from(directory)
137
+ # TODO some support .rb only
138
+ result = {
139
+ :attributes => load_child_files(directory, 'attributes', false),
140
+ :definitions => load_child_files(directory, 'definitions', false),
141
+ :recipes => load_child_files(directory, 'recipes', false),
142
+ :libraries => load_child_files(directory, 'libraries', false),
143
+ :templates => load_child_files(directory, 'templates', true),
144
+ :files => load_child_files(directory, 'files', true),
145
+ :resources => load_child_files(directory, 'resources', true),
146
+ :providers => load_child_files(directory, 'providers', true),
147
+ :root_files => load_files(directory, false)
148
+ }
149
+ set_specificity(result[:templates])
150
+ set_specificity(result[:files])
151
+ result
152
+ end
153
+
154
+ def self.has_child(directory, name)
155
+ if directory.is_a?(Hash)
156
+ directory.has_key?(name)
157
+ else
158
+ directory.child(name).exists?
159
+ end
160
+ end
161
+
162
+ def self.read_file(directory, name)
163
+ if directory.is_a?(Hash)
164
+ directory[name]
165
+ else
166
+ directory.child(name).read
167
+ end
168
+ end
169
+
170
+ def self.filename(directory, name)
171
+ if directory.respond_to?(:file_path)
172
+ File.join(directory.file_path, name)
173
+ else
174
+ nil
175
+ end
176
+ end
177
+
178
+ def self.get_directory(directory, name)
179
+ if directory.is_a?(Hash)
180
+ directory[name].is_a?(Hash) ? directory[name] : nil
181
+ else
182
+ result = directory.child(name)
183
+ result.dir? ? result : nil
184
+ end
185
+ end
186
+
187
+ def self.list(directory)
188
+ if directory.is_a?(Hash)
189
+ directory.keys
190
+ else
191
+ directory.children.map { |c| c.name }
192
+ end
193
+ end
194
+
195
+ def self.load_child_files(parent, key, recursive)
196
+ result = load_files(get_directory(parent, key), recursive)
197
+ result.each do |file|
198
+ file[:path] = "#{key}/#{file[:path]}"
199
+ end
200
+ result
201
+ end
202
+
203
+ def self.load_files(directory, recursive)
204
+ result = []
205
+ if directory
206
+ list(directory).each do |child_name|
207
+ dir = get_directory(directory, child_name)
208
+ if dir
209
+ if recursive
210
+ result += load_child_files(directory, child_name, recursive)
211
+ end
212
+ else
213
+ result += load_file(read_file(directory, child_name), child_name)
214
+ end
215
+ end
216
+ end
217
+ result
218
+ end
219
+
220
+ def self.load_file(value, name)
221
+ [{
222
+ :name => name,
223
+ :path => name,
224
+ :checksum => Digest::MD5.hexdigest(value),
225
+ :specificity => 'default'
226
+ }]
227
+ end
228
+
229
+ def self.set_specificity(files)
230
+ files.each do |file|
231
+ parts = file[:path].split('/')
232
+ raise "Only directories are allowed directly under templates or files: #{file[:path]}" if parts.size == 2
233
+ file[:specificity] = parts[1]
234
+ end
235
+ end
236
+ end
237
+ end
238
+
239
+ CookbookData = ChefData::CookbookData
240
+ end