chef-dk 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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