chef-apply 0.1.17 → 0.1.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +0 -7
  3. data/Gemfile.lock +176 -84
  4. data/chef-apply.gemspec +2 -2
  5. data/lib/chef_apply/version.rb +1 -1
  6. data/spec/fixtures/custom_config.toml +2 -0
  7. data/spec/integration/chef-run_spec.rb +41 -0
  8. data/spec/integration/fixtures/chef_help.out +69 -0
  9. data/spec/integration/fixtures/chef_version.out +1 -0
  10. data/spec/integration/spec_helper.rb +55 -0
  11. data/spec/spec_helper.rb +114 -0
  12. data/spec/support/matchers/output_to_terminal.rb +36 -0
  13. data/spec/unit/action/base_spec.rb +89 -0
  14. data/spec/unit/action/converge_target_spec.rb +292 -0
  15. data/spec/unit/action/generate_local_policy_spec.rb +114 -0
  16. data/spec/unit/action/generate_temp_cookbook_spec.rb +75 -0
  17. data/spec/unit/action/install_chef/base_spec.rb +234 -0
  18. data/spec/unit/action/install_chef_spec.rb +69 -0
  19. data/spec/unit/cli/options_spec.rb +75 -0
  20. data/spec/unit/cli/validation_spec.rb +78 -0
  21. data/spec/unit/cli_spec.rb +440 -0
  22. data/spec/unit/config_spec.rb +70 -0
  23. data/spec/unit/errors/ccr_failure_mapper_spec.rb +103 -0
  24. data/spec/unit/file_fetcher_spec.rb +40 -0
  25. data/spec/unit/fixtures/multi-error.out +2 -0
  26. data/spec/unit/log_spec.rb +37 -0
  27. data/spec/unit/recipe_lookup_spec.rb +122 -0
  28. data/spec/unit/startup_spec.rb +283 -0
  29. data/spec/unit/target_host_spec.rb +231 -0
  30. data/spec/unit/target_resolver_spec.rb +380 -0
  31. data/spec/unit/telemeter/sender_spec.rb +140 -0
  32. data/spec/unit/telemeter_spec.rb +191 -0
  33. data/spec/unit/temp_cookbook_spec.rb +199 -0
  34. data/spec/unit/ui/error_printer_spec.rb +173 -0
  35. data/spec/unit/ui/terminal_spec.rb +109 -0
  36. data/spec/unit/version_spec.rb +31 -0
  37. data/warning.txt +3 -0
  38. metadata +34 -2
@@ -0,0 +1,140 @@
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 "spec_helper"
19
+ require "chef_apply/telemeter/sender"
20
+ require "chef_apply/config"
21
+
22
+ RSpec.describe ChefApply::Telemeter::Sender do
23
+ let(:session_files) { %w{file1 file2} }
24
+ let(:enabled_flag) { true }
25
+ let(:dev_mode) { false }
26
+ let(:config) { double("config") }
27
+
28
+ let(:subject) { ChefApply::Telemeter::Sender.new(session_files) }
29
+
30
+ before do
31
+ allow(config).to receive(:dev).and_return dev_mode
32
+ allow(ChefApply::Config).to receive(:telemetry).and_return config
33
+ allow(ChefApply::Telemeter).to receive(:enabled?).and_return enabled_flag
34
+ # Ensure this is not set for each test:
35
+ ENV.delete("CHEF_TELEMETRY_ENDPOINT")
36
+ end
37
+
38
+ describe "::start_upload_thread" do
39
+ let(:sender_mock) { instance_double(ChefApply::Telemeter::Sender) }
40
+ it "spawns a thread to run the send" do
41
+ expect(ChefApply::Telemeter::Sender).to receive(:find_session_files).and_return session_files
42
+ expect(ChefApply::Telemeter::Sender).to receive(:new).with(session_files).and_return sender_mock
43
+ expect(sender_mock).to receive(:run)
44
+ expect(::Thread).to receive(:new).and_yield
45
+ ChefApply::Telemeter::Sender.start_upload_thread
46
+ end
47
+ end
48
+
49
+ describe "#run" do
50
+ before do
51
+ expect(subject).to receive(:session_files).and_return session_files
52
+ end
53
+
54
+ context "when telemetry is disabled" do
55
+ let(:enabled_flag) { false }
56
+ it "deletes session files without sending" do
57
+ expect(FileUtils).to receive(:rm_rf).with("file1")
58
+ expect(FileUtils).to receive(:rm_rf).with("file2")
59
+ expect(FileUtils).to receive(:rm_rf).with(ChefApply::Config.telemetry_session_file)
60
+ expect(subject).to_not receive(:process_session)
61
+ subject.run
62
+ end
63
+ end
64
+
65
+ context "when telemetry is enabled" do
66
+ context "and telemetry dev mode is true" do
67
+ let(:dev_mode) { true }
68
+ let(:session_files) { [] } # Ensure we don't send anything without mocking :allthecalls:
69
+ context "and a custom telemetry endpoint is not set" do
70
+ it "configures the environment to submit to the Acceptance telemetry endpoint" do
71
+ subject.run
72
+ expect(ENV["CHEF_TELEMETRY_ENDPOINT"]).to eq "https://telemetry-acceptance.chef.io"
73
+ end
74
+ end
75
+
76
+ context "and a custom telemetry endpoint is already set" do
77
+ before do
78
+ ENV["CHEF_TELEMETRY_ENDPOINT"] = "https://localhost"
79
+ end
80
+ it "should not overwrite the custom value" do
81
+ subject.run
82
+ expect(ENV["CHEF_TELEMETRY_ENDPOINT"]).to eq "https://localhost"
83
+ end
84
+ end
85
+ end
86
+
87
+ it "submits the session capture for each session file found" do
88
+ expect(subject).to receive(:process_session).with("file1")
89
+ expect(subject).to receive(:process_session).with("file2")
90
+ expect(FileUtils).to receive(:rm_rf).with(ChefApply::Config.telemetry_session_file)
91
+ subject.run
92
+ end
93
+ end
94
+
95
+ context "when an error occurrs" do
96
+ it "logs it" do
97
+ allow(config).to receive(:enabled?).and_raise("Failed")
98
+ expect(ChefApply::Log).to receive(:fatal)
99
+ subject.run
100
+ end
101
+ end
102
+ end
103
+
104
+ describe "::find_session_files" do
105
+ it "finds all telemetry-payload-*.yml files in the telemetry directory" do
106
+ expect(ChefApply::Config).to receive(:telemetry_path).and_return("/tmp")
107
+ expect(Dir).to receive(:glob).with("/tmp/telemetry-payload-*.yml").and_return []
108
+ ChefApply::Telemeter::Sender.find_session_files
109
+ end
110
+ end
111
+
112
+ describe "process_session" do
113
+ it "loads the sesion and submits it" do
114
+ expect(subject).to receive(:load_and_clear_session).with("file1").and_return({ "version" => "1.0.0", "entries" => [] })
115
+ expect(subject).to receive(:submit_session).with({ "version" => "1.0.0", "entries" => [] })
116
+ subject.process_session("file1")
117
+ end
118
+ end
119
+
120
+ describe "submit_session" do
121
+ let(:telemetry) { instance_double("telemetry") }
122
+ it "removes the telemetry session file and starts a new session, then submits each entry in the session" do
123
+ expect(ChefApply::Config).to receive(:telemetry_session_file).and_return("/tmp/SESSION_ID")
124
+ expect(FileUtils).to receive(:rm_rf).with("/tmp/SESSION_ID")
125
+ expect(Telemetry).to receive(:new).and_return telemetry
126
+ expect(subject).to receive(:submit_entry).with(telemetry, { "event" => "action1" }, 1, 2)
127
+ expect(subject).to receive(:submit_entry).with(telemetry, { "event" => "action2" }, 2, 2)
128
+ subject.submit_session( { "version" => "1.0.0",
129
+ "entries" => [ { "event" => "action1" }, { "event" => "action2" } ] } )
130
+ end
131
+ end
132
+
133
+ describe "submit_entry" do
134
+ let(:telemetry) { instance_double("telemetry") }
135
+ it "submits the entry to telemetry" do
136
+ expect(telemetry).to receive(:deliver).with("test" => "this")
137
+ subject.submit_entry(telemetry, { "test" => "this" }, 1, 1)
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,191 @@
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 "spec_helper"
19
+ require "chef_apply/telemeter"
20
+
21
+ RSpec.describe ChefApply::Telemeter do
22
+ subject { ChefApply::Telemeter.instance }
23
+ let(:host_platform) { "linux" }
24
+
25
+ before do
26
+ allow(subject).to receive(:host_platform).and_return host_platform
27
+ end
28
+
29
+ context "#commit" do
30
+ context "when telemetry is enabled" do
31
+ before do
32
+ allow(subject).to receive(:enabled?).and_return true
33
+ end
34
+
35
+ it "writes events out and clears the queue" do
36
+ subject.capture(:test)
37
+ expect(subject.pending_event_count).to eq 1
38
+ expect(subject).to receive(:convert_events_to_session)
39
+ expect(subject).to receive(:write_session)
40
+
41
+ subject.commit
42
+ expect(subject.pending_event_count).to eq 0
43
+ end
44
+ end
45
+
46
+ context "when telemetry is disabled" do
47
+ before do
48
+ allow(subject).to receive(:enabled?).and_return false
49
+ end
50
+ it "does not write any events and clears the queue" do
51
+ subject.capture(:test)
52
+ expect(subject.pending_event_count).to eq 1
53
+ expect(subject).to_not receive(:convert_events_to_session)
54
+
55
+ subject.commit
56
+ expect(subject.pending_event_count).to eq 0
57
+ end
58
+ end
59
+ end
60
+
61
+ context "#timed_action_capture" do
62
+ context "when a valid target_host is present" do
63
+ it "invokes timed_capture with action and valid target data" do
64
+ target = instance_double("TargetHost",
65
+ base_os: "windows",
66
+ version: "10.0.0",
67
+ architecture: "x86_64",
68
+ hostname: "My_Host",
69
+ transport_type: "winrm")
70
+ action = instance_double("Action::Base", name: "test_action",
71
+ target_host: target)
72
+ expected_data = {
73
+ action: "test_action",
74
+ target: {
75
+ platform: {
76
+ name: "windows",
77
+ version: "10.0.0",
78
+ architecture: "x86_64"
79
+ },
80
+ hostname_sha1: Digest::SHA1.hexdigest("my_host"),
81
+ transport_type: "winrm"
82
+ }
83
+ }
84
+ expect(subject).to receive(:timed_capture).with(:action, expected_data)
85
+ subject.timed_action_capture(action) { :ok }
86
+ end
87
+
88
+ context "when a valid target_host is not present" do
89
+ it "invokes timed_capture with empty target values" do
90
+ expected_data = { action: "Base", target: { platform: {},
91
+ hostname_sha1: nil,
92
+ transport_type: nil } }
93
+ expect(subject).to receive(:timed_capture).
94
+ with(:action, expected_data)
95
+ subject.timed_action_capture(
96
+ ChefApply::Action::Base.new(target_host: nil)
97
+ ) { :ok }
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ context "::enabled?" do
104
+ let(:enabled_flag) { false }
105
+ let(:config) { double("config") }
106
+ before do
107
+ allow(ChefApply::Config).to receive(:telemetry).and_return(config)
108
+ allow(config).to receive(:enable).and_return(enabled_flag)
109
+ end
110
+
111
+ context "when config value is enabled" do
112
+ let(:enabled_flag) { true }
113
+ context "and CHEF_TELEMETRY_OPT_OUT is not present in env vars" do
114
+ it "returns false" do
115
+ ENV.delete("CHEF_TELEMETRY_OPT_OUT")
116
+ expect(subject.enabled?).to eq true
117
+ end
118
+ end
119
+ context "and CHEF_TELEMETRY_OPT_OUT is present in env vars" do
120
+ it "returns false" do
121
+ ENV["CHEF_TELEMETRY_OPT_OUT"] = ""
122
+ expect(subject.enabled?).to eq false
123
+ end
124
+ end
125
+ end
126
+
127
+ context "when config value is disabled" do
128
+ let(:enabled_flag) { false }
129
+ it "returns false" do
130
+ expect(subject.enabled?).to eq false
131
+ end
132
+ end
133
+ end
134
+
135
+ context "#timed_run_capture" do
136
+ it "invokes timed_capture with run data" do
137
+ expected_data = { arguments: [ "arg1" ] }
138
+ expect(subject).to receive(:timed_capture).
139
+ with(:run, expected_data)
140
+ subject.timed_run_capture(["arg1"])
141
+ end
142
+ end
143
+
144
+ context "#timed_capture" do
145
+ let(:runner) { double("capture_test") }
146
+ before do
147
+ expect(subject.pending_event_count).to eq 0
148
+ end
149
+
150
+ it "runs the requested thing and invokes #capture with duration" do
151
+ expect(runner).to receive(:do_it)
152
+ expect(subject).to receive(:capture) do |name, data|
153
+ expect(name).to eq(:do_it_test)
154
+ expect(data[:duration]).to be > 0.0
155
+ end
156
+ subject.timed_capture(:do_it_test) do
157
+ runner.do_it
158
+ end
159
+ end
160
+ end
161
+
162
+ context "#capture" do
163
+ before do
164
+ expect(subject.pending_event_count).to eq 0
165
+ end
166
+ it "adds the captured event to the session" do
167
+ subject.capture(:test, {})
168
+ expect(subject.pending_event_count) == 1
169
+ end
170
+ end
171
+
172
+ context "#make_event_payload" do
173
+ before do
174
+ allow(subject).to receive(:installation_id).and_return "0000"
175
+ end
176
+
177
+ it "adds expected properties" do
178
+ payload = subject.make_event_payload(:run, { hello: "world" })
179
+ expected_payload = {
180
+ event: :run,
181
+ properties: {
182
+ installation_id: "0000",
183
+ run_timestamp: subject.run_timestamp,
184
+ host_platform: host_platform,
185
+ event_data: { hello: "world" }
186
+ }
187
+ }
188
+ expect(payload).to eq expected_payload
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,199 @@
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 "spec_helper"
19
+ require "chef_apply/temp_cookbook"
20
+ require "tempfile"
21
+ require "securerandom"
22
+
23
+ RSpec.describe ChefApply::TempCookbook do
24
+ subject(:tc) { ChefApply::TempCookbook.new }
25
+ let(:uuid) { SecureRandom.uuid }
26
+
27
+ before do
28
+ @repo_paths = ChefApply::Config.chef.cookbook_repo_paths
29
+ ChefApply::Config.chef.cookbook_repo_paths = []
30
+ end
31
+
32
+ after do
33
+ ChefApply::Config.chef.cookbook_repo_paths = @repo_paths
34
+ tc.delete
35
+ end
36
+
37
+ describe "#from_existing_recipe" do
38
+ it "raises an error if the recipe does not have a .rb extension" do
39
+ err = ChefApply::TempCookbook::UnsupportedExtension
40
+ expect { tc.from_existing_recipe("/some/file.chef") }.to raise_error(err)
41
+ end
42
+
43
+ context "when there is an existing cookbook" do
44
+ let(:cb) do
45
+ d = Dir.mktmpdir
46
+ File.open(File.join(d, "metadata.rb"), "w+") do |f|
47
+ f << "name \"foo\""
48
+ end
49
+ FileUtils.mkdir(File.join(d, "recipes"))
50
+ d
51
+ end
52
+
53
+ let(:existing_recipe) do
54
+ File.open(File.join(cb, "recipes/default.rb"), "w+") do |f|
55
+ f.write(uuid)
56
+ f
57
+ end
58
+ end
59
+
60
+ after do
61
+ FileUtils.remove_entry cb
62
+ end
63
+
64
+ it "copies the whole cookbook" do
65
+ tc.from_existing_recipe(existing_recipe.path)
66
+ expect(File.read(File.join(tc.path, "recipes/default.rb"))).to eq(uuid)
67
+ expect(File.read(File.join(tc.path, "Policyfile.rb"))).to eq <<~EXPECTED_POLICYFILE
68
+ name "foo_policy"
69
+ default_source :supermarket
70
+ run_list "foo::default"
71
+ cookbook "foo", path: "."
72
+ EXPECTED_POLICYFILE
73
+ expect(File.read(File.join(tc.path, "metadata.rb"))).to eq("name \"foo\"")
74
+ end
75
+ end
76
+
77
+ context "when there is only a single recipe not in a cookbook" do
78
+ let(:existing_recipe) do
79
+ t = Tempfile.new(["recipe", ".rb"])
80
+ t.write(uuid)
81
+ t.close
82
+ t
83
+ end
84
+
85
+ after do
86
+ existing_recipe.unlink
87
+ end
88
+
89
+ it "copies the existing recipe into a new cookbook" do
90
+ tc.from_existing_recipe(existing_recipe.path)
91
+ recipe_filename = File.basename(existing_recipe.path)
92
+ recipe_name = File.basename(recipe_filename, File.extname(recipe_filename))
93
+ expect(File.read(File.join(tc.path, "recipes/", recipe_filename))).to eq(uuid)
94
+ expect(File.read(File.join(tc.path, "Policyfile.rb"))).to eq <<~EXPECTED_POLICYFILE
95
+ name "cw_recipe_policy"
96
+ default_source :supermarket
97
+ run_list "cw_recipe::#{recipe_name}"
98
+ cookbook "cw_recipe", path: "."
99
+ EXPECTED_POLICYFILE
100
+ expect(File.read(File.join(tc.path, "metadata.rb"))).to eq("name \"cw_recipe\"\n")
101
+ end
102
+ end
103
+ end
104
+
105
+ describe "#from_resource" do
106
+ it "creates a recipe containing the supplied recipe" do
107
+ tc.from_resource("directory", "/tmp/foo", [])
108
+ expect(File.read(File.join(tc.path, "recipes/default.rb"))).to eq("directory '/tmp/foo'\n")
109
+ end
110
+ end
111
+
112
+ describe "#generate_metadata" do
113
+ it "generates metadata in the temp cookbook" do
114
+ f = tc.generate_metadata("foo")
115
+ expect(File.read(f)).to eq("name \"foo\"\n")
116
+ end
117
+ end
118
+
119
+ describe "#generate_policyfile" do
120
+ context "when there is no existing policyfile" do
121
+ it "generates a policyfile in the temp cookbook" do
122
+ f = tc.generate_policyfile("foo", "bar")
123
+ expect(File.read(f)).to eq <<~EXPECTED_POLICYFILE
124
+ name "foo_policy"
125
+ default_source :supermarket
126
+ run_list "foo::bar"
127
+ cookbook "foo", path: "."
128
+ EXPECTED_POLICYFILE
129
+ end
130
+
131
+ context "when there are configured cookbook_repo_paths" do
132
+ it "generates a policyfile in the temp cookbook" do
133
+ ChefApply::Config.chef.cookbook_repo_paths = %w{one two}
134
+ f = tc.generate_policyfile("foo", "bar")
135
+ expect(File.read(f)).to eq <<~EXPECTED_POLICYFILE
136
+ name "foo_policy"
137
+ default_source :chef_repo, "one"
138
+ default_source :chef_repo, "two"
139
+ default_source :supermarket
140
+ run_list "foo::bar"
141
+ cookbook "foo", path: "."
142
+ EXPECTED_POLICYFILE
143
+ end
144
+ end
145
+ end
146
+
147
+ context "when there is an existing policyfile" do
148
+ before do
149
+ File.open(File.join(tc.path, "Policyfile.rb"), "a") do |f|
150
+ f << "this is a policyfile"
151
+ end
152
+ end
153
+ it "only overrides the existing run_list in the policyfile" do
154
+ f = tc.generate_policyfile("foo", "bar")
155
+ expect(File.read(f)).to eq <<~EXPECTED_POLICYFILE
156
+ this is a policyfile
157
+ # Overriding run_list with command line specified value
158
+ run_list "foo::bar"
159
+ EXPECTED_POLICYFILE
160
+ end
161
+ end
162
+ end
163
+
164
+ describe "#create_resource_definition" do
165
+ let(:r1) { "directory" }
166
+ let(:r2) { "/tmp" }
167
+ let(:props) { nil }
168
+ context "when no properties are provided" do
169
+ it "it creates a simple resource" do
170
+ expect(tc.create_resource_definition(r1, r2, [])).to eq("directory '/tmp'\n")
171
+ end
172
+ end
173
+
174
+ context "when properties are provided" do
175
+ let(:props) do
176
+ {
177
+ "key1" => "value",
178
+ "key2" => 0.1,
179
+ "key3" => 100,
180
+ "key4" => true,
181
+ "key_with_underscore" => "value",
182
+ }
183
+ end
184
+
185
+ it "converts the properties to chef-client args" do
186
+ expected = <<~EXPECTED_RESOURCE
187
+ directory '/tmp' do
188
+ key1 'value'
189
+ key2 0.1
190
+ key3 100
191
+ key4 true
192
+ key_with_underscore 'value'
193
+ end
194
+ EXPECTED_RESOURCE
195
+ expect(tc.create_resource_definition(r1, r2, props)).to eq(expected)
196
+ end
197
+ end
198
+ end
199
+ end