chef-apply 0.1.2 → 0.1.15

Sign up to get free protection for your applications and to get access to all the features.
@@ -25,10 +25,7 @@ module ChefApply
25
25
  # it with various recipes, attributes, config, etc. and delete it when the
26
26
  # cookbook is no longer necessary
27
27
  class TempCookbook
28
- attr_reader :path
29
-
30
- # We expect name to come in as a list of strings - resource/resource_name
31
- # or cookbook/recipe combination
28
+ attr_reader :path, :descriptor, :from
32
29
  def initialize
33
30
  @path = Dir.mktmpdir("cw")
34
31
  end
@@ -40,12 +37,13 @@ module ChefApply
40
37
  if cb
41
38
  # Full existing cookbook - only needs policyfile
42
39
  ChefApply::Log.debug("Found full cookbook at path '#{cb[:path]}' and using recipe '#{cb[:recipe_name]}'")
43
- name = cb[:name]
40
+ @descriptor = "#{cb[:name]}::#{cb[:recipe_name]}"
41
+ @from = "#{cb[:path]}"
44
42
  recipe_name = cb[:recipe_name]
43
+ cb_name = cb[:name]
45
44
  FileUtils.cp_r(cb[:path], path)
46
45
  # cp_r copies the whole existing cookbook into the tempdir so need to reset our path
47
46
  @path = File.join(path, File.basename(cb[:path]))
48
- generate_policyfile(name, recipe_name)
49
47
  else
50
48
  # Cookbook from single recipe not in a cookbook. We create the full cookbook
51
49
  # structure including metadata, then generate policyfile. We set the cookbook
@@ -53,27 +51,32 @@ module ChefApply
53
51
  # in the future
54
52
  ChefApply::Log.debug("Found single recipe at path '#{existing_recipe_path}'")
55
53
  recipe = File.basename(existing_recipe_path)
56
- recipe_name = File.basename(existing_recipe_path, ext_name)
57
- name = "cw_recipe"
54
+ recipe_name = File.basename(recipe, ext_name)
55
+ cb_name = "cw_recipe"
56
+ @descriptor = "#{recipe_name}"
57
+ @from = existing_recipe_path
58
58
  recipes_dir = generate_recipes_dir
59
59
  # This has the potential to break if they specify a recipe without a .rb
60
60
  # extension, but lets wait to deal with that bug until we encounter it
61
61
  FileUtils.cp(existing_recipe_path, File.join(recipes_dir, recipe))
62
- generate_metadata(name)
63
- generate_policyfile(name, recipe_name)
62
+ generate_metadata(cb_name)
64
63
  end
64
+ generate_policyfile(cb_name, recipe_name)
65
65
  end
66
66
 
67
67
  def from_resource(resource_type, resource_name, properties)
68
68
  # Generate a cookbook containing a single default recipe with the specified
69
69
  # resource in it. Incloud the resource type in the cookbook name so hopefully
70
70
  # this gives us better reporting info in the future.
71
+ @descriptor = "#{resource_type}[#{resource_name}]"
72
+ @from = "resource"
73
+
71
74
  ChefApply::Log.debug("Generating cookbook for single resource '#{resource_type}[#{resource_name}]'")
72
75
  name = "cw_#{resource_type}"
73
76
  recipe_name = "default"
74
77
  recipes_dir = generate_recipes_dir
75
78
  File.open(File.join(recipes_dir, "#{recipe_name}.rb"), "w+") do |f|
76
- f.print(create_resource(resource_type, resource_name, properties))
79
+ f.print(create_resource_definition(resource_type, resource_name, properties))
77
80
  end
78
81
  generate_metadata(name)
79
82
  generate_policyfile(name, recipe_name)
@@ -137,7 +140,7 @@ module ChefApply
137
140
  policy_file
138
141
  end
139
142
 
140
- def create_resource(resource_type, resource_name, properties)
143
+ def create_resource_definition(resource_type, resource_name, properties)
141
144
  r = "#{resource_type} '#{resource_name}'"
142
145
  # lets format the properties into the correct syntax Chef expects
143
146
  unless properties.empty?
@@ -152,6 +155,14 @@ module ChefApply
152
155
  r
153
156
  end
154
157
 
158
+ def policyfile_lock_path
159
+ File.join(path, "Policyfile.lock.json")
160
+ end
161
+
162
+ def export_path
163
+ File.join(path, "export")
164
+ end
165
+
155
166
  class UnsupportedExtension < ChefApply::ErrorNoLogs
156
167
  def initialize(ext); super("CHEFVAL009", ext); end
157
168
  end
@@ -16,62 +16,30 @@
16
16
  #
17
17
 
18
18
  require "r18n-desktop"
19
+ require "chef_apply/text/text_wrapper"
19
20
 
20
21
  # A very thin wrapper around R18n, the idea being that we're likely to replace r18n
21
22
  # down the road and don't want to have to change all of our commands.
22
23
  module ChefApply
23
- class Text
24
- R18n.from_env(File.join(File.dirname(__FILE__), "../..", "i18n/"))
25
- t = R18n.get.t
26
- t.translation_keys.each do |k|
27
- k = k.to_sym
28
- define_singleton_method k do |*args|
29
- TextWrapper.new(t.send(k, *args))
30
- end
24
+ module Text
25
+ def self._translation_path
26
+ @translation_path ||= File.join(File.dirname(__FILE__), "..", "..", "i18n")
31
27
  end
32
- end
33
28
 
34
- # Our text spinner class really doesn't like handling the TranslatedString or Untranslated classes returned
35
- # by the R18n library. So instead we return these TextWrapper instances which have dynamically defined methods
36
- # corresponding to the known structure of the R18n text file. Most importantly, if a user has accessed
37
- # a leaf node in the code we return a regular String instead of the R18n classes.
38
- class TextWrapper
39
- def initialize(translation_tree)
40
- @tree = translation_tree
41
- @tree.translation_keys.each do |k|
29
+ def self.load
30
+ R18n.from_env(Text._translation_path)
31
+ R18n.extension_places << File.join(Text._translation_path, "errors")
32
+ t = R18n.get.t
33
+ t.translation_keys.each do |k|
42
34
  k = k.to_sym
43
35
  define_singleton_method k do |*args|
44
- subtree = @tree.send(k, *args)
45
- if subtree.translation_keys.empty?
46
- # If there are no more possible children, just return the translated value
47
- subtree.to_s
48
- else
49
- TextWrapper.new(subtree)
50
- end
51
- end
52
- end
53
- end
54
-
55
- def method_missing(name, *args)
56
- raise InvalidKey.new(@tree.instance_variable_get(:@path), name)
57
- end
58
-
59
- class InvalidKey < RuntimeError
60
- def initialize(path, terminus)
61
- line = caller(3, 1).first # 1 - TextWrapper.method_missing
62
- # 2 - TextWrapper.initialize
63
- # 3 - actual caller
64
- if line =~ /.*\/lib\/(.*\.rb):(\d+)/
65
- line = "File: #{$1} Line: #{$2}"
36
+ TextWrapper.new(t.send(k, *args))
66
37
  end
67
-
68
- # Calling back into Text here seems icky, this is an error
69
- # that only engineering should see.
70
- message = "i18n key #{path}.#{terminus} does not exist.\n"
71
- message << "Referenced from #{line}"
72
- super(message)
73
38
  end
74
39
  end
75
40
 
41
+ # We need to do this when the class is loaded to avoid breaking a bunch of behaviors for now.
42
+ load
76
43
  end
44
+
77
45
  end
@@ -0,0 +1,86 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2017 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
+ module ChefApply
19
+ module Text
20
+ # Our text spinner class really doesn't like handling the TranslatedString or Untranslated classes returned
21
+ # by the R18n library. So instead we return these TextWrapper instances which have dynamically defined methods
22
+ # corresponding to the known structure of the R18n text file. Most importantly, if a user has accessed
23
+ # a leaf node in the code we return a regular String instead of the R18n classes.
24
+ class TextWrapper
25
+ def initialize(translation_tree)
26
+ @tree = translation_tree
27
+ @tree.translation_keys.each do |k|
28
+ # Integer keys are not translatable - they're quantity indicators in the key that
29
+ # are instead sent as arguments. If we see one here, it means it was not correctly
30
+ # labeled as plural with !!pl in the parent key
31
+ if k.class == Integer
32
+ raise MissingPlural.new(@tree.instance_variable_get(:@path), k)
33
+ end
34
+ k = k.to_sym
35
+ define_singleton_method k do |*args|
36
+ subtree = @tree.send(k, *args)
37
+ if subtree.translation_keys.empty?
38
+ # If there are no more possible children, just return the translated value
39
+ subtree.to_s
40
+ else
41
+ TextWrapper.new(subtree)
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ def method_missing(name, *args)
48
+ raise InvalidKey.new(@tree.instance_variable_get(:@path), name)
49
+ end
50
+
51
+ # TODO - make the checks for these conditions lint steps that run during build
52
+ # instead of part of the shipped product.
53
+ class TextError < RuntimeError
54
+ attr_accessor :line
55
+ def set_call_context
56
+ # TODO - this can vary (8 isn't always right) - inspect
57
+ @line = caller(8, 1).first
58
+ if @line =~ /.*\/lib\/(.*\.rb):(\d+)/
59
+ @line = "File: #{$1} Line: #{$2}"
60
+ end
61
+ end
62
+ end
63
+
64
+ class InvalidKey < TextError
65
+ def initialize(path, terminus)
66
+ set_call_context
67
+ # Calling back into Text here seems icky, this is an error
68
+ # that only engineering should see.
69
+ message = "i18n key #{path}.#{terminus} does not exist.\n"
70
+ message << "Referenced from #{line}"
71
+ super(message)
72
+ end
73
+ end
74
+
75
+ class MissingPlural < TextError
76
+ def initialize(path, terminus)
77
+ set_call_context
78
+ message = "i18n key #{path}.#{terminus} appears to reference a pluralization.\n"
79
+ message << "Please append the plural indicator '!!pl' to the end of #{path}.\n"
80
+ message << "Referenced from #{line}"
81
+ super(message)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -21,6 +21,7 @@ require "chef_apply/error"
21
21
  require "chef_apply/config"
22
22
  require "chef_apply/text"
23
23
  require "chef_apply/ui/terminal"
24
+ require "chef_apply/errors/standard_error_resolver"
24
25
 
25
26
  module ChefApply::UI
26
27
  class ErrorPrinter
@@ -36,7 +37,7 @@ module ChefApply::UI
36
37
  def self.show_error(e)
37
38
  # Name is misleading - it's unwrapping but also doing further
38
39
  # error resolution for common errors:
39
- unwrapped = ChefApply::StandardErrorResolver.unwrap_exception(e)
40
+ unwrapped = ChefApply::Errors::StandardErrorResolver.unwrap_exception(e)
40
41
  if unwrapped.class == ChefApply::MultiJobFailure
41
42
  capture_multiple_failures(unwrapped)
42
43
  end
@@ -51,7 +52,7 @@ module ChefApply::UI
51
52
  e.params << out_file # Tell the operator where to find this info
52
53
  File.open(out_file, "w") do |out|
53
54
  e.jobs.each do |j|
54
- wrapped = ChefApply::StandardErrorResolver.wrap_exception(j.exception, j.target_host)
55
+ wrapped = ChefApply::Errors::StandardErrorResolver.wrap_exception(j.exception, j.target_host)
55
56
  ep = ErrorPrinter.new(wrapped)
56
57
  msg = ep.format_body().tr("\n", " ").gsub(/ {2,}/, " ").chomp.strip
57
58
  out.write("Host: #{j.target_host.hostname} ")
@@ -47,11 +47,6 @@ module ChefApply
47
47
 
48
48
  def init(location)
49
49
  @location = location
50
- # In Ruby 2.5+ threads print out to stdout when they raise an exception. This is an agressive
51
- # attempt to ensure debugging information is not lost, but in our case it is not necessary
52
- # because we handle all the errors ourself. So we disable this to keep output clean.
53
- # See https://ruby-doc.org/core-2.5.0/Thread.html#method-c-report_on_exception
54
- Thread.report_on_exception = false
55
50
  end
56
51
 
57
52
  def write(msg)
@@ -63,9 +58,16 @@ module ChefApply
63
58
  end
64
59
 
65
60
  def render_parallel_jobs(header, jobs)
66
- multispinner = TTY::Spinner::Multi.new("[:spinner] #{header}", output: @location)
61
+ # Do not indent the topmost 'parent' spinner, but do indent child spinners
62
+ indent_style = { top: "",
63
+ middle: TTY::Spinner::Multi::DEFAULT_INSET[:middle],
64
+ bottom: TTY::Spinner::Multi::DEFAULT_INSET[:bottom] }
65
+ # @option options [Hash] :style
66
+ # keys :top :middle and :bottom can contain Strings that are used to
67
+ # indent the spinners. Ignored if message is blank
68
+ multispinner = TTY::Spinner::Multi.new("[:spinner] #{header}", output: @location, hide_cursor: true, style: indent_style)
67
69
  jobs.each do |a|
68
- multispinner.register(spinner_prefix(a.prefix)) do |spinner|
70
+ multispinner.register(spinner_prefix(a.prefix), hide_cursor: true) do |spinner|
69
71
  reporter = StatusReporter.new(spinner, prefix: a.prefix, key: :status)
70
72
  a.run(reporter)
71
73
  end
@@ -77,7 +79,7 @@ module ChefApply
77
79
  # between render_job and render_parallel
78
80
  def render_job(msg, prefix: "", &block)
79
81
  klass = ChefApply::UI.const_get(ChefApply::Config.dev.spinner)
80
- spinner = klass.new(spinner_prefix(prefix), output: @location)
82
+ spinner = klass.new(spinner_prefix(prefix), output: @location, hide_cursor: true)
81
83
  reporter = StatusReporter.new(spinner, prefix: prefix, key: :status)
82
84
  reporter.update(msg)
83
85
  spinner.run { yield(reporter) }
@@ -16,5 +16,5 @@
16
16
  #
17
17
 
18
18
  module ChefApply
19
- VERSION = "0.1.2"
19
+ VERSION = "0.1.15"
20
20
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chef-apply
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chef Software, Inc
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-11 00:00:00.000000000 Z
11
+ date: 2018-07-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mixlib-cli
@@ -320,19 +320,25 @@ files:
320
320
  - bin/chef-run
321
321
  - chef-apply.gemspec
322
322
  - i18n/en.yml
323
+ - i18n/errors/en.yml
323
324
  - lib/chef_apply.rb
324
325
  - lib/chef_apply/action/base.rb
325
326
  - lib/chef_apply/action/converge_target.rb
327
+ - lib/chef_apply/action/generate_local_policy.rb
328
+ - lib/chef_apply/action/generate_temp_cookbook.rb
326
329
  - lib/chef_apply/action/install_chef.rb
327
330
  - lib/chef_apply/action/install_chef/base.rb
328
331
  - lib/chef_apply/action/install_chef/linux.rb
329
332
  - lib/chef_apply/action/install_chef/windows.rb
330
333
  - lib/chef_apply/action/reporter.rb
331
334
  - lib/chef_apply/cli.rb
332
- - lib/chef_apply/cli_options.rb
335
+ - lib/chef_apply/cli/help.rb
336
+ - lib/chef_apply/cli/options.rb
337
+ - lib/chef_apply/cli/validation.rb
333
338
  - lib/chef_apply/config.rb
334
339
  - lib/chef_apply/error.rb
335
340
  - lib/chef_apply/errors/ccr_failure_mapper.rb
341
+ - lib/chef_apply/errors/standard_error_resolver.rb
336
342
  - lib/chef_apply/file_fetcher.rb
337
343
  - lib/chef_apply/log.rb
338
344
  - lib/chef_apply/recipe_lookup.rb
@@ -345,6 +351,7 @@ files:
345
351
  - lib/chef_apply/telemeter/sender.rb
346
352
  - lib/chef_apply/temp_cookbook.rb
347
353
  - lib/chef_apply/text.rb
354
+ - lib/chef_apply/text/text_wrapper.rb
348
355
  - lib/chef_apply/ui/error_printer.rb
349
356
  - lib/chef_apply/ui/plain_text_element.rb
350
357
  - lib/chef_apply/ui/terminal.rb
@@ -1,145 +0,0 @@
1
- #
2
- # Copyright:: Copyright (c) 2018 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_apply/text"
19
- require "chef_apply/action/install_chef"
20
-
21
- # Moving the options into here so the cli.rb file is smaller and easier to read
22
- # For options that need to be merged back into the global ChefApply::Config object
23
- # we do that with a proc in the option itself. We decided to do that because it is
24
- # an easy, straight forward way to merge those options when they do not directly
25
- # map back to keys in the Config global. IE, we cannot just do
26
- # `ChefApply::Config.merge!(options)` because the keys do not line up, and we do
27
- # not want all CLI params merged back into the global config object.
28
- # We know that the config is already loaded from the file (or program defaults)
29
- # because the `Startup` class was invoked to start the program.
30
- module ChefApply
31
- module CLIOptions
32
-
33
- T = ChefApply::Text.cli
34
- TS = ChefApply::Text.status
35
-
36
- def self.included(klass)
37
- klass.banner T.description + "\n" + T.usage_full
38
-
39
- klass.option :version,
40
- short: "-v",
41
- long: "--version",
42
- description: T.version.description,
43
- boolean: true
44
-
45
- klass.option :help,
46
- short: "-h",
47
- long: "--help",
48
- description: T.help.description,
49
- boolean: true
50
-
51
- # Special note:
52
- # config_path is pre-processed in startup.rb, and is shown here only
53
- # for purpoess of rendering help text.
54
- klass.option :config_path,
55
- short: "-c PATH",
56
- long: "--config PATH",
57
- description: T.default_config_location(ChefApply::Config.default_location),
58
- default: ChefApply::Config.default_location,
59
- proc: Proc.new { |path| ChefApply::Config.custom_location(path) }
60
-
61
- klass.option :identity_file,
62
- long: "--identity-file PATH",
63
- short: "-i PATH",
64
- description: T.identity_file,
65
- proc: (Proc.new do |paths|
66
- path = paths
67
- unless File.exist?(path)
68
- raise OptionValidationError.new("CHEFVAL001", self, path)
69
- end
70
- path
71
- end)
72
-
73
- klass.option :ssl,
74
- long: "--[no-]ssl",
75
- description: T.ssl.desc(ChefApply::Config.connection.winrm.ssl),
76
- boolean: true,
77
- default: ChefApply::Config.connection.winrm.ssl,
78
- proc: Proc.new { |val| ChefApply::Config.connection.winrm.ssl(val) }
79
-
80
- klass.option :ssl_verify,
81
- long: "--[no-]ssl-verify",
82
- description: T.ssl.verify_desc(ChefApply::Config.connection.winrm.ssl_verify),
83
- boolean: true,
84
- default: ChefApply::Config.connection.winrm.ssl_verify,
85
- proc: Proc.new { |val| ChefApply::Config.connection.winrm.ssl_verify(val) }
86
-
87
- klass.option :protocol,
88
- long: "--protocol <PROTOCOL>",
89
- short: "-p",
90
- description: T.protocol_description(ChefApply::Config::SUPPORTED_PROTOCOLS.join(" "),
91
- ChefApply::Config.connection.default_protocol),
92
- default: ChefApply::Config.connection.default_protocol,
93
- proc: Proc.new { |val| ChefApply::Config.connection.default_protocol(val) }
94
-
95
- klass.option :user,
96
- long: "--user <USER>",
97
- description: T.user_description
98
-
99
- klass.option :password,
100
- long: "--password <PASSWORD>",
101
- description: T.password_description
102
-
103
- klass.option :cookbook_repo_paths,
104
- long: "--cookbook-repo-paths PATH",
105
- description: T.cookbook_repo_paths,
106
- default: ChefApply::Config.chef.cookbook_repo_paths,
107
- proc: (Proc.new do |paths|
108
- paths = paths.split(",")
109
- ChefApply::Config.chef.cookbook_repo_paths(paths)
110
- paths
111
- end)
112
-
113
- klass.option :install,
114
- long: "--[no-]install",
115
- default: true,
116
- boolean: true,
117
- description: T.install_description(Action::InstallChef::Base::MIN_CHEF_VERSION)
118
-
119
- klass.option :sudo,
120
- long: "--[no-]sudo",
121
- description: T.sudo.flag_description.sudo,
122
- boolean: true,
123
- default: true
124
-
125
- klass.option :sudo_command,
126
- long: "--sudo-command <COMMAND>",
127
- default: "sudo",
128
- description: T.sudo.flag_description.command
129
-
130
- klass.option :sudo_password,
131
- long: "--sudo-password <PASSWORD>",
132
- description: T.sudo.flag_description.password
133
-
134
- klass.option :sudo_options,
135
- long: "--sudo-options 'OPTIONS...'",
136
- description: T.sudo.flag_description.options
137
- end
138
-
139
- # I really don't like that mixlib-cli refers to the parsed command line flags in
140
- # a hash accesed via the `config` method. Thats just such an overloaded word.
141
- def parsed_options
142
- config
143
- end
144
- end
145
- end