itamae-mitsurin 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +7 -0
  5. data/LICENSE.txt +26 -0
  6. data/README.md +44 -0
  7. data/Rakefile +55 -0
  8. data/bin/itamae +5 -0
  9. data/bin/itamae-mitsurin +5 -0
  10. data/itamae-mitsurin.gemspec +35 -0
  11. data/lib/itamae.rb +18 -0
  12. data/lib/itamae/backend.rb +293 -0
  13. data/lib/itamae/cli.rb +86 -0
  14. data/lib/itamae/definition.rb +40 -0
  15. data/lib/itamae/ext.rb +1 -0
  16. data/lib/itamae/ext/specinfra.rb +39 -0
  17. data/lib/itamae/generators.rb +20 -0
  18. data/lib/itamae/generators/cookbook.rb +22 -0
  19. data/lib/itamae/generators/project.rb +22 -0
  20. data/lib/itamae/generators/role.rb +22 -0
  21. data/lib/itamae/generators/templates/cookbook/default.rb +0 -0
  22. data/lib/itamae/generators/templates/cookbook/files/.keep +0 -0
  23. data/lib/itamae/generators/templates/cookbook/templates/.keep +0 -0
  24. data/lib/itamae/generators/templates/project/Gemfile +4 -0
  25. data/lib/itamae/generators/templates/project/cookbooks/.keep +0 -0
  26. data/lib/itamae/generators/templates/project/roles/.keep +0 -0
  27. data/lib/itamae/generators/templates/role/default.rb +0 -0
  28. data/lib/itamae/generators/templates/role/files/.keep +0 -0
  29. data/lib/itamae/generators/templates/role/templates/.keep +0 -0
  30. data/lib/itamae/handler.rb +21 -0
  31. data/lib/itamae/handler/base.rb +40 -0
  32. data/lib/itamae/handler/debug.rb +10 -0
  33. data/lib/itamae/handler/fluentd.rb +44 -0
  34. data/lib/itamae/handler/json.rb +22 -0
  35. data/lib/itamae/handler_proxy.rb +38 -0
  36. data/lib/itamae/logger.rb +124 -0
  37. data/lib/itamae/mitsurin.rb +13 -0
  38. data/lib/itamae/mitsurin/cli.rb +56 -0
  39. data/lib/itamae/mitsurin/creators.rb +19 -0
  40. data/lib/itamae/mitsurin/creators/cookbook.rb +24 -0
  41. data/lib/itamae/mitsurin/creators/project.rb +24 -0
  42. data/lib/itamae/mitsurin/creators/templates/project/.rspec +2 -0
  43. data/lib/itamae/mitsurin/creators/templates/project/Gemfile +3 -0
  44. data/lib/itamae/mitsurin/creators/templates/project/Rakefile +2 -0
  45. data/lib/itamae/mitsurin/creators/templates/project/environments/.keep +0 -0
  46. data/lib/itamae/mitsurin/creators/templates/project/environments/sample.json +7 -0
  47. data/lib/itamae/mitsurin/creators/templates/project/nodes/.keep +0 -0
  48. data/lib/itamae/mitsurin/creators/templates/project/nodes/sample01.json +8 -0
  49. data/lib/itamae/mitsurin/creators/templates/project/roles/.keep +0 -0
  50. data/lib/itamae/mitsurin/creators/templates/project/site-cookbooks/_base/_base/attributes/.keep +0 -0
  51. data/lib/itamae/mitsurin/creators/templates/project/site-cookbooks/_base/_base/files/.keep +0 -0
  52. data/lib/itamae/mitsurin/creators/templates/project/site-cookbooks/_base/_base/recipes/default.rb +0 -0
  53. data/lib/itamae/mitsurin/creators/templates/project/site-cookbooks/_base/_base/spec/default_spec.rb +2 -0
  54. data/lib/itamae/mitsurin/creators/templates/project/site-cookbooks/_base/_base/templates/.keep +0 -0
  55. data/lib/itamae/mitsurin/creators/templates/project/spec/spec_helper.rb +32 -0
  56. data/lib/itamae/mitsurin/creators/templates/project/tmp-nodes/.keep +0 -0
  57. data/lib/itamae/mitsurin/creators/templates/site-cookbooks/attributes/.keep +0 -0
  58. data/lib/itamae/mitsurin/creators/templates/site-cookbooks/files/.keep +0 -0
  59. data/lib/itamae/mitsurin/creators/templates/site-cookbooks/recipes/default.rb +0 -0
  60. data/lib/itamae/mitsurin/creators/templates/site-cookbooks/spec/.keep +0 -0
  61. data/lib/itamae/mitsurin/creators/templates/site-cookbooks/templates/.keep +0 -0
  62. data/lib/itamae/mitsurin/itamae_task.rb +199 -0
  63. data/lib/itamae/mitsurin/serverspec_task.rb +110 -0
  64. data/lib/itamae/mitsurin/version.rb +5 -0
  65. data/lib/itamae/mitsurin/version.txt +1 -0
  66. data/lib/itamae/node.rb +74 -0
  67. data/lib/itamae/notification.rb +46 -0
  68. data/lib/itamae/recipe.rb +171 -0
  69. data/lib/itamae/recipe_children.rb +86 -0
  70. data/lib/itamae/resource.rb +73 -0
  71. data/lib/itamae/resource/aws_ebs_volume.rb +84 -0
  72. data/lib/itamae/resource/base.rb +374 -0
  73. data/lib/itamae/resource/directory.rb +63 -0
  74. data/lib/itamae/resource/execute.rb +26 -0
  75. data/lib/itamae/resource/file.rb +176 -0
  76. data/lib/itamae/resource/gem_package.rb +81 -0
  77. data/lib/itamae/resource/git.rb +94 -0
  78. data/lib/itamae/resource/group.rb +42 -0
  79. data/lib/itamae/resource/http_request.rb +71 -0
  80. data/lib/itamae/resource/link.rb +33 -0
  81. data/lib/itamae/resource/local_ruby_block.rb +15 -0
  82. data/lib/itamae/resource/package.rb +44 -0
  83. data/lib/itamae/resource/remote_directory.rb +84 -0
  84. data/lib/itamae/resource/remote_file.rb +54 -0
  85. data/lib/itamae/resource/service.rb +69 -0
  86. data/lib/itamae/resource/template.rb +53 -0
  87. data/lib/itamae/resource/user.rb +93 -0
  88. data/lib/itamae/runner.rb +124 -0
  89. data/spec/integration/Vagrantfile +35 -0
  90. data/spec/integration/default_spec.rb +226 -0
  91. data/spec/integration/recipes/default.rb +423 -0
  92. data/spec/integration/recipes/default2.rb +6 -0
  93. data/spec/integration/recipes/define/default.rb +6 -0
  94. data/spec/integration/recipes/define/files/remote_file_in_definition +1 -0
  95. data/spec/integration/recipes/dry_run.rb +6 -0
  96. data/spec/integration/recipes/files/remote_file_auto +1 -0
  97. data/spec/integration/recipes/hello.erb +6 -0
  98. data/spec/integration/recipes/hello.txt +1 -0
  99. data/spec/integration/recipes/included.rb +9 -0
  100. data/spec/integration/recipes/node.json +3 -0
  101. data/spec/integration/recipes/redefine.rb +20 -0
  102. data/spec/integration/recipes/templates/template_auto.erb +6 -0
  103. data/spec/integration/spec_helper.rb +42 -0
  104. data/spec/unit/lib/itamae/backend_spec.rb +95 -0
  105. data/spec/unit/lib/itamae/handler/base_spec.rb +34 -0
  106. data/spec/unit/lib/itamae/handler/fluentd_spec.rb +19 -0
  107. data/spec/unit/lib/itamae/handler_proxy_spec.rb +38 -0
  108. data/spec/unit/lib/itamae/handler_spec.rb +11 -0
  109. data/spec/unit/lib/itamae/node_spec.rb +14 -0
  110. data/spec/unit/lib/itamae/recipe_spec.rb +6 -0
  111. data/spec/unit/lib/itamae/resource/base_spec.rb +127 -0
  112. data/spec/unit/lib/itamae/resource_spec.rb +23 -0
  113. data/spec/unit/lib/itamae/runner_spec.rb +32 -0
  114. data/spec/unit/spec_helper.rb +23 -0
  115. metadata +393 -0
@@ -0,0 +1,6 @@
1
+ file "put file in default2.rb" do
2
+ action :nothing
3
+ path "/tmp/created_in_default2"
4
+ content 'Hello'
5
+ end
6
+
@@ -0,0 +1,6 @@
1
+ define :definition_example, key: 'default' do
2
+ execute "echo 'name:#{params[:name]},key:#{params[:key]},message:#{node[:message]}' > /tmp/created_by_definition"
3
+
4
+ remote_file "/tmp/remote_file_in_definition"
5
+ end
6
+
@@ -0,0 +1,6 @@
1
+ file "/tmp/it_does_not_exist" do
2
+ action :edit
3
+ block do |content|
4
+ content.gsub!("foo", "bar")
5
+ end
6
+ end
@@ -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