itamae-spec 0.0.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 (77) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +7 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +42 -0
  7. data/Rakefile +47 -0
  8. data/bin/itamae-spec +4 -0
  9. data/itamae-spec.gemspec +36 -0
  10. data/lib/itamae-spec.rb +10 -0
  11. data/lib/itamae-spec/cli.rb +19 -0
  12. data/lib/itamae-spec/generators.rb +20 -0
  13. data/lib/itamae-spec/generators/cookbook.rb +10 -0
  14. data/lib/itamae-spec/generators/project.rb +10 -0
  15. data/lib/itamae-spec/generators/templates/cookbook/attributes/.keep +0 -0
  16. data/lib/itamae-spec/generators/templates/cookbook/recipes/default.rb +0 -0
  17. data/lib/itamae-spec/generators/templates/cookbook/recipes/files/.keep +0 -0
  18. data/lib/itamae-spec/generators/templates/cookbook/recipes/templates/.keep +0 -0
  19. data/lib/itamae-spec/generators/templates/cookbook/spec/default_spec.rb +0 -0
  20. data/lib/itamae-spec/generators/templates/project/.rspec +2 -0
  21. data/lib/itamae-spec/generators/templates/project/Gemfile +3 -0
  22. data/lib/itamae-spec/generators/templates/project/Project.json +1 -0
  23. data/lib/itamae-spec/generators/templates/project/Rakefile +9 -0
  24. data/lib/itamae-spec/generators/templates/project/cookbooks/sample/attributes/.keep +0 -0
  25. data/lib/itamae-spec/generators/templates/project/cookbooks/sample/attributes/default.json +5 -0
  26. data/lib/itamae-spec/generators/templates/project/cookbooks/sample/recipes/default.rb +7 -0
  27. data/lib/itamae-spec/generators/templates/project/cookbooks/sample/recipes/files/.keep +0 -0
  28. data/lib/itamae-spec/generators/templates/project/cookbooks/sample/recipes/templates/.keep +0 -0
  29. data/lib/itamae-spec/generators/templates/project/cookbooks/sample/spec/default_spec.rb +9 -0
  30. data/lib/itamae-spec/generators/templates/project/environments/.keep +0 -0
  31. data/lib/itamae-spec/generators/templates/project/environments/sample.json +7 -0
  32. data/lib/itamae-spec/generators/templates/project/keys/.keep +0 -0
  33. data/lib/itamae-spec/generators/templates/project/nodes/.keep +0 -0
  34. data/lib/itamae-spec/generators/templates/project/nodes/sample.json +10 -0
  35. data/lib/itamae-spec/generators/templates/project/roles/.keep +0 -0
  36. data/lib/itamae-spec/generators/templates/project/roles/sample.json +5 -0
  37. data/lib/itamae-spec/generators/templates/project/spec/spec_helper.rb +41 -0
  38. data/lib/itamae-spec/generators/templates/project/tmp-nodes/.keep +0 -0
  39. data/lib/itamae-spec/logger.rb +76 -0
  40. data/lib/itamae-spec/resource.rb +2 -0
  41. data/lib/itamae-spec/resource/http_request.rb +71 -0
  42. data/lib/itamae-spec/resource/s3_file.rb +31 -0
  43. data/lib/itamae-spec/task/base.rb +90 -0
  44. data/lib/itamae-spec/task/base_task.rb +148 -0
  45. data/lib/itamae-spec/task/itamae_task.rb +112 -0
  46. data/lib/itamae-spec/task/local_itamae_task.rb +84 -0
  47. data/lib/itamae-spec/task/local_serverspec_task.rb +125 -0
  48. data/lib/itamae-spec/task/serverspec_task.rb +111 -0
  49. data/lib/itamae-spec/version.rb +3 -0
  50. data/lib/itamae-spec/version.txt +1 -0
  51. data/spec/integration/Vagrantfile +35 -0
  52. data/spec/integration/default_spec.rb +226 -0
  53. data/spec/integration/recipes/default.rb +423 -0
  54. data/spec/integration/recipes/default2.rb +6 -0
  55. data/spec/integration/recipes/define/default.rb +6 -0
  56. data/spec/integration/recipes/define/files/remote_file_in_definition +1 -0
  57. data/spec/integration/recipes/dry_run.rb +6 -0
  58. data/spec/integration/recipes/files/remote_file_auto +1 -0
  59. data/spec/integration/recipes/hello.erb +6 -0
  60. data/spec/integration/recipes/hello.txt +1 -0
  61. data/spec/integration/recipes/included.rb +9 -0
  62. data/spec/integration/recipes/node.json +3 -0
  63. data/spec/integration/recipes/redefine.rb +20 -0
  64. data/spec/integration/recipes/templates/template_auto.erb +6 -0
  65. data/spec/integration/spec_helper.rb +42 -0
  66. data/spec/unit/lib/itamae/backend_spec.rb +95 -0
  67. data/spec/unit/lib/itamae/handler/base_spec.rb +34 -0
  68. data/spec/unit/lib/itamae/handler/fluentd_spec.rb +19 -0
  69. data/spec/unit/lib/itamae/handler_proxy_spec.rb +38 -0
  70. data/spec/unit/lib/itamae/handler_spec.rb +11 -0
  71. data/spec/unit/lib/itamae/node_spec.rb +14 -0
  72. data/spec/unit/lib/itamae/recipe_spec.rb +6 -0
  73. data/spec/unit/lib/itamae/resource/base_spec.rb +127 -0
  74. data/spec/unit/lib/itamae/resource_spec.rb +23 -0
  75. data/spec/unit/lib/itamae/runner_spec.rb +32 -0
  76. data/spec/unit/spec_helper.rb +23 -0
  77. metadata +315 -0
@@ -0,0 +1,6 @@
1
+ <%= node['greeting'] %>
2
+ <%= @goodbye %>
3
+
4
+ total memory: <%= node['memory']['total'] %>
5
+
6
+ uninitialized node key: <%= node['un-initialized'] %>
@@ -0,0 +1 @@
1
+ Hello Itamae
@@ -0,0 +1,9 @@
1
+ require 'pathname'
2
+ included_flag_file = Pathname.new("/tmp/included_rb_is_included")
3
+ if included_flag_file.exist? && included_flag_file.read == $$.to_s
4
+ raise "included.rb should not be included twice."
5
+ else
6
+ included_flag_file.write($$.to_s)
7
+ end
8
+
9
+ execute "touch /tmp/included_recipe"
@@ -0,0 +1,3 @@
1
+ {
2
+ "greeting": "Hello"
3
+ }
@@ -0,0 +1,20 @@
1
+ # spec/integration/recipes/redefine.rb
2
+ define :echo_hello, version: nil do
3
+ file "put file in redefine.rb" do
4
+ action :create
5
+ path "/tmp/created_in_redefine"
6
+ content 'first'
7
+ end
8
+ end
9
+
10
+ # Duplicated definitions
11
+ define :echo_hello, version: nil do
12
+ file "put file in redefine.rb" do
13
+ action :create
14
+ path "/tmp/created_in_redefine"
15
+ content 'second'
16
+ end
17
+ end
18
+
19
+ # Execute
20
+ echo_hello "execute echo_hello!"
@@ -0,0 +1,6 @@
1
+ <%= node['greeting'] %>
2
+ <%= @goodbye %>
3
+
4
+ total memory: <%= node['memory']['total'] %>
5
+
6
+ uninitialized node key: <%= node['un-initialized'] %>
@@ -0,0 +1,42 @@
1
+ require 'serverspec'
2
+ require 'net/ssh'
3
+ require 'tempfile'
4
+
5
+ set :backend, :ssh
6
+
7
+ def vagrant(cmd)
8
+ env = {"VAGRANT_CWD" => File.dirname(__FILE__)}
9
+ system(env, "vagrant #{cmd}")
10
+ end
11
+
12
+ if ENV['ASK_SUDO_PASSWORD']
13
+ begin
14
+ require 'highline/import'
15
+ rescue LoadError
16
+ fail "highline is not available. Try installing it."
17
+ end
18
+ set :sudo_password, ask("Enter sudo password: ") { |q| q.echo = false }
19
+ else
20
+ set :sudo_password, ENV['SUDO_PASSWORD']
21
+ end
22
+
23
+ host = ENV['TARGET_HOST']
24
+
25
+ config = Tempfile.new('', Dir.tmpdir)
26
+ vagrant "ssh-config #{host} > #{config.path}"
27
+
28
+ options = Net::SSH::Config.for(host, [config.path])
29
+
30
+ options[:user] ||= Etc.getlogin
31
+
32
+ set :host, options[:host_name] || host
33
+ set :ssh_options, options
34
+
35
+ # Disable sudo
36
+ # set :disable_sudo, true
37
+
38
+ # Set environment variables
39
+ # set :env, :LANG => 'C', :LC_MESSAGES => 'C'
40
+
41
+ # Set PATH
42
+ # set :path, '/sbin:/usr/local/sbin:$PATH'
@@ -0,0 +1,95 @@
1
+ require 'spec_helper'
2
+ require 'fakefs/spec_helpers'
3
+
4
+ module Itamae
5
+ module Backend
6
+ describe Base do
7
+ include FakeFS::SpecHelpers
8
+
9
+ class Klass < Itamae::Backend::Base
10
+ def initialize(_, backend)
11
+ @backend = backend
12
+ end
13
+ end
14
+
15
+ let(:backend) { double('backend', send_file: nil, send_directory: nil) }
16
+ let(:itamae_backend) { Klass.new('dummy', backend) }
17
+
18
+ describe ".send_file" do
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.") }
22
+ end
23
+
24
+ context "the source file exist, but it is not a regular file" do
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.") }
28
+ end
29
+
30
+ context "the source file is a regular file" do
31
+ before { FileUtils.touch("src") }
32
+ subject { -> { itamae_backend.send_file("src", "dst") } }
33
+ it { expect { subject }.not_to raise_error }
34
+ end
35
+ end
36
+
37
+ describe ".send_directory" do
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.") }
41
+ end
42
+
43
+ context "the source directory exist, but it is not a directory" do
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.") }
47
+ end
48
+
49
+ context "the source directory is a directory" do
50
+ before { Dir.mkdir("src") }
51
+ subject { -> { itamae_backend.send_directory("src", "dst") } }
52
+ it { expect { subject }.not_to raise_error }
53
+ end
54
+ end
55
+ end
56
+
57
+ describe Ssh do
58
+
59
+ describe "#ssh_options" do
60
+ subject { ssh.send(:ssh_options) }
61
+
62
+ let!(:ssh) { described_class.new(options) }
63
+ let!(:host_name) { "example.com" }
64
+ let!(:default_option) do
65
+ opts = {}
66
+ opts[:host_name] = nil
67
+ opts.merge!(Net::SSH::Config.for(host_name))
68
+ opts[:user] = opts[:user] || Etc.getlogin
69
+ opts
70
+ end
71
+
72
+ context "with host option" do
73
+ let(:options) { {host: host_name} }
74
+ it { is_expected.to eq( default_option.merge({host_name: host_name}) ) }
75
+ end
76
+ end
77
+
78
+ describe "#disable_sudo?" do
79
+ subject { ssh.send(:disable_sudo?) }
80
+
81
+ let!(:ssh) { described_class.new(options)}
82
+
83
+ context "when sudo option is true" do
84
+ let(:options) { {sudo: true} }
85
+ it { is_expected.to eq(false) }
86
+ end
87
+
88
+ context "when sudo option is false" do
89
+ let(:options) { {sudo: false} }
90
+ it { is_expected.to eq(true) }
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe Itamae::Handler::Base do
4
+ subject(:handler) { described_class.new({}) }
5
+
6
+ context "when receiving recipe_started event" do
7
+ it "stores the payload" do
8
+ subject.event(:recipe_started, :payload)
9
+ expect(subject.recipes).to eq([:payload])
10
+ end
11
+ end
12
+
13
+ context "when receiving recipe_completed event" do
14
+ before do
15
+ subject.event(:recipe_started, :payload)
16
+ end
17
+
18
+ it "pops the payload" do
19
+ subject.event(:recipe_completed, :payload)
20
+ expect(subject.recipes).to eq([])
21
+ end
22
+ end
23
+
24
+ context "when receiving recipe_failed event" do
25
+ before do
26
+ subject.event(:recipe_started, :payload)
27
+ end
28
+
29
+ it "pops the payload" do
30
+ subject.event(:recipe_failed, :payload)
31
+ expect(subject.recipes).to eq([])
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+ require 'itamae/handler/fluentd'
3
+
4
+ describe Itamae::Handler::Fluentd do
5
+ subject(:handler) do
6
+ described_class.new(options).tap do |h|
7
+ h.fluent_logger = fluent_logger
8
+ end
9
+ end
10
+ let(:options) { {'hostname' => 'me'} }
11
+ let(:fluent_logger) { Fluent::Logger::TestLogger.new }
12
+
13
+ describe '#event' do
14
+ it 'posts a record to fluent logger' do
15
+ subject.event(:type, {arg: 'value'})
16
+ expect(fluent_logger.queue).to eq([{arg: 'value', hostname: 'me'}])
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ module Itamae
4
+ describe HandlerProxy do
5
+ let(:handler) { instance_double(Handler::Base) }
6
+ before { subject.register_instance(handler) }
7
+
8
+ describe "#event" do
9
+ context "with block" do
10
+ context "when the block completes" do
11
+ it "fires *_started and *_completed events" do
12
+ expect(handler).to receive(:event).with(:name_started, :arg)
13
+ expect(handler).to receive(:event).with(:name_completed, :arg)
14
+ subject.event(:name, :arg) { }
15
+ end
16
+ end
17
+
18
+ context "when the block fails" do
19
+ it "fires *_started and *_failed events" do
20
+ expect(handler).to receive(:event).with(:name_started, :arg)
21
+ expect(handler).to receive(:event).with(:name_failed, :arg)
22
+ expect {
23
+ subject.event(:name, :arg) { raise }
24
+ }.to raise_error
25
+ end
26
+ end
27
+ end
28
+
29
+ context "without block" do
30
+ it "fires the event" do
31
+ expect(handler).to receive(:event).with(:name, :arg)
32
+ subject.event(:name, :arg)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ module Itamae
4
+ describe Handler do
5
+ describe ".from_type" do
6
+ it "returns handler class" do
7
+ expect(described_class.from_type('debug')).to eq(Handler::Debug)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ module Itamae
4
+ describe Node do
5
+ let(:backend) { nil }
6
+ describe "#reverse_merge" do
7
+ it "merges a hash but the method receiver's value will be preferred" do
8
+ a = described_class.new({a: :b, c: :d}, backend)
9
+ expected = described_class.new({a: :b, c: :d, e: :f}, backend)
10
+ expect(a.reverse_merge(a: :c, e: :f).mash).to eq(expected.mash)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,6 @@
1
+ require 'spec_helper'
2
+
3
+ module Itamae
4
+ describe Recipe do
5
+ end
6
+ end
@@ -0,0 +1,127 @@
1
+ require 'itamae'
2
+
3
+ class DefineAttributeTestResource < Itamae::Resource::Base
4
+ define_attribute :action, default: :create
5
+ define_attribute :default_attribute, default: :something
6
+ define_attribute :required_attribute, required: true
7
+ define_attribute :typed_attribute, type: Numeric
8
+ define_attribute :default_name_attribute, default_name: true
9
+ end
10
+
11
+ describe DefineAttributeTestResource do
12
+ describe "define_attribute" do
13
+ describe "default" do
14
+ subject do
15
+ described_class.new(double(:recipe), 'resource name') do
16
+ required_attribute :required_value
17
+ end
18
+ end
19
+ it "returns the default value" do
20
+ expect(subject.attributes[:default_attribute]).to eq(:something)
21
+ end
22
+ end
23
+
24
+ describe "falsey" do
25
+ subject do
26
+ described_class.new(double(:recipe), 'resource name') do
27
+ required_attribute :required_value
28
+ default_attribute nil
29
+ end
30
+ end
31
+ it "returns the default value" do
32
+ expect(subject.attributes[:default_attribute]).to eq(nil)
33
+ end
34
+ end
35
+
36
+ describe "required" do
37
+ subject do
38
+ described_class.new(double(:recipe), 'resource name') do
39
+ #required_attribute :required_value
40
+ end
41
+ end
42
+ context "without setting required attribute" do
43
+ it "raises an error" do
44
+ expect do
45
+ subject
46
+ end.to raise_error(Itamae::Resource::AttributeMissingError)
47
+ end
48
+ end
49
+ end
50
+
51
+ describe "type" do
52
+ context "with correct type value" do
53
+ subject do
54
+ described_class.new(double(:recipe), 'resource name') do
55
+ required_attribute :required_value
56
+ typed_attribute 10
57
+ end
58
+ end
59
+ it "returns the value" do
60
+ expect(subject.attributes[:typed_attribute]).to eq(10)
61
+ end
62
+ end
63
+
64
+ context "with incorrect type value" do
65
+ subject do
66
+ described_class.new(double(:recipe), 'resource name') do
67
+ required_attribute :required_value
68
+ typed_attribute "string"
69
+ end
70
+ end
71
+ it "raises an error" do
72
+ expect do
73
+ subject
74
+ end.to raise_error(Itamae::Resource::InvalidTypeError)
75
+ end
76
+ end
77
+ end
78
+
79
+ describe "default_name" do
80
+ context "without setting the value" do
81
+ subject do
82
+ described_class.new(double(:recipe), 'resource name') do
83
+ required_attribute :required_value
84
+ end
85
+ end
86
+ it "returns the resource name" do
87
+ expect(subject.attributes[:default_name_attribute]).
88
+ to eq("resource name")
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ class TestResource < Itamae::Resource::Base
96
+ define_attribute :action, default: :create
97
+ define_attribute :attribute_key, required: false
98
+ end
99
+
100
+ describe TestResource do
101
+ subject(:resource) { described_class.new(recipe, "name") }
102
+
103
+ let(:handler) { Itamae::HandlerProxy.new }
104
+ let(:runner) do
105
+ instance_double(Itamae::Runner).tap do |r|
106
+ allow(r).to receive(:dry_run?).and_return(false)
107
+ allow(r).to receive(:handler).and_return(handler)
108
+ end
109
+ end
110
+ let(:recipe) do
111
+ instance_double(Itamae::Recipe).tap do |r|
112
+ allow(r).to receive(:runner).and_return(runner)
113
+ end
114
+ end
115
+
116
+ describe "#run" do
117
+ before do
118
+ subject.attributes.action = :name
119
+ allow(Itamae::Logger).to receive(:debug) # suppress logger output
120
+ end
121
+
122
+ it "executes <ACTION_NAME>_action method" do
123
+ expect(subject).to receive(:action_name)
124
+ subject.run
125
+ end
126
+ end
127
+ end