chef-dk 0.5.1 → 0.6.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 67e3d7e8d8c597e81964b15ae422a88eb0932d6f
4
- data.tar.gz: 304cb60cbec0463d2871ef35b6459fd6847d83ee
3
+ metadata.gz: c6b56f8142a1f44e274b102d857a291813f4808b
4
+ data.tar.gz: 64405a6fee63415614dc9d98073866d0c89d849f
5
5
  SHA512:
6
- metadata.gz: 4ea74018cf4e8a23c0bebd202878c760ac9dd4f45051de067e5f349951ab68bf1f27443eb0e81953f43d942f72c57565471719fab8c85f53bf83dea65459adb9
7
- data.tar.gz: 5de85053352e6003cac0101ff2648f86b4a32715a2a9539b30be7f7ac1a97786d513e7676820ba37023df9a85cfcc70373419e25d1b33efe4418d2c7da42904d
6
+ metadata.gz: 5dd5c2a1d046ef6252b2115b2c2db68365a55d047563ef1269c5bcab7be6c514bb5a8183fe6147e935dfac529e4f548271c32fab91cfc26fb4b3ab19a37f5b3c
7
+ data.tar.gz: 63805b53f8a1f61ea924c4960d81f4c770e0d86093b0b50313e20dbd1b589b6d97130b6d0589d1e36440e05d6a52485f6ad646b68c30c2316f40850efda4e12a
@@ -35,6 +35,8 @@ ChefDK.commands do |c|
35
35
 
36
36
  c.builtin "diff", :Diff, desc: "Generate an itemized diff of two Policyfile lock documents"
37
37
 
38
+ c.builtin "provision", :Provision, desc: "Provision VMs and clusters via cookbook"
39
+
38
40
  c.builtin "export", :Export, desc: "Export a policy lock as a Chef Zero code repo"
39
41
 
40
42
  c.builtin "verify", :Verify, desc: "Test the embedded ChefDK applications"
@@ -16,6 +16,7 @@
16
16
  #
17
17
 
18
18
  require 'chef-dk/exceptions'
19
+ require 'chef-dk/service_exceptions'
19
20
  require 'chef'
20
21
 
21
22
  module ChefDK
@@ -58,7 +59,10 @@ module ChefDK
58
59
  end
59
60
 
60
61
  def formatter
61
- @formatter ||= Chef::Formatters.new(:doc, stdout, stderr)
62
+ @formatter ||=
63
+ Chef::EventDispatch::Dispatcher.new.tap do |d|
64
+ d.register(Chef::Formatters.new(:doc, stdout, stderr))
65
+ end
62
66
  end
63
67
 
64
68
  def configure
@@ -0,0 +1,399 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2014 Chef Software Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require 'chef-dk/command/base'
19
+ require 'chef-dk/configurable'
20
+ require 'chef-dk/chef_runner'
21
+ require 'chef-dk/policyfile_services/push'
22
+
23
+ require 'chef/provisioning'
24
+
25
+ module ChefDK
26
+
27
+ module ProvisioningData
28
+
29
+ def self.reset
30
+ @context = nil
31
+ end
32
+
33
+ def self.context
34
+ @context ||= Context.new
35
+ end
36
+
37
+ class Context
38
+
39
+ attr_accessor :action
40
+
41
+ attr_accessor :node_name
42
+
43
+ attr_accessor :enable_policyfile
44
+
45
+ attr_accessor :policy_group
46
+
47
+ attr_accessor :policy_name
48
+
49
+ attr_accessor :extra_chef_config
50
+
51
+ def initialize
52
+ @extra_chef_config = ""
53
+ end
54
+
55
+ def convergence_options
56
+ {
57
+ chef_server: Chef::Config.chef_server_url,
58
+ chef_config: chef_config
59
+ }
60
+ end
61
+
62
+ def chef_config
63
+ config=<<-CONFIG
64
+ # SSL Settings:
65
+ ssl_verify_mode #{Chef::Config.ssl_verify_mode.inspect}
66
+
67
+ CONFIG
68
+ if enable_policyfile
69
+ policyfile_config=<<-CONFIG
70
+ # Policyfile Settings:
71
+ use_policyfile true
72
+ policy_document_native_api true
73
+
74
+ policy_group "#{policy_group}"
75
+ policy_name "#{policy_name}"
76
+
77
+ CONFIG
78
+ config << policyfile_config
79
+ end
80
+
81
+ config << extra_chef_config.to_s
82
+ config
83
+ end
84
+
85
+ end
86
+ end
87
+
88
+ module Command
89
+
90
+ class Provision < Base
91
+
92
+ banner(<<-E)
93
+ Usage: chef provision POLICY_GROUP --policy-name POLICY_NAME [options]
94
+ chef provision POLICY_GROUP --sync [POLICYFILE_PATH] [options]
95
+ chef provision --no-policy [options]
96
+
97
+ `chef provision` invokes an embedded chef-client run to provision machines
98
+ using Chef Provisioning. If not otherwise specified, `chef provision` will
99
+ expect to find a cookbook named 'provision' in the current working directory.
100
+ It runs a recipe in this cookbook which should use Chef Provisioning to create
101
+ one or more machines (or other infrastructure).
102
+
103
+ `chef provision` provides three forms of operation:
104
+
105
+ ### chef provision POLICY_GROUP --policy-name POLICY_NAME
106
+
107
+ In the first form of the command, `chef provision` creates machines that will
108
+ operate in policyfile mode. The chef configuration passed to the cookbook will
109
+ set the policy group and policy name as given.
110
+
111
+ ### chef provision POLICY_GROUP --sync [POLICYFILE_PATH] [options]
112
+
113
+ In the second form of the command, `chef provision` create machines that will
114
+ operate in policyfile mode and syncronizes a local policyfile to the server
115
+ before converging the machine(s) defined in the provision cookbook.
116
+
117
+ ### chef provision --no-policy [options]
118
+
119
+ In the third form of the command, `chef provision` expects to create machines
120
+ that will not operate in policyfile mode.
121
+
122
+ Note that this command is considered beta. Behavior, the APIs that pass CLI
123
+ data to chef-client, and argument names may change as more experience is gained
124
+ from real-world usage.
125
+
126
+ Chef Provisioning is documented at https://docs.chef.io/provisioning.html
127
+
128
+ Options:
129
+
130
+ E
131
+ include Configurable
132
+
133
+ option :config_file,
134
+ short: "-c CONFIG_FILE",
135
+ long: "--config CONFIG_FILE",
136
+ description: "Path to configuration file"
137
+
138
+ option :policy_name,
139
+ short: "-p POLICY_NAME",
140
+ long: "--policy-name POLICY_NAME",
141
+ description: "Set the default policy name for provisioned machines"
142
+
143
+ option :sync,
144
+ short: "-s [POLICYFILE_PATH]",
145
+ long: "--sync [POLICYFILE_PATH]",
146
+ description: "Push policyfile to the server before converging node(s)"
147
+
148
+ option :enable_policyfile,
149
+ long: "--[no-]policy",
150
+ description: "Enable/disable policyfile integration (defaults to enabled, use --no-policy to disable)",
151
+ default: true
152
+
153
+ option :destroy,
154
+ short: "-d",
155
+ long: "--destroy",
156
+ description: "Set default machine action to :destroy",
157
+ default: false,
158
+ boolean: true
159
+
160
+ option :machine_recipe,
161
+ short: "-r RECIPE",
162
+ long: "--recipe RECIPE",
163
+ description: "Machine recipe to use",
164
+ default: "default"
165
+
166
+ option :cookbook,
167
+ long: "--cookbook COOKBOOK_PATH",
168
+ description: "Path to your provisioning cookbook",
169
+ default: "./provision"
170
+
171
+ option :node_name,
172
+ short: "-n NODE_NAME",
173
+ long: "--node-name NODE_NAME",
174
+ description: "Set default node name (may be overriden by provisioning cookbook)"
175
+
176
+ option :debug,
177
+ short: "-D",
178
+ long: "--debug",
179
+ description: "Enable stacktraces and other debug output",
180
+ default: false
181
+
182
+
183
+ attr_reader :params
184
+ attr_reader :policyfile_relative_path
185
+ attr_reader :policy_group
186
+
187
+ attr_accessor :ui
188
+
189
+ def initialize(*args)
190
+ super
191
+
192
+ @ui = UI.new
193
+
194
+ @policyfile_relative_path = nil
195
+ @policy_group = nil
196
+
197
+ @provisioning_cookbook_path = nil
198
+ @provisioning_cookbook_name = nil
199
+ end
200
+
201
+ def run(params = [])
202
+ return 1 unless apply_params!(params)
203
+ chef_config # force chef config to load
204
+ return 1 unless check_cookbook_and_recipe_path
205
+
206
+ push.run if sync_policy?
207
+
208
+ setup_context
209
+
210
+ chef_runner.converge
211
+ 0
212
+ rescue ChefRunnerError, PolicyfileServiceError => e
213
+ handle_error(e)
214
+ 1
215
+ # Chef Provisioning doesn't fail gracefully when a driver is missing:
216
+ # https://github.com/chef/chef-provisioning/issues/338
217
+ rescue StandardError, LoadError => error
218
+ ui.err("Error: #{error.message}")
219
+ 1
220
+ end
221
+
222
+ # An instance of ChefRunner. Calling ChefRunner#converge will trigger
223
+ # convergence and generate the desired code.
224
+ def chef_runner
225
+ @chef_runner ||= ChefRunner.new(provisioning_cookbook_path, ["recipe[#{provisioning_cookbook_name}::#{recipe}]"])
226
+ end
227
+
228
+ def push
229
+ @push ||= PolicyfileServices::Push.new(policyfile: policyfile_relative_path,
230
+ ui: ui,
231
+ policy_group: policy_group,
232
+ config: chef_config,
233
+ root_dir: Dir.pwd)
234
+ end
235
+
236
+ def setup_context
237
+ ProvisioningData.context.tap do |c|
238
+
239
+ c.action = default_action
240
+ c.node_name = node_name
241
+
242
+ c.enable_policyfile = enable_policyfile?
243
+
244
+ if enable_policyfile?
245
+ c.policy_group = policy_group
246
+ c.policy_name = policy_name
247
+ end
248
+
249
+ end
250
+ end
251
+
252
+ def policy_name
253
+ if sync_policy?
254
+ push.policy_data["name"]
255
+ else
256
+ config[:policy_name]
257
+ end
258
+ end
259
+
260
+ def default_action
261
+ if config[:destroy]
262
+ :destroy
263
+ else
264
+ :converge
265
+ end
266
+ end
267
+
268
+ def node_name
269
+ config[:node_name]
270
+ end
271
+
272
+ def recipe
273
+ config[:machine_recipe]
274
+ end
275
+
276
+ # Gives the `cookbook_path` in the chef-client sense, which is the
277
+ # directory that contains the provisioning cookbook.
278
+ def provisioning_cookbook_path
279
+ detect_provisioning_cookbook_name_and_path! unless @provisioning_cookbook_path
280
+ @provisioning_cookbook_path
281
+ end
282
+
283
+ # The name of the provisioning cookbook
284
+ def provisioning_cookbook_name
285
+ detect_provisioning_cookbook_name_and_path! unless @provisioning_cookbook_name
286
+ @provisioning_cookbook_name
287
+ end
288
+
289
+ def cookbook_path
290
+ config[:cookbook]
291
+ end
292
+
293
+ def enable_policyfile?
294
+ config[:enable_policyfile]
295
+ end
296
+
297
+ def apply_params!(params)
298
+ remaining_args = parse_options(params)
299
+ if enable_policyfile?
300
+ handle_policy_argv(remaining_args)
301
+ else
302
+ handle_no_policy_argv(remaining_args)
303
+ end
304
+ end
305
+
306
+ def debug?
307
+ !!config[:debug]
308
+ end
309
+
310
+ def sync_policy?
311
+ config.key?(:sync)
312
+ end
313
+
314
+ private
315
+
316
+ def detect_provisioning_cookbook_name_and_path!
317
+ given_path = File.expand_path(cookbook_path, Dir.pwd)
318
+ @provisioning_cookbook_name = File.basename(given_path)
319
+ @provisioning_cookbook_path = File.dirname(given_path)
320
+ end
321
+
322
+ def check_cookbook_and_recipe_path
323
+ if !File.exist?(cookbook_expanded_path)
324
+ ui.err("ERROR: Provisioning cookbook not found at path #{cookbook_expanded_path}")
325
+ false
326
+ elsif !File.exist?(provisioning_recipe_path)
327
+ ui.err("ERROR: Provisioning recipe not found at path #{provisioning_recipe_path}")
328
+ false
329
+ else
330
+ true
331
+ end
332
+ end
333
+
334
+ def provisioning_recipe_path
335
+ File.join(cookbook_expanded_path, 'recipes', "#{recipe}.rb")
336
+ end
337
+
338
+ def cookbook_expanded_path
339
+ File.join(chef_runner.cookbook_path, provisioning_cookbook_name)
340
+ end
341
+
342
+ def handle_no_policy_argv(remaining_args)
343
+ if remaining_args.empty?
344
+ true
345
+ else
346
+ ui.err("The --no-policy flag cannot be combined with policyfile arguments")
347
+ ui.err("")
348
+ ui.err(opt_parser)
349
+ return false
350
+ end
351
+ end
352
+
353
+ def handle_policy_argv(remaining_args)
354
+ if remaining_args.size > 1
355
+ ui.err("Too many arguments")
356
+ ui.err("")
357
+ ui.err(opt_parser)
358
+ false
359
+ elsif remaining_args.size < 1
360
+ ui.err("You must specify a POLICY_GROUP or disable policyfiles with --no-policy")
361
+ ui.err("")
362
+ ui.err(opt_parser)
363
+ false
364
+ elsif !sync_policy? && config[:policy_name].nil?
365
+ ui.err("You must pass either --sync or --policy-name to provision machines in policyfile mode")
366
+ ui.err("")
367
+ ui.err(opt_parser)
368
+ false
369
+ elsif sync_policy? && config[:policy_name]
370
+ ui.err("The --policy-name and --sync arguments cannot be combined")
371
+ ui.err("")
372
+ ui.err(opt_parser)
373
+ false
374
+ elsif sync_policy?
375
+ @policy_group = remaining_args[0]
376
+ @policyfile_relative_path = config[:sync]
377
+ true
378
+ elsif config[:policy_name]
379
+ @policy_group = remaining_args[0]
380
+ true
381
+ else
382
+ raise BUG, "Cannot properly parse input argv '#{ARGV.inspect}'"
383
+ end
384
+ end
385
+
386
+ def handle_error(error)
387
+ ui.err("Error: #{error.message}")
388
+ if error.respond_to?(:reason)
389
+ ui.err("Reason: #{error.reason}")
390
+ ui.err("")
391
+ ui.err(error.extended_error_info) if debug?
392
+ ui.err(error.cause.backtrace.join("\n")) if debug?
393
+ end
394
+ end
395
+
396
+ end
397
+ end
398
+ end
399
+
@@ -27,6 +27,8 @@ module ChefDK
27
27
 
28
28
  def initialize(cookbook_path)
29
29
  @cookbook_path = cookbook_path
30
+ @unborn_branch = nil
31
+ @unborn_branch_ref = nil
30
32
  end
31
33
 
32
34
  def profile_data
@@ -46,6 +48,15 @@ module ChefDK
46
48
 
47
49
  def revision
48
50
  git!("rev-parse HEAD").stdout.strip
51
+ rescue Mixlib::ShellOut::ShellCommandFailed => e
52
+ # We may have an "unborn" branch, i.e. one with no commits.
53
+ if unborn_branch_ref
54
+ nil
55
+ else
56
+ # if we got here, but verify_ref_cmd didn't error, we don't know why
57
+ # the original git command failed, so re-raise.
58
+ raise e
59
+ end
49
60
  end
50
61
 
51
62
  def clean?
@@ -58,6 +69,15 @@ module ChefDK
58
69
 
59
70
  def synchronized_remotes
60
71
  @synchronized_remotes ||= git!("branch -r --contains #{revision}").stdout.lines.map(&:strip)
72
+ rescue Mixlib::ShellOut::ShellCommandFailed => e
73
+ # We may have an "unborn" branch, i.e. one with no commits.
74
+ if unborn_branch_ref
75
+ []
76
+ else
77
+ # if we got here, but verify_ref_cmd didn't error, we don't know why
78
+ # the original git command failed, so re-raise.
79
+ raise e
80
+ end
61
81
  end
62
82
 
63
83
  def remote
@@ -78,18 +98,54 @@ module ChefDK
78
98
  end
79
99
 
80
100
  def current_branch
81
- @current_branch ||= git!('rev-parse --abbrev-ref HEAD').stdout.strip
101
+ @current_branch ||= detect_current_branch
82
102
  end
83
103
 
84
104
  private
85
105
 
86
106
  def git!(subcommand, options={})
87
- options = { cwd: cookbook_path }.merge(options)
88
- cmd = system_command("git #{subcommand}", options)
107
+ cmd = git(subcommand, options)
89
108
  cmd.error!
90
109
  cmd
91
110
  end
92
111
 
112
+ def git(subcommand, options={})
113
+ options = { cwd: cookbook_path }.merge(options)
114
+ system_command("git #{subcommand}", options)
115
+ end
116
+
117
+ def detect_current_branch
118
+ branch = git!('rev-parse --abbrev-ref HEAD').stdout.strip
119
+ @unborn_branch = false
120
+ branch
121
+ rescue Mixlib::ShellOut::ShellCommandFailed => e
122
+ # We may have an "unborn" branch, i.e. one with no commits.
123
+ if unborn_branch_ref
124
+ unborn_branch_ref
125
+ else
126
+ # if we got here, but verify_ref_cmd didn't error, we don't know why
127
+ # the original git command failed, so re-raise.
128
+ raise e
129
+ end
130
+ end
131
+
132
+ def unborn_branch_ref
133
+ @unborn_branch_ref ||=
134
+ begin
135
+ strict_branch_ref = git!("symbolic-ref -q HEAD").stdout.strip
136
+ verify_ref_cmd = git("show-ref --verify #{strict_branch_ref}")
137
+ if verify_ref_cmd.error?
138
+ @unborn_branch = true
139
+ strict_branch_ref
140
+ else
141
+ # if we got here, but verify_ref_cmd didn't error, then `git
142
+ # rev-parse` is probably failing for a reason we haven't anticipated.
143
+ # Calling code should detect this and re-raise.
144
+ nil
145
+ end
146
+ end
147
+ end
148
+
93
149
  end
94
150
  end
95
151
  end