chef-apply 0.1.2

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