itamae 1.10.0 → 1.12.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +203 -0
  3. data/.gitignore +1 -0
  4. data/CHANGELOG.md +144 -1
  5. data/README.md +2 -3
  6. data/Rakefile +50 -42
  7. data/bin/itamae +0 -1
  8. data/itamae.gemspec +2 -2
  9. data/lib/itamae/backend.rb +20 -5
  10. data/lib/itamae/cli.rb +14 -9
  11. data/lib/itamae/definition.rb +0 -2
  12. data/lib/itamae/logger.rb +5 -1
  13. data/lib/itamae/mash.rb +7 -0
  14. data/lib/itamae/node.rb +4 -4
  15. data/lib/itamae/notification.rb +0 -2
  16. data/lib/itamae/recipe.rb +20 -3
  17. data/lib/itamae/resource/base.rb +3 -3
  18. data/lib/itamae/resource/directory.rb +0 -2
  19. data/lib/itamae/resource/execute.rb +0 -2
  20. data/lib/itamae/resource/file.rb +43 -6
  21. data/lib/itamae/resource/gem_package.rb +0 -2
  22. data/lib/itamae/resource/git.rb +5 -7
  23. data/lib/itamae/resource/group.rb +0 -2
  24. data/lib/itamae/resource/http_request.rb +12 -3
  25. data/lib/itamae/resource/link.rb +0 -2
  26. data/lib/itamae/resource/local_ruby_block.rb +0 -2
  27. data/lib/itamae/resource/package.rb +3 -3
  28. data/lib/itamae/resource/remote_directory.rb +1 -3
  29. data/lib/itamae/resource/remote_file.rb +0 -2
  30. data/lib/itamae/resource/service.rb +0 -2
  31. data/lib/itamae/resource/template.rb +8 -4
  32. data/lib/itamae/resource/user.rb +0 -2
  33. data/lib/itamae/resource.rb +0 -1
  34. data/lib/itamae/runner.rb +3 -4
  35. data/lib/itamae/version.rb +1 -1
  36. data/lib/itamae.rb +1 -1
  37. data/spec/integration/default_spec.rb +26 -32
  38. data/spec/integration/docker_spec.rb +29 -0
  39. data/spec/integration/local_spec.rb +6 -0
  40. data/spec/integration/ordinary_user_spec.rb +108 -0
  41. data/spec/integration/recipes/default.rb +10 -48
  42. data/spec/integration/recipes/docker.rb +44 -0
  43. data/spec/integration/recipes/local.rb +19 -0
  44. data/spec/integration/recipes/ordinary_user.rb +109 -0
  45. data/spec/integration/recipes/toplevel_module.rb +6 -0
  46. data/spec/integration/recipes/variables.rb +14 -0
  47. data/spec/unit/lib/itamae/backend_spec.rb +10 -10
  48. data/tasks/integration_local_spec.rb +117 -0
  49. metadata +26 -9
  50. data/.travis.yml +0 -39
@@ -0,0 +1,108 @@
1
+ require 'spec_helper'
2
+
3
+ describe file('/tmp/remote_file') do
4
+ it { should be_file }
5
+ it { should be_owned_by "ordinary_san" }
6
+ it { should be_grouped_into "ordinary_san" }
7
+ its(:content) { should match(/Hello Itamae/) }
8
+ end
9
+
10
+ describe file('/tmp/remote_file_root') do
11
+ it { should be_file }
12
+ it { should be_owned_by "root" }
13
+ it { should be_grouped_into "root" }
14
+ its(:content) { should match(/Hello Itamae/) }
15
+ end
16
+
17
+ %w[/tmp/remote_file_another_ordinary /tmp/remote_file_another_ordinary_with_root].each do |path|
18
+ describe file(path) do
19
+ it { should be_file }
20
+ it { should be_owned_by "itamae" }
21
+ it { should be_grouped_into "itamae" }
22
+ its(:content) { should match(/Hello Itamae/) }
23
+ end
24
+ end
25
+
26
+ ###
27
+
28
+ describe file('/tmp/file') do
29
+ it { should be_file }
30
+ it { should be_owned_by "ordinary_san" }
31
+ it { should be_grouped_into "ordinary_san" }
32
+ its(:content) { should match(/Hello World/) }
33
+ end
34
+
35
+ describe file('/tmp/file_root') do
36
+ it { should be_file }
37
+ it { should be_owned_by "root" }
38
+ it { should be_grouped_into "root" }
39
+ its(:content) { should match(/Hello World/) }
40
+ end
41
+
42
+ %w[/tmp/file_another_ordinary /tmp/file_another_ordinary_with_root].each do |path|
43
+ describe file(path) do
44
+ it { should be_file }
45
+ it { should be_owned_by "itamae" }
46
+ it { should be_grouped_into "itamae" }
47
+ its(:content) { should match(/Hello World/) }
48
+ end
49
+ end
50
+
51
+ ###
52
+
53
+ describe file('/tmp/template') do
54
+ it { should be_file }
55
+ it { should be_owned_by "ordinary_san" }
56
+ it { should be_grouped_into "ordinary_san" }
57
+ its(:content) { should match(/Hello/) }
58
+ its(:content) { should match(/Good bye/) }
59
+ its(:content) { should match(/^total memory: \d+kB$/) }
60
+ its(:content) { should match(/^uninitialized node key: $/) }
61
+ end
62
+
63
+ describe file('/tmp/template_root') do
64
+ it { should be_file }
65
+ it { should be_owned_by "root" }
66
+ it { should be_grouped_into "root" }
67
+ its(:content) { should match(/Hello/) }
68
+ its(:content) { should match(/Good bye/) }
69
+ its(:content) { should match(/^total memory: \d+kB$/) }
70
+ its(:content) { should match(/^uninitialized node key: $/) }
71
+ end
72
+
73
+ %w[/tmp/template_another_ordinary /tmp/template_another_ordinary_with_root].each do |path|
74
+ describe file(path) do
75
+ it { should be_file }
76
+ it { should be_owned_by "itamae" }
77
+ it { should be_grouped_into "itamae" }
78
+ its(:content) { should match(/Hello/) }
79
+ its(:content) { should match(/Good bye/) }
80
+ its(:content) { should match(/^total memory: \d+kB$/) }
81
+ its(:content) { should match(/^uninitialized node key: $/) }
82
+ end
83
+ end
84
+
85
+ ###
86
+
87
+ describe file('/tmp/http_request.html') do
88
+ it { should be_file }
89
+ it { should be_owned_by "ordinary_san" }
90
+ it { should be_grouped_into "ordinary_san" }
91
+ its(:content) { should match(/"from":\s*"itamae"/) }
92
+ end
93
+
94
+ describe file('/tmp/http_request_root.html') do
95
+ it { should be_file }
96
+ it { should be_owned_by "root" }
97
+ it { should be_grouped_into "root" }
98
+ its(:content) { should match(/"from":\s*"itamae"/) }
99
+ end
100
+
101
+ %w[/tmp/http_request_another_ordinary.html /tmp/http_request_another_ordinary_with_root.html].each do |path|
102
+ describe file(path) do
103
+ it { should be_file }
104
+ it { should be_owned_by "itamae" }
105
+ it { should be_grouped_into "itamae" }
106
+ its(:content) { should match(/"from":\s*"itamae"/) }
107
+ end
108
+ end
@@ -45,10 +45,6 @@ package 'dstat' do
45
45
  action :install
46
46
  end
47
47
 
48
- package 'sl' do
49
- version '3.03-17'
50
- end
51
-
52
48
  package 'resolvconf' do
53
49
  action :remove
54
50
  end
@@ -65,10 +61,6 @@ gem_package 'tzinfo' do
65
61
  version '1.2.2'
66
62
  end
67
63
 
68
- gem_package 'bundler' do
69
- options ['--no-ri', '--no-rdoc']
70
- end
71
-
72
64
  gem_package 'rake' do
73
65
  version '11.1.0'
74
66
  end
@@ -218,46 +210,10 @@ http_request "/tmp/http_request_headers.html" do
218
210
  url "https://httpbin.org/get"
219
211
  end
220
212
 
221
- http_request "/tmp/http_request_redirect.html" do
222
- redirect_limit 1
223
- url "https://httpbin.org/redirect-to?url=https%3A%2F%2Fhttpbin.org%2Fget%3Ffrom%3Ditamae"
224
- end
225
-
226
- ######
227
-
228
- service "cron" do
229
- action :stop
230
- end
231
-
232
- execute "ps -C cron > /tmp/cron_stopped; true"
233
-
234
- service "cron" do
235
- action :start
236
- end
237
-
238
- execute "ps -C cron > /tmp/cron_running; true"
239
-
240
- ######
241
-
242
- package "nginx" do
243
- options "--force-yes"
244
- end
245
-
246
- service "nginx" do
247
- action [:enable, :start]
248
- end
249
-
250
- execute "test -f /etc/rc3.d/S20nginx" # test
251
- execute "test $(ps h -C nginx | wc -l) -gt 0" # test
252
-
253
- service "nginx" do
254
- action [:disable, :stop]
255
- end
256
-
257
- execute "test ! -f /etc/rc3.d/S20nginx" # test
258
- execute "test $(ps h -C nginx | wc -l) -eq 0" # test
259
-
260
- ######
213
+ # http_request "/tmp/http_request_redirect.html" do
214
+ # redirect_limit 1
215
+ # url "https://httpbin.org/redirect-to?url=https%3A%2F%2Fhttpbin.org%2Fget%3Ffrom%3Ditamae"
216
+ # end
261
217
 
262
218
  link "/tmp-link" do
263
219
  to "/tmp"
@@ -600,3 +556,9 @@ unless v1 == v2 && v2 == v3 && v1 =~ /\A\d+kB\z/
600
556
  raise "failed to fetch host inventory value (#{v1}, #{v2}, #{v3})"
601
557
  end
602
558
 
559
+ include_recipe "toplevel_module"
560
+ file "/tmp/toplevel_module" do
561
+ content ToplevelModule.helper
562
+ end
563
+
564
+ include_recipe "variables"
@@ -0,0 +1,44 @@
1
+ package 'sl' do
2
+ version '3.03-17'
3
+ end
4
+
5
+ ######
6
+
7
+ gem_package 'ast' do
8
+ version '2.0.0'
9
+ options ['--no-ri', '--no-rdoc']
10
+ end
11
+
12
+ ######
13
+
14
+ service "cron" do
15
+ action :stop
16
+ end
17
+
18
+ execute "ps -C cron > /tmp/cron_stopped; true"
19
+
20
+ service "cron" do
21
+ action :start
22
+ end
23
+
24
+ execute "ps -C cron > /tmp/cron_running; true"
25
+
26
+ ######
27
+
28
+ package "nginx" do
29
+ options "--force-yes"
30
+ end
31
+
32
+ service "nginx" do
33
+ action [:enable, :start]
34
+ end
35
+
36
+ execute "test -f /etc/rc3.d/S20nginx" # test
37
+ execute "test $(ps h -C nginx | wc -l) -gt 0" # test
38
+
39
+ service "nginx" do
40
+ action [:disable, :stop]
41
+ end
42
+
43
+ execute "test ! -f /etc/rc3.d/S20nginx" # test
44
+ execute "test $(ps h -C nginx | wc -l) -eq 0" # test
@@ -0,0 +1,19 @@
1
+ package 'sl'
2
+
3
+ ######
4
+
5
+ gem_package 'ast' do
6
+ version '2.0.0'
7
+ options ['--no-document']
8
+ end
9
+
10
+ ######
11
+
12
+ # Docker backend raises an error with `user` option, so it tests only on `itamae local`.
13
+ # After fix this error, please move this code and the spec to `default.rb`.
14
+ file "/tmp/file_as_ordinary_user" do
15
+ content "Hello World"
16
+ user "itamae"
17
+ owner "itamae"
18
+ group "itamae"
19
+ end
@@ -0,0 +1,109 @@
1
+ remote_file "/tmp/remote_file" do
2
+ source "hello.txt"
3
+ end
4
+
5
+ remote_file "/tmp/remote_file_root" do
6
+ user 'root'
7
+ owner 'root'
8
+ group 'root'
9
+ source "hello.txt"
10
+ end
11
+
12
+ remote_file "/tmp/remote_file_another_ordinary" do
13
+ user 'itamae'
14
+ owner 'itamae'
15
+ group 'itamae'
16
+ source "hello.txt"
17
+ end
18
+
19
+ remote_file "/tmp/remote_file_another_ordinary_with_root" do
20
+ user 'root'
21
+ owner 'itamae'
22
+ group 'itamae'
23
+ source "hello.txt"
24
+ end
25
+
26
+ ###
27
+
28
+ file "/tmp/file" do
29
+ content "Hello World"
30
+ end
31
+
32
+ file "/tmp/file_root" do
33
+ user 'root'
34
+ owner 'root'
35
+ group 'root'
36
+ content 'Hello World'
37
+ end
38
+
39
+ file "/tmp/file_another_ordinary" do
40
+ user 'itamae'
41
+ owner 'itamae'
42
+ group 'itamae'
43
+ content 'Hello World'
44
+ end
45
+
46
+ file "/tmp/file_another_ordinary_with_root" do
47
+ user 'root'
48
+ owner 'itamae'
49
+ group 'itamae'
50
+ content 'Hello World'
51
+ end
52
+
53
+ ###
54
+
55
+ template "/tmp/template" do
56
+ source "hello.erb"
57
+ variables goodbye: "Good bye"
58
+ end
59
+
60
+ template "/tmp/template_root" do
61
+ user 'root'
62
+ owner 'root'
63
+ group 'root'
64
+ source "hello.erb"
65
+ variables goodbye: "Good bye"
66
+ end
67
+
68
+ template "/tmp/template_another_ordinary" do
69
+ user 'itamae'
70
+ owner 'itamae'
71
+ group 'itamae'
72
+ source "hello.erb"
73
+ variables goodbye: "Good bye"
74
+ end
75
+
76
+ template "/tmp/template_another_ordinary_with_root" do
77
+ user 'root'
78
+ owner 'itamae'
79
+ group 'itamae'
80
+ source "hello.erb"
81
+ variables goodbye: "Good bye"
82
+ end
83
+
84
+ ###
85
+
86
+ http_request "/tmp/http_request.html" do
87
+ url "https://httpbin.org/get?from=itamae"
88
+ end
89
+
90
+ http_request "/tmp/http_request_root.html" do
91
+ user 'root'
92
+ owner 'root'
93
+ group 'root'
94
+ url "https://httpbin.org/get?from=itamae"
95
+ end
96
+
97
+ http_request "/tmp/http_request_another_ordinary.html" do
98
+ user 'itamae'
99
+ owner 'itamae'
100
+ group 'itamae'
101
+ url "https://httpbin.org/get?from=itamae"
102
+ end
103
+
104
+ http_request "/tmp/http_request_another_ordinary_with_root.html" do
105
+ user 'root'
106
+ owner 'itamae'
107
+ group 'itamae'
108
+ url "https://httpbin.org/get?from=itamae"
109
+ end
@@ -0,0 +1,6 @@
1
+ # Testing you don't need to write `module ::ToplevelModule`
2
+ module ToplevelModule
3
+ def self.helper
4
+ "helper"
5
+ end
6
+ end
@@ -0,0 +1,14 @@
1
+ node.reverse_merge!(
2
+ variables: {
3
+ lvars: binding.local_variables,
4
+ ivars: instance_variables,
5
+ }
6
+ )
7
+
8
+ file "/tmp/local_variables" do
9
+ content node[:variables][:lvars].to_s
10
+ end
11
+
12
+ file "/tmp/instance_variables" do
13
+ content node[:variables][:ivars].to_s
14
+ end
@@ -17,38 +17,38 @@ module Itamae
17
17
 
18
18
  describe ".send_file" do
19
19
  context "the source file doesn't exist" do
20
- subject { -> { itamae_backend.send_file("src", "dst") } }
21
- it { expect(subject).to raise_error(Itamae::Backend::SourceNotExistError, "The file 'src' doesn't exist.") }
20
+ subject { itamae_backend.send_file("src", "dst") }
21
+ it { expect{ subject }.to raise_error(Itamae::Backend::SourceNotExistError, "The file 'src' doesn't exist.") }
22
22
  end
23
23
 
24
24
  context "the source file exist, but it is not a regular file" do
25
25
  before { Dir.mkdir("src") }
26
- subject { -> { itamae_backend.send_file("src", "dst") } }
27
- it { expect(subject).to raise_error(Itamae::Backend::SourceNotExistError, "'src' is not a file.") }
26
+ subject { itamae_backend.send_file("src", "dst") }
27
+ it { expect{ subject }.to raise_error(Itamae::Backend::SourceNotExistError, "'src' is not a file.") }
28
28
  end
29
29
 
30
30
  context "the source file is a regular file" do
31
31
  before { FileUtils.touch("src") }
32
- subject { -> { itamae_backend.send_file("src", "dst") } }
32
+ subject { itamae_backend.send_file("src", "dst") }
33
33
  it { expect { subject }.not_to raise_error }
34
34
  end
35
35
  end
36
36
 
37
37
  describe ".send_directory" do
38
38
  context "the source directory doesn't exist" do
39
- subject { -> { itamae_backend.send_directory("src", "dst") } }
40
- it { expect(subject).to raise_error(Itamae::Backend::SourceNotExistError, "The directory 'src' doesn't exist.") }
39
+ subject { itamae_backend.send_directory("src", "dst") }
40
+ it { expect{ subject }.to raise_error(Itamae::Backend::SourceNotExistError, "The directory 'src' doesn't exist.") }
41
41
  end
42
42
 
43
43
  context "the source directory exist, but it is not a directory" do
44
44
  before { FileUtils.touch("src") }
45
- subject { -> { itamae_backend.send_directory("src", "dst") } }
46
- it { expect(subject).to raise_error(Itamae::Backend::SourceNotExistError, "'src' is not a directory.") }
45
+ subject { itamae_backend.send_directory("src", "dst") }
46
+ it { expect{ subject }.to raise_error(Itamae::Backend::SourceNotExistError, "'src' is not a directory.") }
47
47
  end
48
48
 
49
49
  context "the source directory is a directory" do
50
50
  before { Dir.mkdir("src") }
51
- subject { -> { itamae_backend.send_directory("src", "dst") } }
51
+ subject { itamae_backend.send_directory("src", "dst") }
52
52
  it { expect { subject }.not_to raise_error }
53
53
  end
54
54
  end
@@ -0,0 +1,117 @@
1
+ desc 'Run all integration tests on `itamae local` command'
2
+ task 'spec:integration:local' => ['spec:integration:local:main', 'spec:integration:local:ordinary_user']
3
+
4
+ namespace 'spec:integration:local' do
5
+ desc 'Run main integration test with `itamae local`'
6
+ task 'main' do
7
+ if RUBY_DESCRIPTION.include?('dev')
8
+ $stderr.puts "This integration test is skipped with unreleased Ruby."
9
+ $stderr.puts "Use released Ruby to execute this integration test."
10
+ next
11
+ end
12
+
13
+ IntegrationLocalSpecRunner.new(
14
+ [
15
+ [
16
+ "spec/integration/recipes/default.rb",
17
+ "spec/integration/recipes/default2.rb",
18
+ "spec/integration/recipes/redefine.rb",
19
+ "spec/integration/recipes/local.rb",
20
+ ],
21
+ [
22
+ "--dry-run",
23
+ "spec/integration/recipes/dry_run.rb",
24
+ ],
25
+ ],
26
+ ['spec/integration/default_spec.rb']
27
+ ).run
28
+
29
+ end
30
+
31
+ desc 'Run integration test for ordinary user with `itamae local`'
32
+ task 'ordinary_user' do
33
+ if RUBY_DESCRIPTION.include?('dev')
34
+ $stderr.puts "This integration test is skipped with unreleased Ruby."
35
+ $stderr.puts "Use released Ruby to execute this integration test."
36
+ next
37
+ end
38
+
39
+ runner = IntegrationLocalSpecRunner.new(
40
+ [
41
+ [
42
+ "--dry-run",
43
+ "spec/integration/recipes/ordinary_user.rb",
44
+ ],
45
+ [
46
+ "spec/integration/recipes/ordinary_user.rb"
47
+ ],
48
+ ],
49
+ ['spec/integration/ordinary_user_spec.rb'],
50
+ user: 'ordinary_san'
51
+ )
52
+ runner.docker_exec 'useradd', 'ordinary_san', '-p', '*'
53
+ runner.docker_exec 'useradd', 'itamae', '-p', '*', '--create-home'
54
+ runner.docker_exec 'sh', '-c', 'echo "ordinary_san ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers'
55
+ runner.run
56
+ end
57
+ end
58
+
59
+ class IntegrationLocalSpecRunner
60
+ CONTAINER_NAME = 'itamae'
61
+ include FileUtils
62
+
63
+ def initialize(suites, specs, ruby_version: RUBY_VERSION.split('.')[0..1].join('.'), user: nil)
64
+ @suites = suites
65
+ @specs = specs
66
+ @ruby_version = ruby_version
67
+ @user = user
68
+
69
+ docker_run
70
+ prepare
71
+ end
72
+
73
+ def run
74
+ provision
75
+ serverspec
76
+ clean_docker_container
77
+ end
78
+
79
+ def docker_run
80
+ mount_dir = Pathname(__dir__).join('../').to_s
81
+ sh 'docker', 'run', '--privileged', '-d', '--name', CONTAINER_NAME, '-v', "#{mount_dir}:/itamae", "ruby:#{@ruby_version}", 'sleep', '1d'
82
+ end
83
+
84
+ def prepare
85
+ docker_exec 'gem', 'install', 'bundler'
86
+ docker_exec 'bundle', 'install', options: %w[--workdir /itamae]
87
+ docker_exec 'apt-get', 'update', '-y'
88
+ docker_exec 'apt-get', 'install', 'locales', 'sudo', '-y'
89
+ docker_exec 'localedef', '-i', 'en_US', '-c', '-f', 'UTF-8', '-A', '/usr/share/locale/locale.alias', 'en_US.UTF-8'
90
+ end
91
+
92
+ def provision
93
+ @suites.each do |suite|
94
+ cmd = %W!bundle exec ruby -w bin/itamae local!
95
+ cmd << "-l" << (ENV['LOG_LEVEL'] || 'debug')
96
+ cmd << "-j" << "spec/integration/recipes/node.json"
97
+ cmd += suite
98
+
99
+ options = %w[--workdir /itamae]
100
+ options.push('--user', @user) if @user
101
+ docker_exec(*cmd, options: options)
102
+ end
103
+ end
104
+
105
+ def serverspec
106
+ ENV['DOCKER_CONTAINER'] = CONTAINER_NAME
107
+ sh('bundle', 'exec', 'rspec', '-I', './spec/integration', *@specs)
108
+ end
109
+
110
+ def clean_docker_container
111
+ sh('docker', 'rm', '-f', CONTAINER_NAME)
112
+ end
113
+
114
+ def docker_exec(*cmd, options: [])
115
+ sh 'docker', 'exec', '--env', 'LANG=en_US.utf8', *options, CONTAINER_NAME, *cmd
116
+ end
117
+ end