knife-essentials 0.9.8 → 1.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/chef/knife/delete_essentials.rb +3 -3
- data/lib/chef/knife/deps_essentials.rb +1 -1
- data/lib/chef/knife/edit_essentials.rb +1 -1
- data/lib/chef/knife/list_essentials.rb +23 -17
- data/lib/chef/knife/show_essentials.rb +14 -7
- data/lib/chef/knife/xargs_essentials.rb +1 -1
- data/lib/chef_fs/command_line.rb +100 -84
- data/lib/chef_fs/file_system.rb +77 -35
- data/lib/chef_fs/file_system/chef_server_root_dir.rb +7 -7
- data/lib/chef_fs/file_system/cookbook_dir.rb +1 -1
- data/lib/chef_fs/file_system/rest_list_entry.rb +2 -0
- data/lib/chef_fs/knife.rb +24 -4
- data/lib/chef_fs/parallelizer.rb +127 -0
- data/lib/chef_fs/raw_request.rb +8 -4
- data/lib/chef_fs/version.rb +1 -1
- data/spec/integration/chef_repo_path_spec.rb +76 -1
- data/spec/integration/delete_spec.rb +256 -13
- data/spec/integration/deps_spec.rb +20 -20
- data/spec/integration/diff_spec.rb +47 -120
- data/spec/integration/download_spec.rb +92 -237
- data/spec/integration/list_spec.rb +114 -0
- data/spec/integration/redirection_spec.rb +49 -0
- data/spec/integration/upload_spec.rb +120 -305
- data/spec/support/file_system_support.rb +1 -1
- data/spec/support/knife_support.rb +4 -0
- metadata +7 -5
@@ -81,13 +81,7 @@ module ChefFS
|
|
81
81
|
EnvironmentsDir.new(self),
|
82
82
|
RestListDir.new("roles", self, nil, ChefFS::DataHandler::RoleDataHandler.new)
|
83
83
|
]
|
84
|
-
if repo_mode == '
|
85
|
-
result += [
|
86
|
-
RestListDir.new("clients", self, nil, ChefFS::DataHandler::ClientDataHandler.new),
|
87
|
-
NodesDir.new(self),
|
88
|
-
RestListDir.new("users", self, nil, ChefFS::DataHandler::UserDataHandler.new)
|
89
|
-
]
|
90
|
-
elsif repo_mode == 'hosted_everything'
|
84
|
+
if repo_mode == 'hosted_everything'
|
91
85
|
result += [
|
92
86
|
AclsDir.new(self),
|
93
87
|
RestListDir.new("clients", self, nil, ChefFS::DataHandler::ClientDataHandler.new),
|
@@ -95,6 +89,12 @@ module ChefFS
|
|
95
89
|
RestListDir.new("groups", self, nil, ChefFS::DataHandler::GroupDataHandler.new),
|
96
90
|
NodesDir.new(self)
|
97
91
|
]
|
92
|
+
elsif repo_mode != 'static'
|
93
|
+
result += [
|
94
|
+
RestListDir.new("clients", self, nil, ChefFS::DataHandler::ClientDataHandler.new),
|
95
|
+
NodesDir.new(self),
|
96
|
+
RestListDir.new("users", self, nil, ChefFS::DataHandler::UserDataHandler.new)
|
97
|
+
]
|
98
98
|
end
|
99
99
|
result.sort_by { |child| child.name }
|
100
100
|
end
|
@@ -150,7 +150,7 @@ module ChefFS
|
|
150
150
|
return [ !exists?, nil, nil ]
|
151
151
|
end
|
152
152
|
are_same = true
|
153
|
-
ChefFS::CommandLine::diff_entries(self, other, nil, :name_only) do |type, old_entry, new_entry|
|
153
|
+
ChefFS::CommandLine::diff_entries(self, other, nil, :name_only).each do |type, old_entry, new_entry|
|
154
154
|
if [ :directory_to_file, :file_to_directory, :deleted, :added, :modified ].include?(type)
|
155
155
|
are_same = false
|
156
156
|
end
|
data/lib/chef_fs/knife.rb
CHANGED
@@ -20,6 +20,7 @@ require 'chef_fs/file_system/chef_server_root_dir'
|
|
20
20
|
require 'chef_fs/file_system/chef_repository_file_system_root_dir'
|
21
21
|
require 'chef_fs/file_pattern'
|
22
22
|
require 'chef_fs/path_utils'
|
23
|
+
require 'chef_fs/parallelizer'
|
23
24
|
require 'chef/config'
|
24
25
|
|
25
26
|
module ChefFS
|
@@ -27,16 +28,21 @@ module ChefFS
|
|
27
28
|
def self.common_options
|
28
29
|
option :repo_mode,
|
29
30
|
:long => '--repo-mode MODE',
|
30
|
-
:description => "Specifies the local repository layout. Values:
|
31
|
+
:description => "Specifies the local repository layout. Values: static, everything, hosted_everything. Default: everything/hosted_everything"
|
31
32
|
|
32
33
|
option :chef_repo_path,
|
33
34
|
:long => '--chef-repo-path PATH',
|
34
35
|
:description => 'Overrides the location of chef repo. Default is specified by chef_repo_path in the config'
|
36
|
+
|
37
|
+
option :concurrency,
|
38
|
+
:long => '--concurrency THREADS',
|
39
|
+
:description => 'Maximum number of simultaneous requests to send (default: 10)'
|
35
40
|
end
|
36
41
|
|
37
42
|
def configure_chef
|
38
43
|
super
|
39
44
|
Chef::Config[:repo_mode] = config[:repo_mode] if config[:repo_mode]
|
45
|
+
Chef::Config[:concurrency] = config[:concurrency].to_i if config[:concurrency]
|
40
46
|
|
41
47
|
# --chef-repo-path overrides all other paths
|
42
48
|
path_variables = %w(acl_path client_path cookbook_path container_path data_bag_path environment_path group_path node_path role_path user_path)
|
@@ -65,6 +71,15 @@ module ChefFS
|
|
65
71
|
Chef::Config[:role_path] = nil
|
66
72
|
end
|
67
73
|
|
74
|
+
# Default to getting *everything* from the server.
|
75
|
+
if !Chef::Config[:repo_mode]
|
76
|
+
if Chef::Config[:chef_server_url] =~ /^[^\/]+\/+organizations\//
|
77
|
+
Chef::Config[:repo_mode] = 'hosted_everything'
|
78
|
+
else
|
79
|
+
Chef::Config[:repo_mode] = 'everything'
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
68
83
|
# Infer any *_path variables that are not specified
|
69
84
|
if Chef::Config[:chef_repo_path]
|
70
85
|
path_variables.each do |variable_name|
|
@@ -76,6 +91,8 @@ module ChefFS
|
|
76
91
|
end
|
77
92
|
end
|
78
93
|
end
|
94
|
+
|
95
|
+
ChefFS::Parallelizer.threads = (Chef::Config[:concurrency] || 10) - 1
|
79
96
|
end
|
80
97
|
|
81
98
|
def chef_fs
|
@@ -91,12 +108,12 @@ module ChefFS
|
|
91
108
|
|
92
109
|
result = {}
|
93
110
|
case Chef::Config[:repo_mode]
|
94
|
-
when '
|
95
|
-
object_names = %w(
|
111
|
+
when 'static'
|
112
|
+
object_names = %w(cookbooks data_bags environments roles)
|
96
113
|
when 'hosted_everything'
|
97
114
|
object_names = %w(acls clients cookbooks containers data_bags environments groups nodes roles)
|
98
115
|
else
|
99
|
-
object_names = %w(cookbooks data_bags environments roles)
|
116
|
+
object_names = %w(clients cookbooks data_bags environments nodes roles users)
|
100
117
|
end
|
101
118
|
object_names.each do |object_name|
|
102
119
|
variable_name = "#{object_name[0..-2]}_path" # cookbooks -> cookbook_path
|
@@ -195,5 +212,8 @@ module ChefFS
|
|
195
212
|
end
|
196
213
|
end
|
197
214
|
|
215
|
+
def parallelize(inputs, options = {}, &block)
|
216
|
+
ChefFS::Parallelizer.parallelize(inputs, options, &block)
|
217
|
+
end
|
198
218
|
end
|
199
219
|
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module ChefFS
|
2
|
+
class Parallelizer
|
3
|
+
@@parallelizer = nil
|
4
|
+
@@threads = 0
|
5
|
+
|
6
|
+
def self.threads=(value)
|
7
|
+
if @@threads != value
|
8
|
+
@@threads = value
|
9
|
+
@@parallelizer = nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.parallelize(enumerator, options = {}, &block)
|
14
|
+
@@parallelizer ||= Parallelizer.new(@@threads)
|
15
|
+
@@parallelizer.parallelize(enumerator, options, &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(threads)
|
19
|
+
@tasks_mutex = Mutex.new
|
20
|
+
@tasks = []
|
21
|
+
@threads = []
|
22
|
+
1.upto(threads) do
|
23
|
+
@threads << Thread.new { worker_loop }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def parallelize(enumerator, options = {}, &block)
|
28
|
+
task = ParallelizedResults.new(enumerator, options, &block)
|
29
|
+
@tasks_mutex.synchronize do
|
30
|
+
@tasks << task
|
31
|
+
end
|
32
|
+
task
|
33
|
+
end
|
34
|
+
|
35
|
+
class ParallelizedResults
|
36
|
+
include Enumerable
|
37
|
+
|
38
|
+
def initialize(enumerator, options, &block)
|
39
|
+
@inputs = enumerator.to_a
|
40
|
+
@options = options
|
41
|
+
@block = block
|
42
|
+
|
43
|
+
@mutex = Mutex.new
|
44
|
+
@outputs = []
|
45
|
+
@status = []
|
46
|
+
end
|
47
|
+
|
48
|
+
def each
|
49
|
+
next_index = 0
|
50
|
+
while true
|
51
|
+
# Report any results that already exist
|
52
|
+
while @status.length > next_index && ([:finished, :exception].include?(@status[next_index]))
|
53
|
+
if @status[next_index] == :finished
|
54
|
+
if @options[:flatten]
|
55
|
+
@outputs[next_index].each do |entry|
|
56
|
+
yield entry
|
57
|
+
end
|
58
|
+
else
|
59
|
+
yield @outputs[next_index]
|
60
|
+
end
|
61
|
+
else
|
62
|
+
raise @outputs[next_index]
|
63
|
+
end
|
64
|
+
next_index = next_index + 1
|
65
|
+
end
|
66
|
+
|
67
|
+
# Pick up a result and process it, if there is one. This ensures we
|
68
|
+
# move forward even if there are *zero* worker threads available.
|
69
|
+
if !process_input
|
70
|
+
# Exit if we're done.
|
71
|
+
if next_index >= @status.length
|
72
|
+
break
|
73
|
+
else
|
74
|
+
# Ruby 1.8 threading sucks. Wait till we process more things.
|
75
|
+
sleep(0.05)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def process_input
|
82
|
+
# Grab the next one to process
|
83
|
+
index, input = @mutex.synchronize do
|
84
|
+
index = @status.length
|
85
|
+
if index >= @inputs.length
|
86
|
+
return nil
|
87
|
+
end
|
88
|
+
input = @inputs[index]
|
89
|
+
@status[index] = :started
|
90
|
+
[ index, input ]
|
91
|
+
end
|
92
|
+
|
93
|
+
begin
|
94
|
+
@outputs[index] = @block.call(input)
|
95
|
+
@status[index] = :finished
|
96
|
+
rescue
|
97
|
+
@outputs[index] = $!
|
98
|
+
@status[index] = :exception
|
99
|
+
end
|
100
|
+
index
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def worker_loop
|
107
|
+
while true
|
108
|
+
begin
|
109
|
+
task = @tasks[0]
|
110
|
+
if task
|
111
|
+
if !task.process_input
|
112
|
+
@tasks_mutex.synchronize do
|
113
|
+
@tasks.delete(task)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
else
|
117
|
+
# Ruby 1.8 threading sucks. Wait a bit to see if another task comes in.
|
118
|
+
sleep(0.05)
|
119
|
+
end
|
120
|
+
rescue
|
121
|
+
puts "ERROR #{$!}"
|
122
|
+
puts $!.backtrace
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/lib/chef_fs/raw_request.rb
CHANGED
@@ -23,10 +23,15 @@ module ChefFS
|
|
23
23
|
response_body = chef_rest.decompress_body(response)
|
24
24
|
|
25
25
|
if response.kind_of?(Net::HTTPSuccess)
|
26
|
-
|
26
|
+
response_body
|
27
27
|
elsif redirect_location = redirected_to(response)
|
28
|
-
|
29
|
-
|
28
|
+
if [:GET, :HEAD].include?(method)
|
29
|
+
chef_rest.follow_redirect do
|
30
|
+
api_request(chef_rest, method, chef_rest.create_url(redirect_location))
|
31
|
+
end
|
32
|
+
else
|
33
|
+
raise Exceptions::InvalidRedirect, "#{method} request was redirected from #{url} to #{redirect_location}. Only GET and HEAD support redirects."
|
34
|
+
end
|
30
35
|
else
|
31
36
|
# have to decompress the body before making an exception for it. But the body could be nil.
|
32
37
|
response.body.replace(chef_rest.decompress_body(response)) if response.body.respond_to?(:replace)
|
@@ -57,7 +62,6 @@ module ChefFS
|
|
57
62
|
response['location']
|
58
63
|
end
|
59
64
|
|
60
|
-
|
61
65
|
def self.build_headers(chef_rest, method, url, headers={}, json_body=false, raw=false)
|
62
66
|
# headers = @default_headers.merge(headers)
|
63
67
|
#headers['Accept'] = "application/json" unless raw
|
data/lib/chef_fs/version.rb
CHANGED
@@ -17,7 +17,7 @@ describe 'chef_repo_path tests' do
|
|
17
17
|
file 'roles/role1.json', {}
|
18
18
|
file 'users/user1.json', {}
|
19
19
|
|
20
|
-
file 'clients2/
|
20
|
+
file 'clients2/client2.json', {}
|
21
21
|
file 'cookbooks2/cookbook2/metadata.rb', ''
|
22
22
|
file 'data_bags2/bag2/item2.json', {}
|
23
23
|
file 'environments2/env2.json', {}
|
@@ -61,6 +61,8 @@ describe 'chef_repo_path tests' do
|
|
61
61
|
cwd 'chef_repo2'
|
62
62
|
it 'knife list --local -Rfp lists everything' do
|
63
63
|
knife('list --local -Rfp').should_succeed <<EOM
|
64
|
+
clients/
|
65
|
+
clients/client2.json
|
64
66
|
cookbooks/
|
65
67
|
cookbooks/cookbook2/
|
66
68
|
cookbooks/cookbook2/metadata.rb
|
@@ -69,8 +71,12 @@ data_bags/bag2/
|
|
69
71
|
data_bags/bag2/item2.json
|
70
72
|
environments/
|
71
73
|
environments/env2.json
|
74
|
+
nodes/
|
75
|
+
nodes/node2.json
|
72
76
|
roles/
|
73
77
|
roles/role2.json
|
78
|
+
users/
|
79
|
+
users/user2.json
|
74
80
|
EOM
|
75
81
|
end
|
76
82
|
end
|
@@ -100,6 +106,8 @@ EOM
|
|
100
106
|
cwd '.'
|
101
107
|
it 'knife list --local -Rfp lists everything' do
|
102
108
|
knife('list --local -Rfp').should_succeed <<EOM
|
109
|
+
clients/
|
110
|
+
clients/client2.json
|
103
111
|
cookbooks/
|
104
112
|
cookbooks/cookbook2/
|
105
113
|
cookbooks/cookbook2/metadata.rb
|
@@ -108,8 +116,12 @@ data_bags/bag2/
|
|
108
116
|
data_bags/bag2/item2.json
|
109
117
|
environments/
|
110
118
|
environments/env2.json
|
119
|
+
nodes/
|
120
|
+
nodes/node2.json
|
111
121
|
roles/
|
112
122
|
roles/role2.json
|
123
|
+
users/
|
124
|
+
users/user2.json
|
113
125
|
EOM
|
114
126
|
end
|
115
127
|
end
|
@@ -165,6 +177,8 @@ EOM
|
|
165
177
|
cwd 'chef_repo2'
|
166
178
|
it 'knife list --local -Rfp lists everything' do
|
167
179
|
knife('list --local -Rfp').should_succeed <<EOM
|
180
|
+
clients/
|
181
|
+
clients/client3.json
|
168
182
|
cookbooks/
|
169
183
|
cookbooks/cookbook3/
|
170
184
|
cookbooks/cookbook3/metadata.rb
|
@@ -173,8 +187,12 @@ data_bags/bag3/
|
|
173
187
|
data_bags/bag3/item3.json
|
174
188
|
environments/
|
175
189
|
environments/env3.json
|
190
|
+
nodes/
|
191
|
+
nodes/node3.json
|
176
192
|
roles/
|
177
193
|
roles/role3.json
|
194
|
+
users/
|
195
|
+
users/user3.json
|
178
196
|
EOM
|
179
197
|
end
|
180
198
|
end
|
@@ -351,6 +369,9 @@ EOM
|
|
351
369
|
cwd 'chef_repo2'
|
352
370
|
it 'knife list --local -Rfp lists everything' do
|
353
371
|
knife('list --local -Rfp').should_succeed <<EOM
|
372
|
+
clients/
|
373
|
+
clients/client1.json
|
374
|
+
clients/client2.json
|
354
375
|
cookbooks/
|
355
376
|
cookbooks/cookbook1/
|
356
377
|
cookbooks/cookbook1/metadata.rb
|
@@ -364,9 +385,15 @@ data_bags/bag2/item2.json
|
|
364
385
|
environments/
|
365
386
|
environments/env1.json
|
366
387
|
environments/env2.json
|
388
|
+
nodes/
|
389
|
+
nodes/node1.json
|
390
|
+
nodes/node2.json
|
367
391
|
roles/
|
368
392
|
roles/role1.json
|
369
393
|
roles/role2.json
|
394
|
+
users/
|
395
|
+
users/user1.json
|
396
|
+
users/user2.json
|
370
397
|
EOM
|
371
398
|
end
|
372
399
|
end
|
@@ -399,6 +426,9 @@ EOM
|
|
399
426
|
cwd '.'
|
400
427
|
it 'knife list --local -Rfp lists everything' do
|
401
428
|
knife('list --local -Rfp').should_succeed <<EOM
|
429
|
+
clients/
|
430
|
+
clients/client1.json
|
431
|
+
clients/client3.json
|
402
432
|
cookbooks/
|
403
433
|
cookbooks/cookbook1/
|
404
434
|
cookbooks/cookbook1/metadata.rb
|
@@ -412,9 +442,15 @@ data_bags/bag3/item3.json
|
|
412
442
|
environments/
|
413
443
|
environments/env1.json
|
414
444
|
environments/env3.json
|
445
|
+
nodes/
|
446
|
+
nodes/node1.json
|
447
|
+
nodes/node3.json
|
415
448
|
roles/
|
416
449
|
roles/role1.json
|
417
450
|
roles/role3.json
|
451
|
+
users/
|
452
|
+
users/user1.json
|
453
|
+
users/user3.json
|
418
454
|
EOM
|
419
455
|
end
|
420
456
|
end
|
@@ -435,6 +471,9 @@ EOM
|
|
435
471
|
cwd 'chef_repo2'
|
436
472
|
it 'knife list --local -Rfp lists everything' do
|
437
473
|
knife('list --local -Rfp').should_succeed <<EOM
|
474
|
+
clients/
|
475
|
+
clients/client1.json
|
476
|
+
clients/client3.json
|
438
477
|
cookbooks/
|
439
478
|
cookbooks/cookbook1/
|
440
479
|
cookbooks/cookbook1/metadata.rb
|
@@ -448,9 +487,15 @@ data_bags/bag3/item3.json
|
|
448
487
|
environments/
|
449
488
|
environments/env1.json
|
450
489
|
environments/env3.json
|
490
|
+
nodes/
|
491
|
+
nodes/node1.json
|
492
|
+
nodes/node3.json
|
451
493
|
roles/
|
452
494
|
roles/role1.json
|
453
495
|
roles/role3.json
|
496
|
+
users/
|
497
|
+
users/user1.json
|
498
|
+
users/user3.json
|
454
499
|
EOM
|
455
500
|
end
|
456
501
|
end
|
@@ -495,6 +540,8 @@ EOM
|
|
495
540
|
cwd 'chef_repo2'
|
496
541
|
it 'knife list --local -Rfp lists everything' do
|
497
542
|
knife('list --local -Rfp').should_succeed <<EOM
|
543
|
+
clients/
|
544
|
+
clients/client3.json
|
498
545
|
cookbooks/
|
499
546
|
cookbooks/cookbook3/
|
500
547
|
cookbooks/cookbook3/metadata.rb
|
@@ -503,8 +550,12 @@ data_bags/bag3/
|
|
503
550
|
data_bags/bag3/item3.json
|
504
551
|
environments/
|
505
552
|
environments/env3.json
|
553
|
+
nodes/
|
554
|
+
nodes/node3.json
|
506
555
|
roles/
|
507
556
|
roles/role3.json
|
557
|
+
users/
|
558
|
+
users/user3.json
|
508
559
|
EOM
|
509
560
|
end
|
510
561
|
end
|
@@ -536,6 +587,9 @@ EOM
|
|
536
587
|
cwd '.'
|
537
588
|
it 'knife list --local -Rfp lists everything' do
|
538
589
|
knife('list --local -Rfp').should_succeed <<EOM
|
590
|
+
clients/
|
591
|
+
clients/client1.json
|
592
|
+
clients/client3.json
|
539
593
|
cookbooks/
|
540
594
|
cookbooks/cookbook1/
|
541
595
|
cookbooks/cookbook1/metadata.rb
|
@@ -549,9 +603,15 @@ data_bags/bag3/item3.json
|
|
549
603
|
environments/
|
550
604
|
environments/env1.json
|
551
605
|
environments/env3.json
|
606
|
+
nodes/
|
607
|
+
nodes/node1.json
|
608
|
+
nodes/node3.json
|
552
609
|
roles/
|
553
610
|
roles/role1.json
|
554
611
|
roles/role3.json
|
612
|
+
users/
|
613
|
+
users/user1.json
|
614
|
+
users/user3.json
|
555
615
|
EOM
|
556
616
|
end
|
557
617
|
end
|
@@ -572,6 +632,9 @@ EOM
|
|
572
632
|
cwd 'chef_repo2'
|
573
633
|
it 'knife list --local -Rfp lists everything' do
|
574
634
|
knife('list --local -Rfp').should_succeed <<EOM
|
635
|
+
clients/
|
636
|
+
clients/client1.json
|
637
|
+
clients/client3.json
|
575
638
|
cookbooks/
|
576
639
|
cookbooks/cookbook1/
|
577
640
|
cookbooks/cookbook1/metadata.rb
|
@@ -585,9 +648,15 @@ data_bags/bag3/item3.json
|
|
585
648
|
environments/
|
586
649
|
environments/env1.json
|
587
650
|
environments/env3.json
|
651
|
+
nodes/
|
652
|
+
nodes/node1.json
|
653
|
+
nodes/node3.json
|
588
654
|
roles/
|
589
655
|
roles/role1.json
|
590
656
|
roles/role3.json
|
657
|
+
users/
|
658
|
+
users/user1.json
|
659
|
+
users/user3.json
|
591
660
|
EOM
|
592
661
|
end
|
593
662
|
end
|
@@ -635,6 +704,8 @@ EOM
|
|
635
704
|
cwd 'chef_repo2'
|
636
705
|
it 'knife list --local -Rfp lists everything' do
|
637
706
|
knife('list --local -Rfp').should_succeed <<EOM
|
707
|
+
clients/
|
708
|
+
clients/client3.json
|
638
709
|
cookbooks/
|
639
710
|
cookbooks/cookbook3/
|
640
711
|
cookbooks/cookbook3/metadata.rb
|
@@ -643,8 +714,12 @@ data_bags/bag/
|
|
643
714
|
data_bags/bag/item.json
|
644
715
|
environments/
|
645
716
|
environments/env3.json
|
717
|
+
nodes/
|
718
|
+
nodes/node3.json
|
646
719
|
roles/
|
647
720
|
roles/role3.json
|
721
|
+
users/
|
722
|
+
users/user3.json
|
648
723
|
EOM
|
649
724
|
end
|
650
725
|
end
|