itamae 1.10.0 → 1.12.5

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 (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