chef-apply 0.1.2 → 0.1.15

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.
@@ -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