chef-apply 0.1.2

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.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +26 -0
  3. data/Gemfile.lock +423 -0
  4. data/LICENSE +201 -0
  5. data/README.md +41 -0
  6. data/Rakefile +32 -0
  7. data/bin/chef-run +23 -0
  8. data/chef-apply.gemspec +67 -0
  9. data/i18n/en.yml +513 -0
  10. data/lib/chef_apply.rb +20 -0
  11. data/lib/chef_apply/action/base.rb +158 -0
  12. data/lib/chef_apply/action/converge_target.rb +173 -0
  13. data/lib/chef_apply/action/install_chef.rb +30 -0
  14. data/lib/chef_apply/action/install_chef/base.rb +137 -0
  15. data/lib/chef_apply/action/install_chef/linux.rb +38 -0
  16. data/lib/chef_apply/action/install_chef/windows.rb +54 -0
  17. data/lib/chef_apply/action/reporter.rb +39 -0
  18. data/lib/chef_apply/cli.rb +470 -0
  19. data/lib/chef_apply/cli_options.rb +145 -0
  20. data/lib/chef_apply/config.rb +150 -0
  21. data/lib/chef_apply/error.rb +108 -0
  22. data/lib/chef_apply/errors/ccr_failure_mapper.rb +93 -0
  23. data/lib/chef_apply/file_fetcher.rb +70 -0
  24. data/lib/chef_apply/log.rb +42 -0
  25. data/lib/chef_apply/recipe_lookup.rb +117 -0
  26. data/lib/chef_apply/startup.rb +162 -0
  27. data/lib/chef_apply/status_reporter.rb +42 -0
  28. data/lib/chef_apply/target_host.rb +233 -0
  29. data/lib/chef_apply/target_resolver.rb +202 -0
  30. data/lib/chef_apply/telemeter.rb +162 -0
  31. data/lib/chef_apply/telemeter/patch.rb +32 -0
  32. data/lib/chef_apply/telemeter/sender.rb +121 -0
  33. data/lib/chef_apply/temp_cookbook.rb +159 -0
  34. data/lib/chef_apply/text.rb +77 -0
  35. data/lib/chef_apply/ui/error_printer.rb +261 -0
  36. data/lib/chef_apply/ui/plain_text_element.rb +75 -0
  37. data/lib/chef_apply/ui/terminal.rb +94 -0
  38. data/lib/chef_apply/version.rb +20 -0
  39. metadata +376 -0
@@ -0,0 +1,145 @@
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
@@ -0,0 +1,150 @@
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/log"
19
+ require "mixlib/config"
20
+ require "fileutils"
21
+ require "pathname"
22
+ require "chef-config/config"
23
+ require "chef-config/workstation_config_loader"
24
+
25
+ module ChefApply
26
+ class Config
27
+ WS_BASE_PATH = File.join(Dir.home, ".chef-workstation/")
28
+ SUPPORTED_PROTOCOLS = %w{ssh winrm}
29
+
30
+ class << self
31
+ @custom_location = nil
32
+
33
+ # Ensure when we extend Mixlib::Config that we load
34
+ # up the workstation config since we will need that
35
+ # to converge later
36
+ def initialize_mixlib_config
37
+ super
38
+ end
39
+
40
+ def custom_location(path)
41
+ @custom_location = path
42
+ raise "No config file located at #{path}" unless exist?
43
+ end
44
+
45
+ def default_location
46
+ File.join(WS_BASE_PATH, "config.toml")
47
+ end
48
+
49
+ def telemetry_path
50
+ File.join(WS_BASE_PATH, "telemetry")
51
+ end
52
+
53
+ def telemetry_session_file
54
+ File.join(telemetry_path, "TELEMETRY_SESSION_ID")
55
+ end
56
+
57
+ def telemetry_installation_identifier_file
58
+ File.join(WS_BASE_PATH, "installation_id")
59
+ end
60
+
61
+ def base_log_directory
62
+ File.dirname(log.location)
63
+ end
64
+
65
+ # These paths are relative to the log output path, which is user-configurable.
66
+ def error_output_path
67
+ File.join(base_log_directory, "errors.txt")
68
+ end
69
+
70
+ def stack_trace_path
71
+ File.join(base_log_directory, "stack-trace.log")
72
+ end
73
+
74
+ def using_default_location?
75
+ @custom_location.nil?
76
+ end
77
+
78
+ def location
79
+ using_default_location? ? default_location : @custom_location
80
+ end
81
+
82
+ def load
83
+ if exist?
84
+ from_file(location)
85
+ end
86
+ end
87
+
88
+ def exist?
89
+ File.exist? location
90
+ end
91
+
92
+ def reset
93
+ @custom_location = nil
94
+ super
95
+ end
96
+ end
97
+
98
+ extend Mixlib::Config
99
+
100
+ config_strict_mode true
101
+
102
+ # When working on Chef Apply itself,
103
+ # developers should set telemetry.dev to true
104
+ # in their local configuration to ensure that dev usage
105
+ # doesn't skew customer telemetry.
106
+ config_context :telemetry do
107
+ default(:dev, false)
108
+ default(:enable, true)
109
+ end
110
+
111
+ config_context :log do
112
+ default(:level, "warn")
113
+ default(:location, File.join(WS_BASE_PATH, "logs/default.log"))
114
+ end
115
+
116
+ config_context :cache do
117
+ default(:path, File.join(WS_BASE_PATH, "cache"))
118
+ end
119
+
120
+ config_context :connection do
121
+ default(:default_protocol, "ssh")
122
+ default(:default_user, nil)
123
+
124
+ config_context :winrm do
125
+ default(:ssl, false)
126
+ default(:ssl_verify, true)
127
+ end
128
+ end
129
+
130
+ config_context :dev do
131
+ default(:spinner, "TTY::Spinner")
132
+ end
133
+
134
+ config_context :chef do
135
+ # We want to use any configured chef repo paths or trusted certs in
136
+ # ~/.chef/knife.rb on the user's workstation. But because they could have
137
+ # config that could mess up our Policyfile creation later we reset the
138
+ # ChefConfig back to default after loading that.
139
+ ChefConfig::WorkstationConfigLoader.new(nil, ChefApply::Log).load
140
+ default(:cookbook_repo_paths, [ChefConfig::Config[:cookbook_path]].flatten)
141
+ default(:trusted_certs_dir, ChefConfig::Config[:trusted_certs_dir])
142
+ ChefConfig::Config.reset
143
+ end
144
+
145
+ config_context :data_collector do
146
+ default :url, nil
147
+ default :token, nil
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,108 @@
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
+ class Error < StandardError
20
+ attr_reader :id, :params
21
+ attr_accessor :show_stack, :show_log, :decorate
22
+ def initialize(id, *params)
23
+ @id = id
24
+ @params = params || []
25
+ @show_log = true
26
+ @show_stack = true
27
+ @decorate = true
28
+ end
29
+ end
30
+
31
+ class ErrorNoLogs < Error
32
+ def initialize(id, *params)
33
+ super
34
+ @show_log = false
35
+ @show_stack = false
36
+ end
37
+ end
38
+
39
+ class ErrorNoStack < Error
40
+ def initialize(id, *params)
41
+ super
42
+ @show_log = true
43
+ @show_stack = false
44
+ end
45
+ end
46
+
47
+ class WrappedError < StandardError
48
+ attr_accessor :target_host, :contained_exception
49
+ def initialize(e, target_host)
50
+ super(e.message)
51
+ @contained_exception = e
52
+ @target_host = target_host
53
+ end
54
+ end
55
+
56
+ class MultiJobFailure < ChefApply::ErrorNoLogs
57
+ attr_reader :jobs
58
+ def initialize(jobs)
59
+ super("CHEFMULTI001")
60
+ @jobs = jobs
61
+ @decorate = false
62
+ end
63
+ end
64
+
65
+ # Provides mappings of common errors that we don't explicitly
66
+ # handle, but can offer expanded help text around.
67
+ class StandardErrorResolver
68
+
69
+ def self.resolve_exception(exception)
70
+ deps
71
+ show_log = true
72
+ show_stack = true
73
+ case exception
74
+ when OpenSSL::SSL::SSLError
75
+ if exception.message =~ /SSL.*verify failed.*/
76
+ id = "CHEFNET002"
77
+ show_log = false
78
+ show_stack = false
79
+ end
80
+ when SocketError then id = "CHEFNET001"; show_log = false; show_stack = false
81
+ end
82
+ if id.nil?
83
+ exception
84
+ else
85
+ e = ChefApply::Error.new(id, exception.message)
86
+ e.show_log = show_log
87
+ e.show_stack = show_stack
88
+ e
89
+ end
90
+ end
91
+
92
+ def self.wrap_exception(original, target_host = nil)
93
+ resolved_exception = resolve_exception(original)
94
+ WrappedError.new(resolved_exception, target_host)
95
+ end
96
+
97
+ def self.unwrap_exception(wrapper)
98
+ resolve_exception(wrapper.contained_exception)
99
+ end
100
+
101
+ def self.deps
102
+ # Avoid loading additional includes until they're needed
103
+ require "socket"
104
+ require "openssl"
105
+ end
106
+ end
107
+
108
+ end
@@ -0,0 +1,93 @@
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
+ require "chef_apply/error"
19
+
20
+ module ChefApply::Errors
21
+ class CCRFailureMapper
22
+ attr_reader :params
23
+
24
+ def initialize(exception, params)
25
+ @params = params
26
+ @cause_line = exception
27
+ end
28
+
29
+ def raise_mapped_exception!
30
+ if @cause_line.nil?
31
+ raise RemoteChefRunFailedToResolveError.new(params[:stdout], params[:stderr])
32
+ else
33
+ errid, *args = exception_args_from_cause()
34
+ if errid.nil?
35
+ raise RemoteChefClientRunFailedUnknownReason.new()
36
+ else
37
+ raise RemoteChefClientRunFailed.new(errid, *args)
38
+ end
39
+
40
+ end
41
+ end
42
+
43
+ # Ideally we will write a custom handler to package up data we care
44
+ # about and present it more directly https://docs.chef.io/handlers.html
45
+ # For now, we'll just match the most common failures based on their
46
+ # messages.
47
+ def exception_args_from_cause
48
+ # Ordering is important below. Some earlier tests are more detailed
49
+ # cases of things that will match more general tests further down.
50
+ case @cause_line
51
+ when /.*had an error:(.*:)\s+(.*$)/
52
+ # Some invalid property value cases, among others.
53
+ ["CHEFCCR002", $2]
54
+ when /.*Chef::Exceptions::ValidationFailed:\s+Option action must be equal to one of:\s+(.*)!\s+You passed :(.*)\./
55
+ # Invalid action - specialization of invalid property value, below
56
+ ["CHEFCCR003", $2, $1]
57
+ when /.*Chef::Exceptions::ValidationFailed:\s+(.*)/
58
+ # Invalid resource property value
59
+ ["CHEFCCR004", $1]
60
+ when /.*NameError: undefined local variable or method `(.+)' for cookbook.+/
61
+ # Invalid resource type in most cases
62
+ ["CHEFCCR005", $1]
63
+ when /.*NoMethodError: undefined method `(.+)' for cookbook.+/
64
+ # Invalid resource type in most cases
65
+ ["CHEFCCR005", $1]
66
+ when /.*undefined method `(.*)' for (.+)/
67
+ # Unknown resource property
68
+ ["CHEFCCR006", $1, $2]
69
+
70
+ # Below would catch the general form of most errors, but the
71
+ # message itself in those lines is not generally aligned
72
+ # with the UX we want to provide.
73
+ # when /.*Exception|Error.*:\s+(.*)/
74
+ else
75
+ nil
76
+ end
77
+ end
78
+
79
+ class RemoteChefClientRunFailed < ChefApply::ErrorNoLogs
80
+ def initialize(id, *args); super(id, *args); end
81
+ end
82
+
83
+ class RemoteChefClientRunFailedUnknownReason < ChefApply::ErrorNoStack
84
+ def initialize(); super("CHEFCCR099"); end
85
+ end
86
+
87
+ class RemoteChefRunFailedToResolveError < ChefApply::ErrorNoStack
88
+ def initialize(stdout, stderr); super("CHEFCCR001", stdout, stderr); end
89
+ end
90
+
91
+ end
92
+
93
+ end