itamae-1.12.5/0000755000004100000410000000000014167043240013071 5ustar www-datawww-dataitamae-1.12.5/.rspec0000644000004100000410000000001414167043240014201 0ustar www-datawww-data--color -fd itamae-1.12.5/README.md0000644000004100000410000000600114167043240014345 0ustar www-datawww-data# [![](https://raw.githubusercontent.com/itamae-kitchen/itamae-logos/master/small/FA-Itamae-horizontal-01-180x72.png)](https://github.com/itamae-kitchen/itamae) [![Gem Version](https://badge.fury.io/rb/itamae.svg)](http://badge.fury.io/rb/itamae) [![Code Climate](https://codeclimate.com/github/ryotarai/itamae/badges/gpa.svg)](https://codeclimate.com/github/ryotarai/itamae) [![Build Status](https://github.com/itamae-kitchen/itamae/workflows/test/badge.svg?branch=master)](https://github.com/itamae-kitchen/itamae/actions?query=workflow%3Atest) [![Slack](https://img.shields.io/badge/slack-join-blue.svg)](https://join.slack.com/t/itamae/shared_invite/enQtNTExNTI3ODM1NTY5LTM5MWJlZTgwODE0YTUwMThiNzZjN2I1MGNlZjE2NjlmNzg5NTNlOTliMDhkNDNmNTQ2ZTgwMzZjNjI5NDJiZGI) Simple and lightweight configuration management tool inspired by Chef. - [CHANGELOG](https://github.com/itamae-kitchen/itamae/blob/master/CHANGELOG.md) ## Concept - Chef-like DSL (but not compatible with Chef) - Simpler and lighter weight than Chef - Only recipes - Idempotent ## Installation ``` $ gem install itamae ``` ## Getting Started Create a recipe file as `recipe.rb`: ```ruby package 'nginx' do action :install end service 'nginx' do action [:enable, :start] end ``` And then excute `itamae` command to apply a recipe to a local machine. ``` $ itamae local recipe.rb INFO : Starting Itamae... INFO : Recipe: /home/user/recipe.rb INFO : package[nginx] INFO : action: install INFO : installed will change from 'false' to 'true' INFO : service[nginx] INFO : action: enable INFO : action: start ``` Or you can apply a recipe to a remote machine by `itamae ssh`. ``` $ itamae ssh --host host001.example.jp recipe.rb ``` You can also apply a recipe to Vagrant VM by `itamae ssh --vagrant`. ``` $ itamae ssh --vagrant --host vm_name recipe.rb ``` You can find further information to use Itamae on [Itamae Wiki](https://github.com/itamae-kitchen/itamae/wiki). Enjoy! ## Documentation https://github.com/itamae-kitchen/itamae/wiki ## Run tests Requirements: Vagrant ``` $ bundle exec rake spec ``` ## Get Involved - [Join Slack team](https://itamae-slackin.herokuapp.com) ## Presentations / Articles ### in Japanese - [Itamae - Infra as Code 現状確認会](https://speakerdeck.com/ryotarai/itamae-infra-as-code-xian-zhuang-que-ren-hui) - [クックパッドのサーバプロビジョニング事情 - クックパッド開発者ブログ](http://techlife.cookpad.com/entry/2015/05/12/080000) - [Itamaeが構成管理を仕込みます! ~新進気鋭の国産・構成管理ツール~:連載|gihyo.jp … 技術評論社](http://gihyo.jp/admin/serial/01/itamae) ## Contributing If you have a problem, please [create an issue](https://github.com/itamae-kitchen/itamae/issues/new) or a pull request. 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request itamae-1.12.5/tasks/0000755000004100000410000000000014167043240014216 5ustar www-datawww-dataitamae-1.12.5/tasks/integration_local_spec.rb0000644000004100000410000000660514167043240021261 0ustar www-datawww-datadesc 'Run all integration tests on `itamae local` command' task 'spec:integration:local' => ['spec:integration:local:main', 'spec:integration:local:ordinary_user'] namespace 'spec:integration:local' do desc 'Run main integration test with `itamae local`' task 'main' do if RUBY_DESCRIPTION.include?('dev') $stderr.puts "This integration test is skipped with unreleased Ruby." $stderr.puts "Use released Ruby to execute this integration test." next end IntegrationLocalSpecRunner.new( [ [ "spec/integration/recipes/default.rb", "spec/integration/recipes/default2.rb", "spec/integration/recipes/redefine.rb", "spec/integration/recipes/local.rb", ], [ "--dry-run", "spec/integration/recipes/dry_run.rb", ], ], ['spec/integration/default_spec.rb'] ).run end desc 'Run integration test for ordinary user with `itamae local`' task 'ordinary_user' do if RUBY_DESCRIPTION.include?('dev') $stderr.puts "This integration test is skipped with unreleased Ruby." $stderr.puts "Use released Ruby to execute this integration test." next end runner = IntegrationLocalSpecRunner.new( [ [ "--dry-run", "spec/integration/recipes/ordinary_user.rb", ], [ "spec/integration/recipes/ordinary_user.rb" ], ], ['spec/integration/ordinary_user_spec.rb'], user: 'ordinary_san' ) runner.docker_exec 'useradd', 'ordinary_san', '-p', '*' runner.docker_exec 'useradd', 'itamae', '-p', '*', '--create-home' runner.docker_exec 'sh', '-c', 'echo "ordinary_san ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers' runner.run end end class IntegrationLocalSpecRunner CONTAINER_NAME = 'itamae' include FileUtils def initialize(suites, specs, ruby_version: RUBY_VERSION.split('.')[0..1].join('.'), user: nil) @suites = suites @specs = specs @ruby_version = ruby_version @user = user docker_run prepare end def run provision serverspec clean_docker_container end def docker_run mount_dir = Pathname(__dir__).join('../').to_s sh 'docker', 'run', '--privileged', '-d', '--name', CONTAINER_NAME, '-v', "#{mount_dir}:/itamae", "ruby:#{@ruby_version}", 'sleep', '1d' end def prepare docker_exec 'gem', 'install', 'bundler' docker_exec 'bundle', 'install', options: %w[--workdir /itamae] docker_exec 'apt-get', 'update', '-y' docker_exec 'apt-get', 'install', 'locales', 'sudo', '-y' docker_exec 'localedef', '-i', 'en_US', '-c', '-f', 'UTF-8', '-A', '/usr/share/locale/locale.alias', 'en_US.UTF-8' end def provision @suites.each do |suite| cmd = %W!bundle exec ruby -w bin/itamae local! cmd << "-l" << (ENV['LOG_LEVEL'] || 'debug') cmd << "-j" << "spec/integration/recipes/node.json" cmd += suite options = %w[--workdir /itamae] options.push('--user', @user) if @user docker_exec(*cmd, options: options) end end def serverspec ENV['DOCKER_CONTAINER'] = CONTAINER_NAME sh('bundle', 'exec', 'rspec', '-I', './spec/integration', *@specs) end def clean_docker_container sh('docker', 'rm', '-f', CONTAINER_NAME) end def docker_exec(*cmd, options: []) sh 'docker', 'exec', '--env', 'LANG=en_US.utf8', *options, CONTAINER_NAME, *cmd end end itamae-1.12.5/bin/0000755000004100000410000000000014167043240013641 5ustar www-datawww-dataitamae-1.12.5/bin/itamae0000755000004100000410000000007414167043240015030 0ustar www-datawww-data#!/usr/bin/env ruby require 'itamae/cli' Itamae::CLI.start itamae-1.12.5/spec/0000755000004100000410000000000014167043240014023 5ustar www-datawww-dataitamae-1.12.5/spec/unit/0000755000004100000410000000000014167043240015002 5ustar www-datawww-dataitamae-1.12.5/spec/unit/lib/0000755000004100000410000000000014167043240015550 5ustar www-datawww-dataitamae-1.12.5/spec/unit/lib/itamae/0000755000004100000410000000000014167043240017010 5ustar www-datawww-dataitamae-1.12.5/spec/unit/lib/itamae/node_spec.rb0000644000004100000410000000064514167043240021301 0ustar www-datawww-datarequire 'spec_helper' module Itamae describe Node do let(:backend) { nil } describe "#reverse_merge" do it "merges a hash but the method receiver's value will be preferred" do a = described_class.new({a: :b, c: :d}, backend) expected = described_class.new({a: :b, c: :d, e: :f}, backend) expect(a.reverse_merge(a: :c, e: :f).mash).to eq(expected.mash) end end end end itamae-1.12.5/spec/unit/lib/itamae/runner_spec.rb0000644000004100000410000000137414167043240021665 0ustar www-datawww-datarequire 'spec_helper' require 'tmpdir' module Itamae describe Runner do subject { described_class.new(double(:node)) } around do |example| Dir.mktmpdir do |dir| Dir.chdir(dir) do example.run end end end describe ".run" do let(:recipes) { %w! ./recipe1.rb ./recipe2.rb ! } it "runs each recipe with the runner" do pending "Rewrite later" recipes.each do |r| recipe = double(:recipe) allow(Recipe).to receive(:new).with( an_instance_of(Itamae::Runner), File.expand_path(r) ).and_return(recipe) expect(recipe).to receive(:run) end described_class.run(recipes, :local, {}) end end end end itamae-1.12.5/spec/unit/lib/itamae/handler/0000755000004100000410000000000014167043240020425 5ustar www-datawww-dataitamae-1.12.5/spec/unit/lib/itamae/handler/base_spec.rb0000644000004100000410000000144714167043240022704 0ustar www-datawww-datarequire 'spec_helper' describe Itamae::Handler::Base do subject(:handler) { described_class.new({}) } context "when receiving recipe_started event" do it "stores the payload" do subject.event(:recipe_started, :payload) expect(subject.recipes).to eq([:payload]) end end context "when receiving recipe_completed event" do before do subject.event(:recipe_started, :payload) end it "pops the payload" do subject.event(:recipe_completed, :payload) expect(subject.recipes).to eq([]) end end context "when receiving recipe_failed event" do before do subject.event(:recipe_started, :payload) end it "pops the payload" do subject.event(:recipe_failed, :payload) expect(subject.recipes).to eq([]) end end end itamae-1.12.5/spec/unit/lib/itamae/handler/fluentd_spec.rb0000644000004100000410000000100114167043240023415 0ustar www-datawww-datarequire 'spec_helper' require 'itamae/handler/fluentd' describe Itamae::Handler::Fluentd do subject(:handler) do described_class.new(options).tap do |h| h.fluent_logger = fluent_logger end end let(:options) { {'hostname' => 'me'} } let(:fluent_logger) { Fluent::Logger::TestLogger.new } describe '#event' do it 'posts a record to fluent logger' do subject.event(:type, {arg: 'value'}) expect(fluent_logger.queue).to eq([{arg: 'value', hostname: 'me'}]) end end end itamae-1.12.5/spec/unit/lib/itamae/backend_spec.rb0000644000004100000410000000711414167043240021741 0ustar www-datawww-datarequire 'spec_helper' require 'fakefs/spec_helpers' module Itamae module Backend describe Base do include FakeFS::SpecHelpers class Klass < Itamae::Backend::Base def initialize(_, backend) @backend = backend end end let(:backend) { double('backend', send_file: nil, send_directory: nil) } let(:itamae_backend) { Klass.new('dummy', backend) } describe ".send_file" do context "the source file doesn't exist" do subject { itamae_backend.send_file("src", "dst") } it { expect{ subject }.to raise_error(Itamae::Backend::SourceNotExistError, "The file 'src' doesn't exist.") } end context "the source file exist, but it is not a regular file" do before { Dir.mkdir("src") } subject { itamae_backend.send_file("src", "dst") } it { expect{ subject }.to raise_error(Itamae::Backend::SourceNotExistError, "'src' is not a file.") } end context "the source file is a regular file" do before { FileUtils.touch("src") } subject { itamae_backend.send_file("src", "dst") } it { expect { subject }.not_to raise_error } end end describe ".send_directory" do context "the source directory doesn't exist" do subject { itamae_backend.send_directory("src", "dst") } it { expect{ subject }.to raise_error(Itamae::Backend::SourceNotExistError, "The directory 'src' doesn't exist.") } end context "the source directory exist, but it is not a directory" do before { FileUtils.touch("src") } subject { itamae_backend.send_directory("src", "dst") } it { expect{ subject }.to raise_error(Itamae::Backend::SourceNotExistError, "'src' is not a directory.") } end context "the source directory is a directory" do before { Dir.mkdir("src") } subject { itamae_backend.send_directory("src", "dst") } it { expect { subject }.not_to raise_error } end end end describe Ssh do describe "#ssh_options" do subject { ssh.send(:ssh_options) } let!(:ssh) { described_class.new(options) } let!(:host_name) { "example.com" } let!(:default_option) do opts = {} opts[:host_name] = nil opts.merge!(Net::SSH::Config.for(host_name)) opts[:user] = opts[:user] || Etc.getlogin opts end context "with host option" do let(:options) { {host: host_name} } it { is_expected.to eq( default_option.merge({host_name: host_name}) ) } end context "with ssh_config option" do around do |example| Tempfile.create("ssh_config") do |temp| temp.write(<_action method" do expect(subject).to receive(:action_name) subject.run end end end itamae-1.12.5/spec/unit/lib/itamae/handler_proxy_spec.rb0000644000004100000410000000221314167043240023223 0ustar www-datawww-datarequire 'spec_helper' module Itamae describe HandlerProxy do let(:handler) { instance_double(Handler::Base) } before { subject.register_instance(handler) } describe "#event" do context "with block" do context "when the block completes" do it "fires *_started and *_completed events" do expect(handler).to receive(:event).with(:name_started, :arg) expect(handler).to receive(:event).with(:name_completed, :arg) subject.event(:name, :arg) { } end end context "when the block fails" do it "fires *_started and *_failed events" do expect(handler).to receive(:event).with(:name_started, :arg) expect(handler).to receive(:event).with(:name_failed, :arg) expect { subject.event(:name, :arg) { raise "name is failed" } }.to raise_error "name is failed" end end end context "without block" do it "fires the event" do expect(handler).to receive(:event).with(:name, :arg) subject.event(:name, :arg) end end end end end itamae-1.12.5/spec/unit/lib/itamae/recipe_spec.rb0000644000004100000410000000010414167043240021611 0ustar www-datawww-datarequire 'spec_helper' module Itamae describe Recipe do end end itamae-1.12.5/spec/unit/lib/itamae/logger_spec.rb0000644000004100000410000000213514167043240021627 0ustar www-datawww-datarequire 'spec_helper' module Itamae describe Logger do describe "#debug" do context "`msg` is a String" do it "indents the message" do expect_any_instance_of(::Logger).to receive(:debug).with(" msg") Itamae.logger.with_indent do Itamae.logger.debug("msg") end end end context "`msg` is an Exception" do let(:msg) { ::Exception.new("error") } before do allow(msg).to receive(:backtrace) { %w!frame1 frame2! } end it "indents the error message and the backtrace" do expect_any_instance_of(::Logger).to receive(:debug).with(<<-MSG.rstrip) error (Exception) frame1 frame2 MSG Itamae.logger.with_indent do Itamae.logger.debug(msg) end end end context "`msg` is an Array" do it "indents the message" do expect_any_instance_of(::Logger).to receive(:debug).with(" []") Itamae.logger.with_indent do Itamae.logger.debug([]) end end end end end end itamae-1.12.5/spec/unit/spec_helper.rb0000644000004100000410000000150314167043240017617 0ustar www-datawww-datarequire 'itamae' # This file was generated by the `rspec --init` command. Conventionally, all # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. # Require this file using `require "spec_helper"` to ensure that it is only # loaded once. # # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration RSpec.configure do |config| config.run_all_when_everything_filtered = true config.filter_run :focus # Run specs in random order to surface order dependencies. If you find an # order dependency and want to debug it, you can fix the order by providing # the seed, which is printed after each run. # --seed 1234 config.order = 'random' config.raise_errors_for_deprecations! end Itamae.logger = ::Logger.new(StringIO.new) Specinfra.configuration.error_on_missing_backend_type = false itamae-1.12.5/spec/integration/0000755000004100000410000000000014167043240016346 5ustar www-datawww-dataitamae-1.12.5/spec/integration/recipes/0000755000004100000410000000000014167043240020000 5ustar www-datawww-dataitamae-1.12.5/spec/integration/recipes/docker.rb0000644000004100000410000000123214167043240021572 0ustar www-datawww-datapackage 'sl' do version '3.03-17' end ###### gem_package 'ast' do version '2.0.0' options ['--no-ri', '--no-rdoc'] end ###### service "cron" do action :stop end execute "ps -C cron > /tmp/cron_stopped; true" service "cron" do action :start end execute "ps -C cron > /tmp/cron_running; true" ###### package "nginx" do options "--force-yes" end service "nginx" do action [:enable, :start] end execute "test -f /etc/rc3.d/S20nginx" # test execute "test $(ps h -C nginx | wc -l) -gt 0" # test service "nginx" do action [:disable, :stop] end execute "test ! -f /etc/rc3.d/S20nginx" # test execute "test $(ps h -C nginx | wc -l) -eq 0" # test itamae-1.12.5/spec/integration/recipes/default.rb0000644000004100000410000002414314167043240021755 0ustar www-datawww-datanode.reverse_merge!({ message: "Hello, Itamae" }) execute 'apt-get update' execute 'deluser --remove-home itamae2' do only_if "id itamae2" end include_recipe "./included.rb" include_recipe "./included.rb" # including the same recipe is expected to be skipped. user "create itamae user" do uid 123 username "itamae" password "$1$ltOY8bZv$iZ57f1KAp8jwKViNm3pze." home '/home/foo' shell '/bin/sh' end user "update itamae user" do uid 1234 username "itamae" password "$1$TQz9gPMl$nHYrsA5W2ZdZ0Yn021BQH1" home '/home/itamae' shell '/bin/dash' end directory "/home/itamae" do mode "755" owner "itamae" group "itamae" end user "create itamae2 user with create home directory" do username "itamae2" create_home true home "/home/itamae2" shell "/bin/sh" end ###### package 'dstat' do action :install end package 'resolvconf' do action :remove end ###### package "ruby" gem_package 'tzinfo' do version '1.1.0' end gem_package 'tzinfo' do version '1.2.2' end gem_package 'rake' do version '11.1.0' end gem_package 'rake' do version '11.2.2' end gem_package 'rake' do action :uninstall version '11.2.2' end gem_package 'test-unit' do version '2.5.5' end gem_package 'test-unit' do version '2.4.9' end gem_package 'test-unit' do action :uninstall end ###### execute "echo -n > /tmp/notifies" execute "echo -n 1 >> /tmp/notifies" do action :nothing end execute "echo -n 2 >> /tmp/notifies" do notifies :run, "execute[echo -n 1 >> /tmp/notifies]" end execute "echo -n 3 >> /tmp/notifies" do action :nothing end execute "echo -n 4 >> /tmp/notifies" do notifies :run, "execute[echo -n 3 >> /tmp/notifies]", :immediately end ###### execute "echo -n > /tmp/subscribes" execute "echo -n 1 >> /tmp/subscribes" do action :nothing subscribes :run, "execute[echo -n 2 >> /tmp/subscribes]" end execute "echo -n 2 >> /tmp/subscribes" execute "echo -n 3 >> /tmp/subscribes" do action :nothing subscribes :run, "execute[echo -n 4 >> /tmp/subscribes]", :immediately end execute "echo -n 4 >> /tmp/subscribes" ###### remote_file "/tmp/remote_file" do source "hello.txt" end remote_file "/tmp/remote_file_auto" do source :auto end ###### directory "/tmp/directory" do mode "700" owner "itamae" group "itamae" end directory "/tmp/directory_never_exist1" do action :create end directory "/tmp/directory_never_exist1" do action :delete end template "/tmp/template" do source "hello.erb" variables goodbye: "Good bye" end template "/tmp/template_auto" do source :auto variables goodbye: "Good bye" end file "/tmp/file" do content "Hello World" mode "777" end file "/tmp/file_with_suid" do content "Hello World" mode "4755" owner "itamae" group "itamae" end execute "echo 'Hello Execute' > /tmp/execute" file "/tmp/never_exist1" do only_if "exit 1" end file "/tmp/never_exist2" do not_if "exit 0" end ###### http_request "/tmp/http_request.html" do url "https://httpbin.org/get?from=itamae" end http_request "/tmp/http_request_delete.html" do action :delete url "https://httpbin.org/delete?from=itamae" end http_request "/tmp/http_request_post.html" do action :post message "love=sushi" url "https://httpbin.org/post?from=itamae" end http_request "/tmp/http_request_put.html" do action :put message "love=sushi" url "https://httpbin.org/put?from=itamae" end http_request "/tmp/http_request_headers.html" do headers "User-Agent" => "Itamae" url "https://httpbin.org/get" end # http_request "/tmp/http_request_redirect.html" do # redirect_limit 1 # url "https://httpbin.org/redirect-to?url=https%3A%2F%2Fhttpbin.org%2Fget%3Ffrom%3Ditamae" # end link "/tmp-link" do to "/tmp" end execute "touch /tmp-link-force" link "/tmp-link-force" do to "/tmp" force true end ###### execute "mkdir -p /tmp/link-force-no-dereference1" link "link-force-no-dereference" do cwd "/tmp" to "link-force-no-dereference1" force true end execute "mkdir -p /tmp/link-force-no-dereference2" link "link-force-no-dereference" do cwd "/tmp" to "link-force-no-dereference2" force true end ##### local_ruby_block "greeting" do block do Itamae.logger.info "板前" end end ##### package "git" git "/tmp/git_repo" do repository "https://github.com/ryotarai/infrataster.git" revision "v0.1.0" end git "/tmp/git_repo_submodule" do repository "https://github.com/mmasaki/fake_repo_including_submodule.git" recursive true end git "/tmp/git_repo_depth_1" do repository "https://github.com/ryotarai/infrataster.git" depth 1 end ##### execute "echo -n \"$HOME\n$(pwd)\" > /tmp/created_by_itamae_user" do user "itamae" end ##### execute "echo 'notify to resource in default2.rb'" do notifies :create, "file[put file in default2.rb]" end ##### file "/tmp/never_exist3" do action :create end file "/tmp/never_exist3" do action :delete end ##### include_recipe "define/default.rb" definition_example "name" do key 'value' end execute "touch /tmp/trigger_for_definition_example_2" definition_example_2 "created" do key "value2" only_if "test -f /tmp/trigger_for_definition_example_2" end definition_example_2 "not_created" do key "value2" not_if "test -f /tmp/trigger_for_definition_example_2" end definition_example_3 "created" do key "value3" not_if "test -f /tmp/this_file_is_not_exists" end definition_example_3 "not_created" do key "value3" only_if "test -f /tmp/this_file_is_not_exists" end ##### file "/tmp/never_exist4" do action :nothing end file "/tmp/file1" do content "Hello, World" end file "/tmp/file1" do content "Hello, World" notifies :create, "file[/tmp/never_exist4]" end ##### file "/tmp/file_create_without_content" do content "Hello, World" end file "/tmp/file_create_without_content" do owner "itamae" group "itamae" mode "600" end ##### execute 'true' do verify 'true' end ##### execute 'echo 1 > /tmp/multi_delayed_notifies' do notifies :run, "execute[echo 2 >> /tmp/multi_delayed_notifies]" end execute 'echo 2 >> /tmp/multi_delayed_notifies' do action :nothing notifies :run, "execute[echo 3 >> /tmp/multi_delayed_notifies]" end execute 'echo 3 >> /tmp/multi_delayed_notifies' do action :nothing notifies :run, "execute[echo 4 >> /tmp/multi_delayed_notifies]" end execute 'echo 4 >> /tmp/multi_delayed_notifies' do action :nothing end ##### execute 'echo 1 > /tmp/multi_immediately_notifies' do notifies :run, "execute[echo 2 >> /tmp/multi_immediately_notifies]", :immediately end execute 'echo 2 >> /tmp/multi_immediately_notifies' do action :nothing notifies :run, "execute[echo 3 >> /tmp/multi_immediately_notifies]", :immediately end execute 'echo 3 >> /tmp/multi_immediately_notifies' do action :nothing notifies :run, "execute[echo 4 >> /tmp/multi_immediately_notifies]", :immediately end execute 'echo 4 >> /tmp/multi_immediately_notifies' do action :nothing end ##### execute 'echo -n 1 > /tmp/file_edit_notifies' do action :nothing end file '/tmp/file_edit_sample' do content 'Hello, world' owner 'itamae' group 'itamae' mode '444' end file '/tmp/file_edit_sample' do action :edit owner 'itamae2' group 'itamae2' mode '400' block do |content| content.gsub!('world', 'Itamae') end notifies :run, "execute[echo -n 1 > /tmp/file_edit_notifies]" end file '/tmp/file_edit_with_suid' do content 'Hello, world' owner 'itamae' group 'itamae' mode '600' end file '/tmp/file_edit_with_suid' do action :edit owner 'itamae2' group 'itamae2' mode '4755' end file '/tmp/file_edit_keeping_mode_owner' do content 'Hello, world' owner 'itamae' group 'itamae' mode '444' end file '/tmp/file_edit_keeping_mode_owner' do action :edit block do |content| content.gsub!('world', 'Itamae') end end ### execute "f=/tmp/file_edit_with_content_change_updates_timestamp && echo 'Hello, world' > $f && touch -d 2016-05-02T01:23:45Z $f" file "/tmp/file_edit_with_content_change_updates_timestamp" do action :edit block do |content| content.gsub!('world', 'Itamae') end end ### execute "touch -d 2016-05-02T12:34:56Z /tmp/file_edit_without_content_change_keeping_timestamp" file "/tmp/file_edit_without_content_change_keeping_timestamp" do action :edit block do |content| # no change end end ### file '/tmp/file_without_content_change_updates_mode_and_owner' do action :create content 'Hello, world' owner 'itamae' group 'itamae' mode '444' end file '/tmp/file_without_content_change_updates_mode_and_owner' do action :create content 'Hello, world' # no change owner 'itamae2' group 'itamae2' mode '666' end ### execute "touch -d 2016-05-01T01:23:45Z /tmp/file_with_content_change_updates_timestamp" file "/tmp/file_with_content_change_updates_timestamp" do content "Hello, world" end ### execute "f=/tmp/file_without_content_change_keeping_timestamp && echo 'Hello, world' > $f && touch -d 2016-05-01T12:34:56Z $f" file "/tmp/file_without_content_change_keeping_timestamp" do content "Hello, world\n" end ### unless run_command("echo -n Hello").stdout == "Hello" raise "run_command in a recipe failed" end define :run_command_in_definition do unless run_command("echo -n Hello").stdout == "Hello" raise "run_command in a definition failed" end end execute "echo Hello" do unless run_command("echo -n Hello").stdout == "Hello" raise "run_command in a resource failed" end end local_ruby_block 'execute run_command' do block do unless run_command("echo -n Hello").stdout == "Hello" raise "run_command in local_ruby_block failed" end end end execute "touch /tmp/subscribed_from_parent" do action :nothing subscribes :run, 'execute[subscribed from parent]' end ### file "/tmp/empty_file1" do content "" end remote_file "/tmp/empty_file2" do source "files/empty_file" end template "/tmp/empty_file3" do source "templates/empty_file.erb" end ### v1 = node.memory.total v2 = node[:memory][:total] v3 = node['memory']['total'] unless v1 == v2 && v2 == v3 && v1 =~ /\A\d+kB\z/ raise "failed to fetch host inventory value (#{v1}, #{v2}, #{v3})" end include_recipe "toplevel_module" file "/tmp/toplevel_module" do content ToplevelModule.helper end include_recipe "variables" itamae-1.12.5/spec/integration/recipes/redefine.rb0000644000004100000410000000065014167043240022107 0ustar www-datawww-data# spec/integration/recipes/redefine.rb define :echo_hello, version: nil do file "put file in redefine.rb" do action :create path "/tmp/created_in_redefine" content 'first' end end # Duplicated definitions define :echo_hello, version: nil do file "put file in redefine.rb" do action :create path "/tmp/created_in_redefine" content 'second' end end # Execute echo_hello "execute echo_hello!" itamae-1.12.5/spec/integration/recipes/templates/0000755000004100000410000000000014167043240021776 5ustar www-datawww-dataitamae-1.12.5/spec/integration/recipes/templates/empty_file.erb0000644000004100000410000000000014167043240024613 0ustar www-datawww-dataitamae-1.12.5/spec/integration/recipes/templates/template_auto.erb0000644000004100000410000000021514167043240025331 0ustar www-datawww-data<%= node['greeting'] %> <%= @goodbye %> total memory: <%= node['memory']['total'] %> uninitialized node key: <%= node['un-initialized'] %> itamae-1.12.5/spec/integration/recipes/local.rb0000644000004100000410000000057614167043240021427 0ustar www-datawww-datapackage 'sl' ###### gem_package 'ast' do version '2.0.0' options ['--no-document'] end ###### # Docker backend raises an error with `user` option, so it tests only on `itamae local`. # After fix this error, please move this code and the spec to `default.rb`. file "/tmp/file_as_ordinary_user" do content "Hello World" user "itamae" owner "itamae" group "itamae" end itamae-1.12.5/spec/integration/recipes/included.rb0000644000004100000410000000053114167043240022113 0ustar www-datawww-datarequire 'pathname' included_flag_file = Pathname.new("/tmp/included_rb_is_included") if included_flag_file.exist? && included_flag_file.read == $$.to_s raise "included.rb should not be included twice." else included_flag_file.write($$.to_s) end execute "touch /tmp/included_recipe" execute "subscribed from parent" do command "true" end itamae-1.12.5/spec/integration/recipes/toplevel_module.rb0000644000004100000410000000017314167043240023525 0ustar www-datawww-data# Testing you don't need to write `module ::ToplevelModule` module ToplevelModule def self.helper "helper" end end itamae-1.12.5/spec/integration/recipes/define/0000755000004100000410000000000014167043240021232 5ustar www-datawww-dataitamae-1.12.5/spec/integration/recipes/define/default.rb0000644000004100000410000000150414167043240023203 0ustar www-datawww-datadefine :definition_example, key: 'default' do execute "echo 'name:#{params[:name]},key:#{params[:key]},message:#{node[:message]}' > /tmp/created_by_definition" remote_file "/tmp/remote_file_in_definition" end define :definition_example_2, key: 'default' do execute "echo 'name:#{params[:name]},key:#{params[:key]},message:#{node[:message]}' > /tmp/created_by_definition_2_#{params[:name]}" remote_file "/tmp/remote_file_in_definition_2_#{params[:name]}" do source "files/remote_file_in_definition_2" end end define :definition_example_3, key: 'default' do execute "echo 'name:#{params[:name]},key:#{params[:key]},message:#{node[:message]}' > /tmp/created_by_definition_3_#{params[:name]}" remote_file "/tmp/remote_file_in_definition_3_#{params[:name]}" do source "files/remote_file_in_definition_3" end end itamae-1.12.5/spec/integration/recipes/define/files/0000755000004100000410000000000014167043240022334 5ustar www-datawww-dataitamae-1.12.5/spec/integration/recipes/define/files/remote_file_in_definition_30000644000004100000410000000002514167043240027666 0ustar www-datawww-datadefinition_example_3 itamae-1.12.5/spec/integration/recipes/define/files/remote_file_in_definition0000644000004100000410000000002314167043240027442 0ustar www-datawww-datadefinition_example itamae-1.12.5/spec/integration/recipes/define/files/remote_file_in_definition_20000644000004100000410000000002514167043240027665 0ustar www-datawww-datadefinition_example_2 itamae-1.12.5/spec/integration/recipes/ordinary_user.rb0000644000004100000410000000357614167043240023225 0ustar www-datawww-dataremote_file "/tmp/remote_file" do source "hello.txt" end remote_file "/tmp/remote_file_root" do user 'root' owner 'root' group 'root' source "hello.txt" end remote_file "/tmp/remote_file_another_ordinary" do user 'itamae' owner 'itamae' group 'itamae' source "hello.txt" end remote_file "/tmp/remote_file_another_ordinary_with_root" do user 'root' owner 'itamae' group 'itamae' source "hello.txt" end ### file "/tmp/file" do content "Hello World" end file "/tmp/file_root" do user 'root' owner 'root' group 'root' content 'Hello World' end file "/tmp/file_another_ordinary" do user 'itamae' owner 'itamae' group 'itamae' content 'Hello World' end file "/tmp/file_another_ordinary_with_root" do user 'root' owner 'itamae' group 'itamae' content 'Hello World' end ### template "/tmp/template" do source "hello.erb" variables goodbye: "Good bye" end template "/tmp/template_root" do user 'root' owner 'root' group 'root' source "hello.erb" variables goodbye: "Good bye" end template "/tmp/template_another_ordinary" do user 'itamae' owner 'itamae' group 'itamae' source "hello.erb" variables goodbye: "Good bye" end template "/tmp/template_another_ordinary_with_root" do user 'root' owner 'itamae' group 'itamae' source "hello.erb" variables goodbye: "Good bye" end ### http_request "/tmp/http_request.html" do url "https://httpbin.org/get?from=itamae" end http_request "/tmp/http_request_root.html" do user 'root' owner 'root' group 'root' url "https://httpbin.org/get?from=itamae" end http_request "/tmp/http_request_another_ordinary.html" do user 'itamae' owner 'itamae' group 'itamae' url "https://httpbin.org/get?from=itamae" end http_request "/tmp/http_request_another_ordinary_with_root.html" do user 'root' owner 'itamae' group 'itamae' url "https://httpbin.org/get?from=itamae" end itamae-1.12.5/spec/integration/recipes/hello.erb0000644000004100000410000000021514167043240021573 0ustar www-datawww-data<%= node['greeting'] %> <%= @goodbye %> total memory: <%= node['memory']['total'] %> uninitialized node key: <%= node['un-initialized'] %> itamae-1.12.5/spec/integration/recipes/variables.rb0000644000004100000410000000041014167043240022270 0ustar www-datawww-datanode.reverse_merge!( variables: { lvars: binding.local_variables, ivars: instance_variables, } ) file "/tmp/local_variables" do content node[:variables][:lvars].to_s end file "/tmp/instance_variables" do content node[:variables][:ivars].to_s end itamae-1.12.5/spec/integration/recipes/default2.rb0000644000004100000410000000015514167043240022034 0ustar www-datawww-datafile "put file in default2.rb" do action :nothing path "/tmp/created_in_default2" content 'Hello' end itamae-1.12.5/spec/integration/recipes/dry_run.rb0000644000004100000410000000015714167043240022012 0ustar www-datawww-datafile "/tmp/it_does_not_exist" do action :edit block do |content| content.gsub!("foo", "bar") end end itamae-1.12.5/spec/integration/recipes/node.json0000644000004100000410000000003214167043240021613 0ustar www-datawww-data{ "greeting": "Hello" } itamae-1.12.5/spec/integration/recipes/files/0000755000004100000410000000000014167043240021102 5ustar www-datawww-dataitamae-1.12.5/spec/integration/recipes/files/remote_file_auto0000644000004100000410000000001514167043240024343 0ustar www-datawww-dataHello Itamae itamae-1.12.5/spec/integration/recipes/files/empty_file0000644000004100000410000000000014167043240023150 0ustar www-datawww-dataitamae-1.12.5/spec/integration/recipes/hello.txt0000644000004100000410000000001514167043240021640 0ustar www-datawww-dataHello Itamae itamae-1.12.5/spec/integration/docker_spec.rb0000644000004100000410000000145614167043240021162 0ustar www-datawww-datarequire 'spec_helper' describe file('/tmp/cron_stopped') do it { should be_file } its(:content) do expect(subject.content.lines.size).to eq 1 end end # FIXME: cron service is not running in docker... # # root@3450c6da6ea5:/# ps -C cron # PID TTY TIME CMD # root@3450c6da6ea5:/# service cron start # Rather than invoking init scripts through /etc/init.d, use the service(8) # utility, e.g. service cron start # # Since the script you are attempting to invoke has been converted to an # Upstart job, you may also use the start(8) utility, e.g. start cron # root@3450c6da6ea5:/# ps -C cron # PID TTY TIME CMD # root@3450c6da6ea5:/# # describe file('/tmp/cron_running') do # it { should be_file } # its(:content) do # expect(subject.content.lines.size).to eq 2 # end # end itamae-1.12.5/spec/integration/default_spec.rb0000644000004100000410000002145214167043240021335 0ustar www-datawww-datarequire 'spec_helper' describe user("itamae") do it { should exist } it { should have_uid 1234 } it { should have_home_directory '/home/itamae' } it { should have_login_shell '/bin/dash' } end describe file('/tmp/included_recipe') do it { should be_file } end describe package('dstat') do it { should be_installed } end describe package('sl') do it { should be_installed } end describe package('resolvconf') do it { should_not be_installed } end %w!/tmp/remote_file /tmp/remote_file_auto!.each do |f| describe file(f) do it { should be_file } its(:content) { should match(/Hello Itamae/) } end end describe file('/tmp/directory') do it { should be_directory } it { should be_mode 700 } it { should be_owned_by "itamae" } it { should be_grouped_into "itamae" } end describe file('/tmp/directory_never_exist1') do it { should_not be_directory } end %w!/tmp/template /tmp/template_auto!.each do |f| describe file(f) do it { should be_file } its(:content) { should match(/Hello/) } its(:content) { should match(/Good bye/) } its(:content) { should match(/^total memory: \d+kB$/) } its(:content) { should match(/^uninitialized node key: $/) } end end describe file('/tmp/file') do it { should be_file } its(:content) { should match(/Hello World/) } it { should be_mode 777 } end describe file('/tmp/file_with_suid') do it { should be_file } it { should be_mode 4755 } it { should be_owned_by "itamae" } it { should be_grouped_into "itamae" } end describe file('/tmp/execute') do it { should be_file } its(:content) { should match(/Hello Execute/) } end describe file('/tmp/never_exist1') do it { should_not be_file } end describe file('/tmp/never_exist2') do it { should_not be_file } end describe file('/tmp/http_request.html') do it { should be_file } its(:content) { should match(/"from":\s*"itamae"/) } end describe file('/tmp/http_request_delete.html') do it { should be_file } its(:content) { should match(/"from":\s*"itamae"/) } end describe file('/tmp/http_request_post.html') do it { should be_file } its(:content) { should match(/"from":\s*"itamae"/) } its(:content) { should match(/"love":\s*"sushi"/) } end describe file('/tmp/http_request_put.html') do it { should be_file } its(:content) { should match(/"from":\s*"itamae"/) } its(:content) { should match(/"love":\s*"sushi"/) } end describe file('/tmp/http_request_headers.html') do it { should be_file } its(:content) { should match(/"User-Agent":\s*"Itamae"/) } end xdescribe file('/tmp/http_request_redirect.html') do it { should be_file } its(:content) { should match(/"from":\s*"itamae"/) } end describe file('/tmp/notifies') do it { should be_file } its(:content) { should eq("2431") } end describe file('/tmp/subscribes') do it { should be_file } its(:content) { should eq("2431") } end describe file('/tmp-link') do it { should be_linked_to '/tmp' } its(:content) do expect(subject.content.lines.size).to eq 0 end end describe file('/tmp-link-force') do it { should be_linked_to '/tmp' } end describe file('/tmp/link-force-no-dereference') do it { should be_linked_to 'link-force-no-dereference2' } end describe file('/tmp/link-force-no-dereference/link-force-no-dereference2') do it { should_not exist } end describe command('cd /tmp/git_repo && git rev-parse HEAD') do its(:stdout) { should match(/3116e170b89dc0f7315b69c1c1e1fd7fab23ac0d/) } end describe command('cd /tmp/git_repo_submodule/empty_repo && cat README.md') do its(:stdout) { should match(/Empty Repo/) } end describe command('cd /tmp/git_repo_depth_1 && git rev-list --count HEAD') do its(:stdout) { should eq "1\n" } end describe file('/tmp/created_by_itamae_user') do it { should be_file } it { should be_owned_by 'itamae' } its(:content) { should eq("/home/itamae\n/home/itamae") } end describe file('/tmp/created_in_default2') do it { should be_file } end describe file('/tmp/never_exist3') do it { should_not be_file } end describe file('/tmp/never_exist4') do it { should_not be_file } end describe file('/tmp/created_in_redefine') do it { should be_file } its(:content) { should match(/first/) } end describe command('gem list') do its(:stdout) { should include('tzinfo (1.2.2, 1.1.0)') } end describe command('gem list') do its(:stdout) { should match(/^rake \(.*11.1.0.*\)/) } end describe command('gem list') do its(:stdout) { should_not include('test-unit') } end describe command('gem list') do its(:stdout) { should include('ast (2.0.0)') } end describe command('ri AST') do its(:stderr) { should eq("Nothing known about AST\n") } end describe file('/tmp/created_by_definition') do it { should be_file } its(:content) { should eq("name:name,key:value,message:Hello, Itamae\n") } end describe file('/tmp/remote_file_in_definition') do it { should be_file } its(:content) { should eq("definition_example\n") } end describe file('/tmp/created_by_definition_2_created') do it { should be_file } its(:content) { should eq("name:created,key:value2,message:Hello, Itamae\n") } end describe file('/tmp/remote_file_in_definition_2_created') do it { should be_file } its(:content) { should eq("definition_example_2\n") } end describe file('/tmp/created_by_definition_2_not_created') do it { should_not exist } end describe file('/tmp/remote_file_in_definition_2_not_created') do it { should_not exist } end describe file('/tmp/created_by_definition_3_created') do it { should be_file } its(:content) { should eq("name:created,key:value3,message:Hello, Itamae\n") } end describe file('/tmp/remote_file_in_definition_3_created') do it { should be_file } its(:content) { should eq("definition_example_3\n") } end describe file('/tmp/created_by_definition_3_not_created') do it { should_not exist } end describe file('/tmp/remote_file_in_definition_3_not_created') do it { should_not exist } end describe file('/tmp/multi_delayed_notifies') do it { should be_file } its(:content) { should eq("1\n2\n3\n4\n") } end describe file('/tmp/multi_immediately_notifies') do it { should be_file } its(:content) { should eq("1\n2\n3\n4\n") } end describe file('/tmp/file_edit_sample') do it { should be_file } its(:content) { should eq("Hello, Itamae") } it { should be_mode 400 } it { should be_owned_by "itamae2" } it { should be_grouped_into "itamae2" } end describe file('/tmp/file_edit_with_suid') do it { should be_file } it { should be_mode 4755 } it { should be_owned_by "itamae2" } it { should be_grouped_into "itamae2" } end describe file('/tmp/file_edit_keeping_mode_owner') do it { should be_file } its(:content) { should eq("Hello, Itamae") } it { should be_mode 444 } it { should be_owned_by "itamae" } it { should be_grouped_into "itamae" } end describe file('/tmp/file_edit_with_content_change_updates_timestamp') do its(:mtime) { should be > DateTime.iso8601("2016-05-02T01:23:45Z") } end describe file('/tmp/file_edit_without_content_change_keeping_timestamp') do its(:mtime) { should eq(DateTime.iso8601("2016-05-02T12:34:56Z")) } end describe file('/home/itamae2') do it { should be_directory } it { should be_owned_by "itamae2" } it { should be_grouped_into "itamae2" } end describe file('/tmp/file_create_without_content') do its(:content) { should eq("Hello, World") } it { should be_mode 600 } it { should be_owned_by "itamae" } it { should be_grouped_into "itamae" } end describe file('/tmp/file_edit_notifies') do its(:content) { should eq("1") } end describe file('/tmp/file_without_content_change_updates_mode_and_owner') do its(:content) { should eq("Hello, world") } it { should be_mode 666 } it { should be_owned_by "itamae2" } it { should be_grouped_into "itamae2" } end describe file('/tmp/file_with_content_change_updates_timestamp') do its(:mtime) { should be > DateTime.iso8601("2016-05-01T01:23:45Z") } end describe file('/tmp/file_without_content_change_keeping_timestamp') do its(:mtime) { should eq(DateTime.iso8601("2016-05-01T12:34:56Z")) } end describe file('/tmp/subscribed_from_parent') do it { should be_file } end describe file('/tmp/empty_file1') do it { should exist } it { should be_file } its(:content) { should eq "" } end describe file('/tmp/empty_file2') do it { should exist } it { should be_file } its(:content) { should eq "" } end describe file('/tmp/empty_file3') do it { should exist } it { should be_file } its(:content) { should eq "" } end describe file('/tmp/toplevel_module') do it { should exist } it { should be_file } its(:content) { should eq "helper" } end describe file('/tmp/local_variables') do it { should exist } it { should be_file } its(:content) { should eq "[]" } end describe file('/tmp/instance_variables') do it { should exist } it { should be_file } its(:content) { should eq "[:@recipe]" } # backward compatibility end itamae-1.12.5/spec/integration/ordinary_user_spec.rb0000644000004100000410000000632314167043240022576 0ustar www-datawww-datarequire 'spec_helper' describe file('/tmp/remote_file') do it { should be_file } it { should be_owned_by "ordinary_san" } it { should be_grouped_into "ordinary_san" } its(:content) { should match(/Hello Itamae/) } end describe file('/tmp/remote_file_root') do it { should be_file } it { should be_owned_by "root" } it { should be_grouped_into "root" } its(:content) { should match(/Hello Itamae/) } end %w[/tmp/remote_file_another_ordinary /tmp/remote_file_another_ordinary_with_root].each do |path| describe file(path) do it { should be_file } it { should be_owned_by "itamae" } it { should be_grouped_into "itamae" } its(:content) { should match(/Hello Itamae/) } end end ### describe file('/tmp/file') do it { should be_file } it { should be_owned_by "ordinary_san" } it { should be_grouped_into "ordinary_san" } its(:content) { should match(/Hello World/) } end describe file('/tmp/file_root') do it { should be_file } it { should be_owned_by "root" } it { should be_grouped_into "root" } its(:content) { should match(/Hello World/) } end %w[/tmp/file_another_ordinary /tmp/file_another_ordinary_with_root].each do |path| describe file(path) do it { should be_file } it { should be_owned_by "itamae" } it { should be_grouped_into "itamae" } its(:content) { should match(/Hello World/) } end end ### describe file('/tmp/template') do it { should be_file } it { should be_owned_by "ordinary_san" } it { should be_grouped_into "ordinary_san" } its(:content) { should match(/Hello/) } its(:content) { should match(/Good bye/) } its(:content) { should match(/^total memory: \d+kB$/) } its(:content) { should match(/^uninitialized node key: $/) } end describe file('/tmp/template_root') do it { should be_file } it { should be_owned_by "root" } it { should be_grouped_into "root" } its(:content) { should match(/Hello/) } its(:content) { should match(/Good bye/) } its(:content) { should match(/^total memory: \d+kB$/) } its(:content) { should match(/^uninitialized node key: $/) } end %w[/tmp/template_another_ordinary /tmp/template_another_ordinary_with_root].each do |path| describe file(path) do it { should be_file } it { should be_owned_by "itamae" } it { should be_grouped_into "itamae" } its(:content) { should match(/Hello/) } its(:content) { should match(/Good bye/) } its(:content) { should match(/^total memory: \d+kB$/) } its(:content) { should match(/^uninitialized node key: $/) } end end ### describe file('/tmp/http_request.html') do it { should be_file } it { should be_owned_by "ordinary_san" } it { should be_grouped_into "ordinary_san" } its(:content) { should match(/"from":\s*"itamae"/) } end describe file('/tmp/http_request_root.html') do it { should be_file } it { should be_owned_by "root" } it { should be_grouped_into "root" } its(:content) { should match(/"from":\s*"itamae"/) } end %w[/tmp/http_request_another_ordinary.html /tmp/http_request_another_ordinary_with_root.html].each do |path| describe file(path) do it { should be_file } it { should be_owned_by "itamae" } it { should be_grouped_into "itamae" } its(:content) { should match(/"from":\s*"itamae"/) } end end itamae-1.12.5/spec/integration/local_spec.rb0000644000004100000410000000023214167043240020774 0ustar www-datawww-datadescribe file('/tmp/file_as_ordinary_user') do it { should be_file } it { should be_owned_by "itamae" } it { should be_grouped_into "itamae" } end itamae-1.12.5/spec/integration/spec_helper.rb0000644000004100000410000000073514167043240021171 0ustar www-datawww-datarequire "serverspec" require "docker" set :backend, :docker set :docker_image, ENV["DOCKER_IMAGE"] set :docker_container, ENV["DOCKER_CONTAINER"] # Disable sudo # set :disable_sudo, true # Set environment variables # set :env, :LANG => 'C', :LC_MESSAGES => 'C' # Set PATH # set :path, '/sbin:/usr/local/sbin:$PATH' RSpec.configure do |config| unless ENV["CI"] # focus is enabled only local (Run all specs at CI) config.filter_run_when_matching :focus end end itamae-1.12.5/CHANGELOG.md0000644000004100000410000005323514167043240014712 0ustar www-datawww-data## Unreleased [full changelog](https://github.com/itamae-kitchen/itamae/compare/v1.12.5...master) ## v1.12.5 [full changelog](https://github.com/itamae-kitchen/itamae/compare/v1.12.4...v1.12.5) Bugfixes - [Define exit_on_failure? to suppress thor's warning](https://github.com/itamae-kitchen/itamae/pull/344) ## v1.12.4 [full changelog](https://github.com/itamae-kitchen/itamae/compare/v1.12.3...v1.12.4) Improvements - [Initialize tmp_dir if no tmpdir option was specified.](https://github.com/itamae-kitchen/itamae/pull/341) ## v1.12.3 [full changelog](https://github.com/itamae-kitchen/itamae/compare/v1.12.2...v1.12.3) Bugfixes - [Fix Chef install script URL (by @NPoi)](https://github.com/itamae-kitchen/itamae/pull/339) ## v1.12.2 [full changelog](https://github.com/itamae-kitchen/itamae/compare/v1.12.1...v1.12.2) Improvements - [remote_directory: add diff -r option](https://github.com/itamae-kitchen/itamae/pull/338) ## v1.12.1 [full changelog](https://github.com/itamae-kitchen/itamae/compare/v1.12.0...v1.12.1) Improvements - [Allow defining top-level modules without `::`](https://github.com/itamae-kitchen/itamae/pull/332) ## v1.12.0 [full changelog](https://github.com/itamae-kitchen/itamae/compare/v1.11.2...v1.12.0) Improvements - [Add `--tmp-dir` to cli options](https://github.com/itamae-kitchen/itamae/pull/331) ## v1.11.2 [full changelog](https://github.com/itamae-kitchen/itamae/compare/v1.11.1...v1.11.2) Bugfixes - [Support thor-1.1.0 (by @chaaaaarlotte)](https://github.com/itamae-kitchen/itamae/pull/329) ## v1.11.1 [full changelog](https://github.com/itamae-kitchen/itamae/compare/v1.11.0...v1.11.1) Improvements - [Support Ruby 3.0.0](https://github.com/itamae-kitchen/itamae/pull/326) - [file: improve diff output (by @terceiro)](https://github.com/itamae-kitchen/itamae/pull/327) ## v1.11.0 [full changelog](https://github.com/itamae-kitchen/itamae/compare/v1.10.10...v1.11.0) Improvements - [file: add support for sensitive files (by @terceiro)](https://github.com/itamae-kitchen/itamae/pull/325) ## v1.10.10 [full changelog](https://github.com/itamae-kitchen/itamae/compare/v1.10.9...v1.10.10) Improvements - [Make output unbuffered](https://github.com/itamae-kitchen/itamae/pull/317) ## v1.10.9 [full changelog](https://github.com/itamae-kitchen/itamae/compare/v1.10.8...v1.10.9) Improvements - [Fix CRLF problem in windows](https://github.com/itamae-kitchen/itamae/pull/316) ## v1.10.8 [full changelog](https://github.com/itamae-kitchen/itamae/compare/v1.10.7...v1.10.8) Improvements - [Print "(in action_XXX)" as a debug log](https://github.com/itamae-kitchen/itamae/pull/315) - [Reduce `check_package_is_installed` calling when package version is not specified](https://github.com/itamae-kitchen/itamae/pull/314) - [Simplify Git resource's get_revision method](https://github.com/itamae-kitchen/itamae/pull/313) ## v1.10.7 [full changelog](https://github.com/itamae-kitchen/itamae/compare/v1.10.6...v1.10.7) Improvements - [Improve `file` resource performance](https://github.com/itamae-kitchen/itamae/pull/310) ## v1.10.6 [full changelog](https://github.com/itamae-kitchen/itamae/compare/v1.10.5...v1.10.6) Improvements - [Don't use `sudo` when `--no-sudo` is passed](https://github.com/itamae-kitchen/itamae/pull/302) ## v1.10.5 [full changelog](https://github.com/itamae-kitchen/itamae/compare/v1.10.4...v1.10.5) Improvements - [Check http status code in `http_request` resource (by @takumin)](https://github.com/itamae-kitchen/itamae/pull/296) ## v1.10.4 [full changelog](https://github.com/itamae-kitchen/itamae/compare/v1.10.3...v1.10.4) Bugfixes - [Suppress Ruby warnings (by @pocke)](https://github.com/itamae-kitchen/itamae/pull/284) - [Suppress Ruby warning (by @pocke)](https://github.com/itamae-kitchen/itamae/pull/287) - [Run test cases correctly (by @pocke)](https://github.com/itamae-kitchen/itamae/pull/289) Improvements - [Add description to --tag option of docker subcommand (by @pocke)](https://github.com/itamae-kitchen/itamae/pull/286) - [Refine `itamae docker`'s created message (by @pocke)](https://github.com/itamae-kitchen/itamae/pull/288) ## v1.10.3 [full changelog](https://github.com/itamae-kitchen/itamae/compare/v1.10.2...v1.10.3) Bugfixes - [Make `send_file` aware of `user` option](https://github.com/itamae-kitchen/itamae/pull/277) ## v1.10.2 [full changelog](https://github.com/itamae-kitchen/itamae/compare/v1.10.1...v1.10.2) Bugfixes - [Disable mash warnings (review catch up)](https://github.com/itamae-kitchen/itamae/pull/273) ## v1.10.1 [full changelog](https://github.com/itamae-kitchen/itamae/compare/v1.10.0...v1.10.1) Bugfixes - [fail `--ohai` option when using ohai v13.0.1 or higher](https://github.com/itamae-kitchen/itamae/pull/251) ## v1.10.0 [full changelog](https://github.com/itamae-kitchen/itamae/compare/v1.9.13...v1.10.0) Features - [Support `only_if` and `not_if` inside a `define`](https://github.com/itamae-kitchen/itamae/pull/271) ## v1.9.13 [full changelog](https://github.com/itamae-kitchen/itamae/compare/v1.9.12...v1.9.13) Bugfixes - [Fixed. Can not create empty file](https://github.com/itamae-kitchen/itamae/pull/269) ## v1.9.12 [full changelog](https://github.com/itamae-kitchen/itamae/compare/v1.9.11...v1.9.12) Features - [jail backend: add support of FreeBSD Jail (`itamae jail`)](https://github.com/itamae-kitchen/itamae/pull/249) Bugfixes - [docker backend: Fixed edit action of file resource doesn't work with docker backend](https://github.com/itamae-kitchen/itamae/pull/257) Improvements - [Print '(dry-run)' first in dry-run mode](https://github.com/itamae-kitchen/itamae/pull/252) ## v1.9.11 Features - [docker backend: Support image tagging](https://github.com/itamae-kitchen/itamae/pull/230) - [docker backend: Support docker_container_create_options](https://github.com/itamae-kitchen/itamae/pull/231) Bugfixes - [Fix help subcommand](https://github.com/itamae-kitchen/itamae/pull/235) ## v1.9.10 Features - [Add depth attribute to git resource](https://github.com/itamae-kitchen/itamae/pull/219) - [Support force link a direcotory](https://github.com/itamae-kitchen/itamae/pull/229) - [Add support password authentication for ssh](https://github.com/itamae-kitchen/itamae/pull/227) Bugfixes - [Run a resource subscribing a resource in child recipe](https://github.com/itamae-kitchen/itamae/pull/224) - [Change file owner first, then change file permissions](https://github.com/itamae-kitchen/itamae/pull/228) Improvements - [Dir.exists? is deprecated, use Dir.exist?](https://github.com/itamae-kitchen/itamae/pull/226) ## v1.9.9 Features - [`itamae ssh` now accepts `--ssh-config` option](https://github.com/itamae-kitchen/itamae/pull/211) - [Introduce `--login-shell` option](https://github.com/itamae-kitchen/itamae/pull/217) - [`gem_package` resource has `uninstall` action](https://github.com/itamae-kitchen/itamae/pull/216) Bugfixes - [`send_file` fails against docker backend](https://github.com/itamae-kitchen/itamae/pull/215) ## v1.9.8 Bugfixes - [edit action of file resource: Keep mtime if no change](https://github.com/itamae-kitchen/itamae/pull/212) ## v1.9.7 (pre) Bugfixes - [Mark a file as updated in dry-run mode (by @ryotarai)](https://github.com/itamae-kitchen/itamae/pull/208) - [Do not surround LF with the ANSI escape sequence (by @daic-h)](https://github.com/itamae-kitchen/itamae/pull/209) ## v1.9.6 Features - [Introduce `--detailed-exitcode` option. (by @ryotarai)](https://github.com/itamae-kitchen/itamae/pull/206) Bugfixes - [If `git rev-list` fails, do `git fetch origin` (by @ryotarai)](https://github.com/itamae-kitchen/itamae/pull/205) - [If gid passed to user resource is a String, treat it as group name. (by @ryotarai)](https://github.com/itamae-kitchen/itamae/pull/207) ## v1.9.5 Bugfixes - [Set mode and owner correctly when file not changed (by @sorah)](https://github.com/itamae-kitchen/itamae/commit/438d79e4ef714f637f8f1cb50b01293e5232340a) - [Make tempfile unreadable to secure (by @sorah)](https://github.com/itamae-kitchen/itamae/commit/7af1d29fc020e57f3587aace728fbb40e35669cf) - [Accept any objects as a log message (by @abicky)](https://github.com/itamae-kitchen/itamae/pull/195) ## v1.9.4 Bugfixes - [Fix a bug that displays inappropriate diff in file deletion (by @takus)](https://github.com/itamae-kitchen/itamae/pull/200) - [Show diff on edit action of file resource in dry-run mode. (by @ryotarai)](https://github.com/itamae-kitchen/itamae/pull/197) - [Stop to call `chown --reference` and `chmod --reference` (by @yuichiro-naito)](https://github.com/itamae-kitchen/itamae/pull/193) ## v1.9.3 Improvements - [Support redirect on http_request resource (by @hico-horiuchi)](https://github.com/itamae-kitchen/itamae/pull/190) - [Use /bin/bash as default shell if shell is not set (by @hico-horiuchi)](https://github.com/itamae-kitchen/itamae/pull/192) - [Stop replacing files which are not updated (by @KitaitiMakoto)](https://github.com/itamae-kitchen/itamae/pull/194) ## v1.9.2 Features - [New option: `options` for `gem_package` resource (by @hico-horiuchi)](https://github.com/itamae-kitchen/itamae/pull/186) Improvements - [Execute `vagrant ssh-config` under `Bundler.with_clean_env` (by @hfm)](https://github.com/itamae-kitchen/itamae/pull/188) - [Specify type of `recursive` option for `git` resource and `force` option for `link` resource (by @k0kubun)](https://github.com/itamae-kitchen/itamae/pull/189) ## v1.9.1 Features - [Add `get`, `post`, `put`, `delete` and `options` actions to `http_request` resource (by @hico-horiuchi)](https://github.com/itamae-kitchen/itamae/pull/184) ## v1.9.0 Features - [New resource: `http_request` resource (by @hico-horiuchi)](https://github.com/itamae-kitchen/itamae/pull/180) - [Introduce Handler which handles events from Itamae (by @ryotarai)](https://github.com/itamae-kitchen/itamae/pull/181) - Compatibility can be broken because this is experimental feature Improvements - [Optimize `git` resource for fixed revision (by @k0kubun)](https://github.com/itamae-kitchen/itamae/pull/182) - Rename `--dot` option to `--recipe-graph` option. (by @ryotarai) - Compatibility can be broken because this is experimental feature ## v1.8.0 Features - [`generate` and `destroy` subcommands to manipulate cookbooks and roles (by @k0kubun)](https://github.com/itamae-kitchen/itamae/pull/176) Improvements - [Fallback to autoload resource plugin (by @k0kubun)](https://github.com/itamae-kitchen/itamae/pull/179) ## v1.7.0 No change ## v1.7.0.pre Features - `--profile` option (by @ryotarai) - `--profile PATH` saves executed commands to `PATH` in JSON format - Compatibility can be broken because this is experimental feature Bugfixes - [Suppress errors of `edit` action of `file` resource when the target file doesn't exist in `dry-run` mode (by @ryotarai)](https://github.com/itamae-kitchen/itamae/pull/144) ## v1.6.3 Features - [New command: `itamae init` which creates files and directories following the best practices (by @hmadison)](https://github.com/itamae-kitchen/itamae/pull/172) ## v1.6.2 Bugfixes - [Treat recipe name, arg of `include_recipe`, including `::` twice or more properly (by @sue445)](https://github.com/itamae-kitchen/itamae/pull/171) ## v1.6.1 Bugfixes - [Send a notification from `edit` action of `file` resource properly (by @kurochan)](https://github.com/itamae-kitchen/itamae/pull/169) ## v1.6.0 Improvements - [Ignore `--node-yaml` when the result is false (by @k0kubun)](https://github.com/itamae-kitchen/itamae/pull/165) - [Allow `include_recipe` to omit `.rb` extension (by @k0kubun)](https://github.com/itamae-kitchen/itamae/pull/166) - This is backward-compatible change - [Allow `load_recipes` to load plugin recipes directly (by @k0kubun)](https://github.com/itamae-kitchen/itamae/pull/167) ## v1.5.2 Improvements - [`include_recipe 'plugin_name'` loads `itamae/plugin/recipe/plugin_name/default.rb` too (by @ryotarai)](https://github.com/itamae-kitchen/itamae/pull/162) ## v1.5.1 Improvements - [Logger can be injected one which doesn't have `color` method. (by @ryotarai)](https://github.com/itamae-kitchen/itamae/commit/7c50f376f69029836047f26ab0a46b41b928c0d3) ## v1.5.0 Improvements - [Make a logger injectable from outside of Itamae. (by @ryotarai)](https://github.com/itamae-kitchen/itamae/pull/160) ## v1.4.5 Improvements - [Load `default.rb` if `include_recipe` is called with directory path. (by @Rudolph-Miller)](https://github.com/itamae-kitchen/itamae/pull/156) ## v1.4.4 Features - `--shell` option for `local`, `ssh` and `docker` subcommands. If it is set, it will be used instead of /bin/sh (by @ryotarai) ## v1.4.3 Bugfixes - [Restore original attributes of a resource after each action. (by @ryotarai)](https://github.com/itamae-kitchen/itamae/commit/28d33da3cb67c6a7635e47845b0055cb17df53a8) ## v1.4.2 Improvements - [Load plugin gems that is not managed by bundler. (by @KitaitiMakoto)](https://github.com/itamae-kitchen/itamae/pull/151) ## v1.4.1 Improvements - [`gem_binary` of `gem_package` resource accepts an Array too. (by @eagletmt)](https://github.com/itamae-kitchen/itamae/pull/149) - [`git` resource executes `git clone` if the destination directory is empty (by @tacahilo)](https://github.com/itamae-kitchen/itamae/pull/150) ## v1.4.0 Improvements - Make `cwd` a common attribute. (idea by @tacahilo) - It was an attribute for execute resource - When `user` attribute is set, change directory to the user's home directory. (idea by @tacahilo) - even if cd command fail, it will be ignored - directory specified by cwd will take precedence over this ## v1.3.6 Bugfixes - `create` action of `file` resource without `content` attribute changes mode and owner without touching the content of the file (by @ryotarai) ## v1.3.5 Improvements - [`create` action of `file` resource without `content` attribute changes mode and owner without touching the content of the file (by @ryotarai)](https://github.com/itamae-kitchen/itamae/compare/itamae-kitchen:d4a0abc...itamae-kitchen:3eae144) Bugfixes - [Edit action of file resource should set owner and mode if specified (by @eagletmt)](https://github.com/itamae-kitchen/itamae/pull/143) ## v1.3.4 Improvements - [Output stdout/err logs during command execution (by @ryotarai)](https://github.com/itamae-kitchen/itamae/commit/24f140dd9744f30c645422959a6a72b6e31eacc4) ## v1.3.3 Improvements - [Add `container` option to `docker` subcommand (by @marcy-terui)](https://github.com/itamae-kitchen/itamae/pull/142) ## v1.3.2 Features - [Add `recursive` attribute to `git` resource (by @mmasaki)](https://github.com/itamae-kitchen/itamae/pull/140) ## v1.3.1 Features - [Add `delete` action to `directory` resource (by @rrreeeyyy)](https://github.com/itamae-kitchen/itamae/pull/139) ## v1.3.0 Improvements - Update `HOME` environment variable when `user` attribute is specified. (incompatible change) (by @ryotarai) ## v1.2.21 Improvements - [Show error message when specified action is unavailable in dry_run mode (by @eagletmt)](https://github.com/itamae-kitchen/itamae/pull/137) - [Fix deprecation warnings in unit tests (by @eagletmt)](https://github.com/itamae-kitchen/itamae/pull/138) ## v1.2.20 Improvements - [Wrap host inventory value with Hashie::Mash to access it by a method call (by @ryotarai)](https://github.com/itamae-kitchen/itamae/pull/135) ## v1.2.19 Features - [`create_home` attribute of user resource (by @xibbar)](https://github.com/itamae-kitchen/itamae/pull/131) ## v1.2.18 Features - `run_command` method in a recipe, definition and resource (by @ryotarai) ## v1.2.17 Features - [Support provider for service resource (by @sonots)](https://github.com/itamae-kitchen/itamae/pull/134) ## v1.2.16 Improvements - [`force` option for `link` resource (by @mikeda)](https://github.com/itamae-kitchen/itamae/pull/128) ## v1.2.15 Bugfixes - [Fix --no-sudo to work properly (by @evalphobia)](https://github.com/itamae-kitchen/itamae/pull/126) - [Fix a glitch on raising exception when source doesn't exist (by @mozamimy)](https://github.com/itamae-kitchen/itamae/pull/125) ## v1.2.14 Features - "edit" action of "file" resource (by @ryotarai) ## v1.2.13 Features - [Add "shell" attribute to user resource (by @toritori0318)](https://github.com/itamae-kitchen/itamae/pull/120) ## v1.2.12 Bugfixes - Run delayed notifications created by a delayed notification. (by @ryotarai) - Set updated false after executing resources. (by @ryotarai) ## v1.2.11 Bugfixes - [Show difference of user resource when it is created. by @gongo](https://github.com/itamae-kitchen/itamae/pull/118) ## v1.2.10 Bugfixes - [Use given attribute value even if it's falsey (by @sorah)](https://github.com/itamae-kitchen/itamae/pull/117) ## v1.2.9 Bugfixes - Do not use local variable named `variables`. (by @ryotarai) If `variables` is used as local variable's name, the following causes a syntax error. ``` template "..." do variables foo: bar # variables(foo: bar) # This never cause a syntax error end ``` See also: https://bugs.ruby-lang.org/issues/11016 ## v1.2.8 Improvements - [Load ~/.ssh/config (by @maruware)](https://github.com/itamae-kitchen/itamae/pull/115) ## v1.2.7 Bugfixes - Backend::Docker#finalize should be public. (by @mizzy) ## v1.2.6 - Remove code for debugging... (by @ryotarai) ## v1.2.5 Bugfixes - Bugs in definition feature. (by @ryotarai) ## v1.2.4 Improvements - Use specinfra/core instead of specinfra. (by @ryotarai) ## v1.2.3 Bugfixes - Bugs in Node class (by @ryotarai) ## v1.2.2 Improvements - Refactor Backend and Runner class for multi backends. (by @ryotarai) ## v1.2.1 (yanked) ## v1.2.0 Feature - Docker backend (by @ryotarai) - This backend builds a Docker image. - Usage: `itamae docker --image baseimage recipe.rb` - NOTE: This feature is experimental. - Compatibility can be broken because this is experimental feature ## v1.1.26 Bugfix - Always outdent. (by @ryotarai) ## v1.1.25 Improvements - Make logging less verbose by default. (by @eagletmt) - Change indent width from 3 to 2. (by @ryotarai) ## v1.1.24 Bugfixes - Make `node` accessible from define block. (by @ryotarai) ## v1.1.23 Feature - Validate node attributes by `Node#validate!` (by @ryotarai) ## v1.1.22 Improvements - `source :auto` accepts a template without .erb extension. (by @ryotarai) ## v1.1.21 Bugfixes - Ignore CommandExecutionError during listing installed gems. (by @eagletmt) - because `gem` command may not be installed in dry-run mode ## v1.1.20 Features - `source :auto` of remote_file and template resources. (by @ryotarai) - details: https://github.com/itamae-kitchen/itamae/issues/94 ## v1.1.19 Features - `verify` attribute - command will be executed after running resource action. (by @ryotarai) - If it fails, Itamae will abort (notifications will not be executed) Improvements - [`--vagrant` option without `--host` assumes the VM name `default` (by @muratayusuke)](https://github.com/itamae-kitchen/itamae/pull/91) - `delayed` is a valid notification timing. (by @ryotarai) - same as Chef - If invalid notification timing is provided, an error will be raised. (by @ryotarai) ## v1.1.18 Improvements - [Add remove action to package resource (by @eagletmt)](https://github.com/itamae-kitchen/itamae/pull/92) - Colorize diff output of file resource (by @ryotarai) - removed lines in red - inserted lines in green ## v1.1.17 Bugfixes - Do not remove space char in output of diff. (by @ryotarai) ## v1.1.16 Features - `source` attribute of `gem_package` resource. (by @ryotarai) ## v1.1.15 Features - Implement `gem_package` resource. (by @ryotarai) ## v1.1.14 Improvements - Start a service only if the service is not running. (by @ryotarai) - Stop a service only if the service is running. (by @ryotarai) ## v1.1.13 Improvements - [Set executed attr of execute resource for logging purpose. (by @ryotarai)](https://github.com/itamae-kitchen/itamae/pull/86) - [Colorize diff output of file resource green. (by @ryotarai)](https://github.com/itamae-kitchen/itamae/pull/87) ## v1.1.12 Bugfixes - [Update home directory of user resource if changed. (by @ryotarai)](https://github.com/itamae-kitchen/itamae/commit/0b5ad5245af8a7849d36d0598f06b7adb9ac025a) ## v1.1.11 Bugfixes - [Do not include recipes which are already included. (by @ryotarai)](https://github.com/itamae-kitchen/itamae/pull/85) - This may break backward compatibility. ## v1.1.10 Feature - `--dot` option to write dependency graph of recipes - Compatibility can be broken because this is experimental feature ## v1.1.9 Improvements - Show template file path when rendering the template fails. (by @ryotarai) ## v1.1.8 Improvements - [Show differences in green (by @mizzy)](https://github.com/itamae-kitchen/itamae/pull/82) ## v1.1.7 Bugfixes - Fix a typo bug (by @ryotarai) ## v1.1.6 (yanked) Improvements - [Normalize mode value of file resource by prepending '0' (by @sorah)](https://github.com/itamae-kitchen/itamae/pull/76) Bugfixes - [Fix a problem that occurs when the current value is false. (by @mizzy)](https://github.com/itamae-kitchen/itamae/pull/75) ## v1.1.5 Bugfixes - Clear current attributes before each action. (by @ryotarai) - Turn on updated-flag after each action. (by @ryotarai) ## v1.1.4 Bugfixes - `Node#[]` with unknown key returns nil. (by @nownabe) - https://github.com/itamae-kitchen/itamae/pull/71 ## v1.1.3 Features - `group` resource (Thanks to @a2ikm) - https://github.com/itamae-kitchen/itamae/pull/70 ## v1.1.2 Features - `user` resource accepts group name (String) as its `gid`. (by @ryotarai) ## v1.1.1 Features - New resource `remote_directory` which transfers a directory from local to remote like `remote_file` resource. (by @k0kubun) - https://github.com/itamae-kitchen/itamae/pull/66 ## v1.1.0 Incompatible changes - [`uid` and `gid` attributes of `user` resource accept only Integer. (by @eagletmt)](https://github.com/itamae-kitchen/itamae/pull/65) itamae-1.12.5/.gitignore0000644000004100000410000000027714167043240015067 0ustar www-datawww-data*.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp .vagrant Gemfile.local .ruby-version itamae-1.12.5/Rakefile0000644000004100000410000000434514167043240014544 0ustar www-datawww-datarequire "bundler/gem_tasks" require 'rspec/core/rake_task' require 'tempfile' require 'net/ssh' Dir['tasks/*.rb'].each do |file| require_relative file end desc 'Run unit and integration specs.' task :spec => ['spec:unit', 'spec:integration:all'] TEST_IMAGE = ENV["TEST_IMAGE"] || "ubuntu:trusty" namespace :spec do RSpec::Core::RakeTask.new("unit") do |task| task.ruby_opts = '-I ./spec/unit' task.pattern = "./spec/unit{,/*/**}/*_spec.rb" end namespace :integration do container_name = 'itamae' task :all => ['spec:integration:docker' 'spec:integration:local'] desc "Run provision and specs" task :docker => ["docker:boot", "docker:provision", "docker:serverspec", 'docker:clean_docker_container'] namespace :docker do desc "Run docker" task :boot do sh "docker run --privileged -d --name #{container_name} #{TEST_IMAGE} /sbin/init" end desc "Run itamae" task :provision do suites = [ [ "spec/integration/recipes/default.rb", "spec/integration/recipes/default2.rb", "spec/integration/recipes/redefine.rb", "spec/integration/recipes/docker.rb", ], [ "--dry-run", "spec/integration/recipes/dry_run.rb", ], ] suites.each do |suite| cmd = %w!bundle exec ruby -w bin/itamae docker! cmd << "-l" << (ENV['LOG_LEVEL'] || 'debug') cmd << "-j" << "spec/integration/recipes/node.json" cmd << "--container" << container_name cmd << "--tag" << "itamae:latest" cmd << "--tmp-dir" << (ENV['ITAMAE_TMP_DIR'] || '/tmp/itamae_tmp') cmd += suite p cmd unless system(*cmd) raise "#{cmd} failed" end end end desc "Run serverspec tests" RSpec::Core::RakeTask.new(:serverspec) do |t| ENV['DOCKER_CONTAINER'] = container_name t.ruby_opts = '-I ./spec/integration' t.pattern = "spec/integration/{default,docker}_spec.rb" end desc 'Clean a docker container for test' task :clean_docker_container do sh('docker', 'rm', '-f', container_name) end end end end task :default => :spec itamae-1.12.5/lib/0000755000004100000410000000000014167043240013637 5ustar www-datawww-dataitamae-1.12.5/lib/itamae/0000755000004100000410000000000014167043240015077 5ustar www-datawww-dataitamae-1.12.5/lib/itamae/generators.rb0000644000004100000410000000057014167043240017577 0ustar www-datawww-datarequire "itamae/generators/cookbook" require "itamae/generators/project" require "itamae/generators/role" module Itamae module Generators def self.find(target) case target when 'cookbook' Cookbook when 'project' Project when 'role' Role else raise "Unexpected target: #{target}" end end end end itamae-1.12.5/lib/itamae/runner.rb0000644000004100000410000000632514167043240016743 0ustar www-datawww-datarequire 'json' require 'yaml' module Itamae class Runner class << self def run(recipe_files, backend_type, options) Itamae.logger.info "Starting Itamae... #{options[:dry_run] ? '(dry-run)' : ''}" backend = Backend.create(backend_type, options) runner = self.new(backend, options) runner.load_recipes(recipe_files) runner.run runner end end attr_reader :backend attr_reader :options attr_reader :node attr_reader :tmpdir attr_reader :children attr_reader :handler def initialize(backend, options) @backend = backend @options = options prepare_handler @node = create_node @tmpdir = options[:tmp_dir] || '/tmp/itamae_tmp' @children = RecipeChildren.new @diff = false @backend.run_command(["mkdir", "-p", @tmpdir]) @backend.run_command(["chmod", "777", @tmpdir]) end def load_recipes(paths) paths.each do |path| expanded_path = File.expand_path(path) if path.include?('::') gem_path = Recipe.find_recipe_in_gem(path) expanded_path = gem_path if gem_path end recipe = Recipe.new(self, expanded_path) children << recipe recipe.load end end def run if recipe_graph_file = options[:recipe_graph] save_dependency_graph(recipe_graph_file) end children.run @backend.finalize if profile = options[:profile] save_profile(profile) end end def dry_run? @options[:dry_run] end def save_dependency_graph(path) Itamae.logger.info "Writing recipe dependency graph to #{path}..." open(path, 'w') do |f| f.write(children.dependency_in_dot) end end def save_profile(path) open(path, 'w', 0600) do |f| f.write(@backend.executed_commands.to_json) end end def diff? @diff end def diff_found! @diff = true end private def create_node hash = {} if @options[:ohai] unless @backend.run_command("which ohai", error: false).exit_status == 0 # install Ohai Itamae.logger.info "Installing Chef package... (to use Ohai)" @backend.run_command("curl -L https://omnitruck.chef.io/install.sh | bash") end Itamae.logger.info "Loading node data via ohai..." hash.merge!(JSON.parse(@backend.run_command("ohai 2>/dev/null").stdout)) end if @options[:node_json] path = File.expand_path(@options[:node_json]) Itamae.logger.info "Loading node data from #{path}..." hash.merge!(JSON.load(open(path))) end if @options[:node_yaml] path = File.expand_path(@options[:node_yaml]) Itamae.logger.info "Loading node data from #{path}..." hash.merge!(YAML.load(open(path)) || {}) end Node.new(hash, @backend) end def prepare_handler @handler = HandlerProxy.new (@options[:handlers] || []).each do |handler| type = handler.delete('type') unless type raise "#{type} field is not set" end @handler.register_instance(Handler.from_type(type).new(handler)) end end end end itamae-1.12.5/lib/itamae/version.rb0000644000004100000410000000004714167043240017112 0ustar www-datawww-datamodule Itamae VERSION = "1.12.5" end itamae-1.12.5/lib/itamae/handler_proxy.rb0000644000004100000410000000125414167043240020304 0ustar www-datawww-datamodule Itamae class HandlerProxy def initialize @instances = [] end def register_instance(instance) @instances << instance end def event(*args, &block) if block_given? _event_with_block(*args, &block) else _event(*args) end end private def _event(*args) @instances.each do |i| i.event(*args) end end def _event_with_block(event_name, *args, &block) event("#{event_name}_started".to_sym, *args) block.call rescue event("#{event_name}_failed".to_sym, *args) raise else event("#{event_name}_completed".to_sym, *args) end end end itamae-1.12.5/lib/itamae/recipe_children.rb0000644000004100000410000000366414167043240020554 0ustar www-datawww-datamodule Itamae class RecipeChildren < Array NotFoundError = Class.new(StandardError) def find_resource_by_description(desc) # desc is like 'resource_type[name]' resources.find do |resource| type, name = Itamae::Resource.parse_description(desc) resource.resource_type == type && resource.resource_name == name end.tap do |resource| unless resource raise NotFoundError, "'#{desc}' resource is not found." end end end def subscribing(target) resources.map do |resource| resource.subscriptions.select do |subscription| subscription.resource == target end end.flatten end def find_recipe_by_path(path) recipes.find do |recipe| recipe.path == path end end def resources self.map do |item| case item when Resource::Base item when Recipe item.children.resources end end.flatten end def recipes(options = {}) options = {recursive: true}.merge(options) self.select do |item| item.is_a?(Recipe) end.map do |recipe| if options[:recursive] [recipe] + recipe.children.recipes else recipe end end.flatten end def run self.each do |resource| resource.run end end # returns dependencies graph in DOT def dependency_in_dot result = "" result << "digraph recipes {\n" result << " rankdir=LR;\n" result << _dependency_in_dot result << "}" result end def _dependency_in_dot result = "" recipes(recursive: false).each do |recipe| recipe.children.recipes(recursive: false).each do |child_recipe| result << %{ "#{recipe.path}" -> "#{child_recipe.path}";\n} end result << recipe.children._dependency_in_dot end result end end end itamae-1.12.5/lib/itamae/handler/0000755000004100000410000000000014167043240016514 5ustar www-datawww-dataitamae-1.12.5/lib/itamae/handler/base.rb0000644000004100000410000000152414167043240017755 0ustar www-datawww-datarequire 'socket' module Itamae module Handler class Base attr_reader :recipes, :resources, :actions def initialize(options) @options = options @recipes = [] @resources = [] @actions = [] end def event(type, payload = {}) case type when :recipe_started @recipes << payload when :recipe_completed, :recipe_failed @recipes.pop when :resource_started @resources << payload when :resource_completed, :resource_failed @resources.pop when :action_started @actions << payload when :action_completed, :action_failed @actions.pop end end private def hostname @hostname ||= @options['hostname'] || Socket.gethostname end end end end itamae-1.12.5/lib/itamae/handler/json.rb0000644000004100000410000000062714167043240020017 0ustar www-datawww-datamodule Itamae module Handler class Json < Base def initialize(*) super require 'time' open_file end def event(type, payload = {}) super @f.puts({'time' => Time.now.iso8601, 'event' => type, 'payload' => payload}.to_json) end private def open_file @f = open(@options.fetch('path'), 'a') end end end end itamae-1.12.5/lib/itamae/handler/debug.rb0000644000004100000410000000027314167043240020131 0ustar www-datawww-datamodule Itamae module Handler class Debug < Base def event(type, payload = {}) super Itamae.logger.info("EVENT:#{type} #{payload}") end end end end itamae-1.12.5/lib/itamae/handler/fluentd.rb0000644000004100000410000000200714167043240020501 0ustar www-datawww-datamodule Itamae module Handler class Fluentd < Base attr_accessor :fluent_logger # for test def initialize(*) super load_fluent_logger end def event(type, payload = {}) super unless @fluent_logger.post(type, payload.merge(hostname: hostname)) Itamae.logger.warn "Sending logs to Fluentd failed: #{@fluent_logger.last_error}" end end private def load_fluent_logger begin require 'fluent-logger' rescue LoadError raise "Loading fluent-logger gem failed. Please install 'fluent-logger' gem to use fluentd handler." end @fluent_logger = Fluent::Logger::FluentLogger.new(tag_prefix, host: fluentd_host, port: fluentd_port) end def tag_prefix @options['tag_prefix'] || 'itamae_server' end def fluentd_host @options['host'] || 'localhost' end def fluentd_port (@options['port'] || 24224).to_i end end end end itamae-1.12.5/lib/itamae/generators/0000755000004100000410000000000014167043240017250 5ustar www-datawww-dataitamae-1.12.5/lib/itamae/generators/cookbook.rb0000644000004100000410000000055014167043240021403 0ustar www-datawww-datarequire 'thor' require 'thor/group' module Itamae module Generators class Cookbook < Thor::Group include Thor::Actions def self.source_root File.expand_path('../templates/cookbook', __FILE__) end def copy_files directory '.' end def remove_files remove_file '.' end end end end itamae-1.12.5/lib/itamae/generators/templates/0000755000004100000410000000000014167043240021246 5ustar www-datawww-dataitamae-1.12.5/lib/itamae/generators/templates/role/0000755000004100000410000000000014167043240022207 5ustar www-datawww-dataitamae-1.12.5/lib/itamae/generators/templates/role/default.rb0000644000004100000410000000000014167043240024146 0ustar www-datawww-dataitamae-1.12.5/lib/itamae/generators/templates/role/templates/0000755000004100000410000000000014167043240024205 5ustar www-datawww-dataitamae-1.12.5/lib/itamae/generators/templates/role/templates/.keep0000644000004100000410000000000014167043240025120 0ustar www-datawww-dataitamae-1.12.5/lib/itamae/generators/templates/role/files/0000755000004100000410000000000014167043240023311 5ustar www-datawww-dataitamae-1.12.5/lib/itamae/generators/templates/role/files/.keep0000644000004100000410000000000014167043240024224 0ustar www-datawww-dataitamae-1.12.5/lib/itamae/generators/templates/cookbook/0000755000004100000410000000000014167043240023054 5ustar www-datawww-dataitamae-1.12.5/lib/itamae/generators/templates/cookbook/default.rb0000644000004100000410000000000014167043240025013 0ustar www-datawww-dataitamae-1.12.5/lib/itamae/generators/templates/cookbook/templates/0000755000004100000410000000000014167043240025052 5ustar www-datawww-dataitamae-1.12.5/lib/itamae/generators/templates/cookbook/templates/.keep0000644000004100000410000000000014167043240025765 0ustar www-datawww-dataitamae-1.12.5/lib/itamae/generators/templates/cookbook/files/0000755000004100000410000000000014167043240024156 5ustar www-datawww-dataitamae-1.12.5/lib/itamae/generators/templates/cookbook/files/.keep0000644000004100000410000000000014167043240025071 0ustar www-datawww-dataitamae-1.12.5/lib/itamae/generators/templates/project/0000755000004100000410000000000014167043240022714 5ustar www-datawww-dataitamae-1.12.5/lib/itamae/generators/templates/project/roles/0000755000004100000410000000000014167043240024040 5ustar www-datawww-dataitamae-1.12.5/lib/itamae/generators/templates/project/roles/.keep0000644000004100000410000000000014167043240024753 0ustar www-datawww-dataitamae-1.12.5/lib/itamae/generators/templates/project/cookbooks/0000755000004100000410000000000014167043240024705 5ustar www-datawww-dataitamae-1.12.5/lib/itamae/generators/templates/project/cookbooks/.keep0000644000004100000410000000000014167043240025620 0ustar www-datawww-dataitamae-1.12.5/lib/itamae/generators/templates/project/Gemfile0000644000004100000410000000007714167043240024213 0ustar www-datawww-datasource 'https://rubygems.org' gem 'itamae' # gem 'serverspec' itamae-1.12.5/lib/itamae/generators/role.rb0000644000004100000410000000054014167043240020535 0ustar www-datawww-datarequire 'thor' require 'thor/group' module Itamae module Generators class Role < Thor::Group include Thor::Actions def self.source_root File.expand_path('../templates/role', __FILE__) end def copy_files directory '.' end def remove_files remove_file '.' end end end end itamae-1.12.5/lib/itamae/generators/project.rb0000644000004100000410000000054014167043240021242 0ustar www-datawww-datarequire 'thor' require 'thor/group' module Itamae module Generators class Project < Thor::Group include Thor::Actions def self.source_root File.dirname(__FILE__) + '/templates/project' end def copy_files directory '.' end def bundle run 'bundle install' end end end end itamae-1.12.5/lib/itamae/recipe.rb0000644000004100000410000001232614167043240016677 0ustar www-datawww-datamodule Itamae class Recipe NotFoundError = Class.new(StandardError) attr_reader :path attr_reader :runner attr_reader :children attr_reader :delayed_notifications class << self def find_recipe_in_gem(recipe) plugin_name, recipe_file = recipe.split('::', 2) recipe_file = recipe_file.gsub("::", "/") if recipe_file gem_name = "itamae-plugin-recipe-#{plugin_name}" begin gem gem_name rescue LoadError end spec = Gem.loaded_specs.values.find do |spec| spec.name == gem_name end return nil unless spec candidate_files = [] if recipe_file recipe_file += '.rb' unless recipe_file.end_with?('.rb') candidate_files << "#{plugin_name}/#{recipe_file}" else candidate_files << "#{plugin_name}/default.rb" candidate_files << "#{plugin_name}.rb" end candidate_files.map do |file| File.join(spec.lib_dirs_glob, 'itamae', 'plugin', 'recipe', file) end.find do |path| File.exist?(path) end end end def initialize(runner, path) @runner = runner @path = path @delayed_notifications = [] @children = RecipeChildren.new end def dir ::File.dirname(@path) end def load(vars = {}) context = EvalContext.new(self, vars) InstanceEval.new(File.read(path), path, 1, context: context).call end def run show_banner @runner.handler.event(:recipe, path: @path) do Itamae.logger.with_indent do @children.run run_delayed_notifications end end end private def run_delayed_notifications @delayed_notifications.uniq! do |notification| [notification.action, notification.action_resource] end while notification = @delayed_notifications.shift notification.run end end def show_banner Itamae.logger.info "Recipe: #{@path}" end class EvalContext def initialize(recipe, vars) @recipe = recipe vars.each do |k, v| define_singleton_method(k) { v } end end def respond_to_missing?(method, include_private = false) Resource.get_resource_class(method) true rescue NameError false end def method_missing(*args, &block) super unless args.size == 2 method, name = args begin klass = Resource.get_resource_class(method) rescue NameError super end resource = klass.new(@recipe, name, &block) @recipe.children << resource end def define(name, params = {}, &block) Resource.define_resource(name, Definition.create_class(name, params, @recipe, &block)) end def include_recipe(target) expanded_path = ::File.expand_path(target, File.dirname(@recipe.path)) expanded_path = ::File.join(expanded_path, 'default.rb') if ::Dir.exist?(expanded_path) expanded_path.concat('.rb') unless expanded_path.end_with?('.rb') candidate_paths = [expanded_path, Recipe.find_recipe_in_gem(target)].compact path = candidate_paths.find {|path| File.exist?(path) } unless path raise NotFoundError, "Recipe not found. (#{target})" end if runner.children.find_recipe_by_path(path) Itamae.logger.debug "Recipe, #{path}, is skipped because it is already included" return end recipe = Recipe.new(runner, path) @recipe.children << recipe recipe.load end def node runner.node end def runner @recipe.runner end def run_command(*args) runner.backend.run_command(*args) end end class InstanceEval def initialize(src, path, lineno, context:) # Using instance_eval + eval to allow top-level class/module definition without `::`. # To pass args without introducing any local/instance variables, this code is also eval-ed. @code = <<-RUBY @context.instance_eval do eval(#{src.dump}, nil, #{path.dump}, #{lineno}) end RUBY @context = context end # This method has no local variables to avoid spilling them to recipes. def call eval(@code) end end private_constant :InstanceEval class RecipeFromDefinition < Recipe attr_accessor :definition def load(vars = {}) context = EvalContext.new(self, vars) context.instance_eval(&@definition.class.definition_block) end def run if @definition.do_not_run_because_of_only_if? Itamae.logger.debug "#{@definition.resource_type}[#{@definition.resource_name}] Execution skipped because of only_if attribute" return elsif @definition.do_not_run_because_of_not_if? Itamae.logger.debug "#{@definition.resource_type}[#{@definition.resource_name}] Execution skipped because of not_if attribute" return end super end private def show_banner Itamae.logger.debug "#{@definition.resource_type}[#{@definition.resource_name}]" end end end end itamae-1.12.5/lib/itamae/ext.rb0000644000004100000410000000003714167043240016224 0ustar www-datawww-datarequire 'itamae/ext/specinfra' itamae-1.12.5/lib/itamae/node.rb0000644000004100000410000000303414167043240016351 0ustar www-datawww-datarequire 'hashie' require 'json' require 'schash' require 'itamae/mash' module Itamae class Node ValidationError = Class.new(StandardError) attr_reader :mash def initialize(hash, backend) @mash = Itamae::Mash.new(hash) @backend = backend end def reverse_merge(other_hash) self.class.new(_reverse_merge(other_hash), @backend) end def reverse_merge!(other_hash) @mash.replace(_reverse_merge(other_hash)) end def [](key) if @mash.has_key?(key) @mash[key] else fetch_inventory_value(key) end end def validate!(&block) errors = Schash::Validator.new(&block).validate(@mash) unless errors.empty? errors.each do |error| Itamae.logger.error "'#{error.position.join('->')}' #{error.message}" end raise ValidationError end end private def _reverse_merge(other_hash) Itamae::Mash.new(other_hash).merge(@mash) end def method_missing(method, *args) if @mash.respond_to?(method) return @mash.public_send(method, *args) elsif args.empty? && value = fetch_inventory_value(method) return value end super end def respond_to?(method, priv = false) @mash.respond_to?(method, priv) || super end def fetch_inventory_value(key) value = @backend.host_inventory[key] if value.is_a?(Hash) value = Itamae::Mash.new(value) end value rescue NotImplementedError, NameError nil end end end itamae-1.12.5/lib/itamae/resource.rb0000644000004100000410000000375014167043240017260 0ustar www-datawww-datarequire 'itamae/resource/base' require 'itamae/resource/file' require 'itamae/resource/package' require 'itamae/resource/remote_directory' require 'itamae/resource/remote_file' require 'itamae/resource/directory' require 'itamae/resource/template' require 'itamae/resource/http_request' require 'itamae/resource/execute' require 'itamae/resource/service' require 'itamae/resource/link' require 'itamae/resource/local_ruby_block' require 'itamae/resource/git' require 'itamae/resource/user' require 'itamae/resource/group' require 'itamae/resource/gem_package' module Itamae module Resource Error = Class.new(StandardError) AttributeMissingError = Class.new(StandardError) InvalidTypeError = Class.new(StandardError) ParseError = Class.new(StandardError) class << self def to_camel_case(str) str.split('_').map {|part| part.capitalize}.join end def get_resource_class(method) begin self.const_get(to_camel_case(method.to_s)) rescue NameError begin ::Itamae::Plugin::Resource.const_get(to_camel_case(method.to_s)) rescue NameError autoload_plugin_resource(method) end end end def autoload_plugin_resource(method) begin require "itamae/plugin/resource/#{method}" ::Itamae::Plugin::Resource.const_get(to_camel_case(method.to_s)) rescue LoadError, NameError raise Error, "#{method} resource is missing." end end def define_resource(name, klass) class_name = to_camel_case(name.to_s) if Resource.const_defined?(class_name) Itamae.logger.warn "Redefine class. (#{class_name})" return end Resource.const_set(class_name, klass) end def parse_description(desc) if /\A([^\[]+)\[([^\]]+)\]\z/ =~ desc [$1, $2] else raise ParseError, "'#{desc}' doesn't represent a resource." end end end end end itamae-1.12.5/lib/itamae/definition.rb0000644000004100000410000000157514167043240017564 0ustar www-datawww-datamodule Itamae class Definition < Resource::Base class << self attr_accessor :definition_block attr_accessor :defined_in_recipe def create_class(name, params, defined_in_recipe, &block) Class.new(self).tap do |klass| klass.definition_block = block klass.defined_in_recipe = defined_in_recipe klass.define_attribute :action, default: :run params.each_pair do |key, value| klass.define_attribute key.to_sym, type: Object, default: value end end end end def initialize(*args) super r = Recipe::RecipeFromDefinition.new( runner, self.class.defined_in_recipe.path, ) recipe.children << r r.definition = self r.load(params: @attributes.merge(name: resource_name)) end def run(*args) # nothing end end end itamae-1.12.5/lib/itamae/cli.rb0000644000004100000410000001171114167043240016174 0ustar www-datawww-datarequire 'itamae' require 'thor' module Itamae class CLI < Thor GENERATE_TARGETS = %w[cookbook role].freeze def initialize(*) super Itamae.logger.level = ::Logger.const_get(options[:log_level].upcase) if options[:log_level] Itamae.logger.formatter.colored = options[:color] if options[:color] end def self.define_exec_options option :recipe_graph, type: :string, desc: "[EXPERIMENTAL] Write recipe dependency graph in DOT", banner: "PATH" option :node_json, type: :string, aliases: ['-j'] option :node_yaml, type: :string, aliases: ['-y'] option :dry_run, type: :boolean, aliases: ['-n'] option :shell, type: :string, default: "/bin/sh" option :login_shell, type: :boolean, default: false option :ohai, type: :boolean, default: false, desc: "This option is DEPRECATED and will be unavailable." option :profile, type: :string, desc: "[EXPERIMENTAL] Save profiling data", banner: "PATH" option :detailed_exitcode, type: :boolean, default: false, desc: "exit code 0 - The run succeeded with no changes or failures, exit code 1 - The run failed, exit code 2 - The run succeeded, and some resources were changed" option :log_level, type: :string, aliases: ['-l'], default: 'info' option :color, type: :boolean, default: true option :config, type: :string, aliases: ['-c'] option :tmp_dir, type: :string, aliases: ['-t'], default: "/tmp/itamae_tmp" end def self.options @itamae_options ||= super.dup.tap do |options| if config = options[:config] options.merge!(YAML.load_file(config)) end end end def self.exit_on_failure? true end desc "local RECIPE [RECIPE...]", "Run Itamae locally" define_exec_options def local(*recipe_files) if recipe_files.empty? raise "Please specify recipe files." end run(recipe_files, :local, options) end desc "ssh RECIPE [RECIPE...]", "Run Itamae via ssh" define_exec_options option :host, type: :string, aliases: ['-h'] option :user, type: :string, aliases: ['-u'] option :key, type: :string, aliases: ['-i'] option :port, type: :numeric, aliases: ['-p'] option :ssh_config, type: :string option :vagrant, type: :boolean, default: false option :ask_password, type: :boolean, default: false option :sudo, type: :boolean, default: true def ssh(*recipe_files) if recipe_files.empty? raise "Please specify recipe files." end unless options[:host] || options[:vagrant] raise "Please set '-h ' or '--vagrant'" end run(recipe_files, :ssh, options) end desc "docker RECIPE [RECIPE...]", "Create Docker image" define_exec_options option :image, type: :string, desc: "This option or 'container' option is required." option :container, type: :string, desc: "This option or 'image' option is required." option :tls_verify_peer, type: :boolean, default: true option :tag, type: :string, desc: 'Tag name of created docker image.' def docker(*recipe_files) if recipe_files.empty? raise "Please specify recipe files." end run(recipe_files, :docker, options) end desc "jail RECIPE [RECIPE...]", "Run Itamae in jail" define_exec_options option :jail_name, type: :string, desc: "Jail Hostname" def jail(*recipe_files) if recipe_files.empty? raise "Please specify recipe files." end run(recipe_files, :jexec, options) end desc "version", "Print version" def version puts "Itamae v#{Itamae::VERSION}" end desc "init NAME", "Create a new project" def init(name) generator = Generators::Project.new generator.destination_root = name generator.invoke_all end desc 'generate [cookbook|role] [NAME]', 'Initialize role or cookbook (short-cut alias: "g")' map 'g' => 'generate' def generate(target, name) validate_generate_target!('generate', target) generator = Generators.find(target).new generator.destination_root = File.join("#{target}s", name) generator.copy_files end desc 'destroy [cookbook|role] [NAME]', 'Undo role or cookbook (short-cut alias: "d")' map 'd' => 'destroy' def destroy(target, name) validate_generate_target!('destroy', target) generator = Generators.find(target).new generator.destination_root = File.join("#{target}s", name) generator.remove_files end private def validate_generate_target!(command, target) unless GENERATE_TARGETS.include?(target) msg = %Q!ERROR: "itamae #{command}" was called with "#{target}" ! msg << "but expected to be in #{GENERATE_TARGETS.inspect}" fail InvocationError, msg end end def run(recipe_files, backend_type, options) runner = Runner.run(recipe_files, backend_type, options) if options[:detailed_exitcode] && runner.diff? exit 2 end end end end itamae-1.12.5/lib/itamae/backend.rb0000644000004100000410000002157114167043240017021 0ustar www-datawww-datarequire 'specinfra/core' require 'singleton' require 'io/console' require 'net/ssh' Specinfra::Configuration.error_on_missing_backend_type = true module Specinfra module Configuration def self.sudo_password return ENV['SUDO_PASSWORD'] if ENV['SUDO_PASSWORD'] return @sudo_password if defined?(@sudo_password) # TODO: Fix this dirty hack return nil unless caller.any? {|call| call.include?('channel_data') } print "sudo password: " @sudo_password = STDIN.noecho(&:gets).strip print "\n" @sudo_password end end end module Itamae module Backend UnknownBackendTypeError = Class.new(StandardError) CommandExecutionError = Class.new(StandardError) SourceNotExistError = Class.new(StandardError) class << self def create(type, opts = {}) self.const_get(type.capitalize).new(opts) end end class Base attr_reader :executed_commands def initialize(options) @options = options @backend = create_specinfra_backend @executed_commands = [] end def run_command(commands, options = {}) options = {error: true}.merge(options) command = build_command(commands, options) Itamae.logger.debug "Executing `#{command}`..." result = nil Itamae.logger.with_indent do reset_output_handler result = run_command_with_profiling(command) flush_output_handler_buffer if result.exit_status == 0 || !options[:error] method = :debug message = "exited with #{result.exit_status}" else method = :error message = "Command `#{command}` failed. (exit status: #{result.exit_status})" unless Itamae.logger.level == ::Logger::DEBUG result.stdout.each_line do |l| log_output_line("stdout", l, :error) end result.stderr.each_line do |l| log_output_line("stderr", l, :error) end end end Itamae.logger.public_send(method, message) end if options[:error] && result.exit_status != 0 raise CommandExecutionError end result end def get_command(*args) @backend.command.get(*args) end def receive_file(src, dst = nil) if dst Itamae.logger.debug "Receiving a file from '#{src}' to '#{dst}'..." else Itamae.logger.debug "Receiving a file from '#{src}'..." end @backend.receive_file(src, dst) end def send_file(src, dst, user: nil) Itamae.logger.debug "Sending a file from '#{src}' to '#{dst}'..." unless ::File.exist?(src) raise SourceNotExistError, "The file '#{src}' doesn't exist." end unless ::File.file?(src) raise SourceNotExistError, "'#{src}' is not a file." end if self.instance_of?(Backend::Local) read_command = build_command("cat #{src.shellescape}", {}) write_command = build_command("cat > #{dst.shellescape}", user: user) command = [read_command, write_command].join(' | ') run_command(command) else @backend.send_file(src, dst) end end def send_directory(src, dst) Itamae.logger.debug "Sending a directory from '#{src}' to '#{dst}'..." unless ::File.exist?(src) raise SourceNotExistError, "The directory '#{src}' doesn't exist." end unless ::File.directory?(src) raise SourceNotExistError, "'#{src}' is not a directory." end @backend.send_directory(src, dst) end def host_inventory @backend.host_inventory end def finalize # pass end private def create_specinfra_backend raise NotImplementedError end def reset_output_handler @buf = {} %w!stdout stderr!.each do |output_name| @buf[output_name] = "" handler = lambda do |str| lines = str.split(/\r?\n/, -1) @buf[output_name] += lines.pop unless lines.empty? lines[0] = @buf[output_name] + lines[0] @buf[output_name] = "" lines.each do |l| log_output_line(output_name, l) end end end @backend.public_send("#{output_name}_handler=", handler) end end def flush_output_handler_buffer @buf.each do |output_name, line| next if line.empty? log_output_line(output_name, line) end end def log_output_line(output_name, line, severity = :debug) line = line.gsub(/[[:cntrl:]]/, '') Itamae.logger.public_send(severity, "#{output_name} | #{line}") end def build_command(commands, options) if commands.is_a?(Array) command = commands.map do |cmd| cmd.shellescape end.join(' ') else command = commands end cwd = options[:cwd] if cwd command = "cd #{cwd.shellescape} && #{command}" end user = options[:user] if user && use_sudo? command = "cd ~#{user.shellescape} ; #{command}" command = "sudo -H -u #{user.shellescape} -- #{shell.shellescape} -c #{command.shellescape}" end command end def use_sudo? return true unless @options.key?(:sudo) !!@options[:sudo] end def shell @options[:shell] || '/bin/sh' end def run_command_with_profiling(command) start_at = Time.now result = @backend.run_command(command) duration = Time.now.to_f - start_at.to_f @executed_commands << {command: command, duration: duration} result end end class Local < Base private def create_specinfra_backend Specinfra::Backend::Exec.new( shell: @options[:shell], ) end end class Jexec < Base private def create_specinfra_backend Specinfra::Backend::Jexec.new( shell: @options[:shell], login_shell: @options[:login_shell], jail_name: @options[:jail_name], ) end end class Ssh < Base private def create_specinfra_backend Specinfra::Backend::Ssh.new( request_pty: true, host: ssh_options[:host_name], disable_sudo: disable_sudo?, ssh_options: ssh_options, shell: @options[:shell], login_shell: @options[:login_shell], ) end def ssh_options opts = {} opts[:host_name] = @options[:host] # from ssh-config ssh_config_files = @options[:ssh_config] ? [@options[:ssh_config]] : Net::SSH::Config.default_files opts.merge!(Net::SSH::Config.for(@options[:host], ssh_config_files)) opts[:user] = @options[:user] || opts[:user] || Etc.getlogin opts[:password] = @options[:password] if @options[:password] opts[:keys] = [@options[:key]] if @options[:key] opts[:port] = @options[:port] if @options[:port] if @options[:vagrant] config = Tempfile.new('', Dir.tmpdir) hostname = opts[:host_name] || 'default' vagrant_cmd = "vagrant ssh-config #{hostname} > #{config.path}" if defined?(Bundler) Bundler.with_clean_env do `#{vagrant_cmd}` end else `#{vagrant_cmd}` end opts.merge!(Net::SSH::Config.for(hostname, [config.path])) end if @options[:ask_password] print "password: " password = STDIN.noecho(&:gets).strip print "\n" opts.merge!(password: password) end opts end def disable_sudo? !@options[:sudo] end end class Docker < Base def finalize image = @backend.commit_container /\A(?.+?)(?:|:(?[^:]+))\z/.match(@options[:tag]) do |m| image.tag(repo: m[:repo], tag: m[:tag]) end log_message = "Image created: #{image.id}" log_message << ", and tagged as #{@options[:tag]}" if @options[:tag] Itamae.logger.info log_message end private def create_specinfra_backend begin require 'docker' rescue LoadError Itamae.logger.fatal "To use docker backend, please install 'docker-api' gem" end # TODO: Move to Specinfra? Excon.defaults[:ssl_verify_peer] = @options[:tls_verify_peer] ::Docker.logger = Itamae.logger Specinfra::Backend::Docker.new( docker_image: @options[:image], docker_container: @options[:container], shell: @options[:shell], docker_container_create_options: @options[:docker_container_create_options], ) end end end end itamae-1.12.5/lib/itamae/resource/0000755000004100000410000000000014167043240016726 5ustar www-datawww-dataitamae-1.12.5/lib/itamae/resource/template.rb0000644000004100000410000000221214167043240021063 0ustar www-datawww-datarequire 'erb' require 'tempfile' module Itamae module Resource class Template < RemoteFile define_attribute :variables, type: Hash, default: {} def pre_action attributes.content = RenderContext.new(self).render_file(source_file) super end private def content_file nil end def source_file_dir "templates" end def source_file_exts [".erb", ""] end class RenderContext def initialize(resource) @resource = resource @resource.attributes.variables.each_pair do |key, value| instance_variable_set("@#{key}".to_sym, value) end end def render_file(src) template = ::File.read(src) erb = if ERB.instance_method(:initialize).parameters.assoc(:key) # Ruby 2.6+ ERB.new(template, trim_mode: '-') else ERB.new(template, nil, '-') end erb.filename = src erb.result(binding) end def node @resource.recipe.runner.node end end end end end itamae-1.12.5/lib/itamae/resource/remote_file.rb0000644000004100000410000000237014167043240021547 0ustar www-datawww-datamodule Itamae module Resource class RemoteFile < File SourceNotFoundError = Class.new(StandardError) define_attribute :source, type: [String, Symbol], default: :auto private def content_file source_file end def source_file @source_file ||= find_source_file end def find_source_file if attributes.source == :auto dirs = attributes.path.split(::File::SEPARATOR) dirs.shift if dirs.first == "" searched_paths = [] dirs.size.times do |i| source_file_exts.each do |ext| path = ::File.join(@recipe.dir, source_file_dir, "#{dirs[i..-1].join("/")}#{ext}") if ::File.exist?(path) Itamae.logger.debug "#{path} is used as a source file." return path else searched_paths << path end end end raise SourceNotFoundError, "source file is not found (searched paths: #{searched_paths.join(', ')})" else ::File.expand_path(attributes.source, @recipe.dir) end end def source_file_dir "files" end def source_file_exts [""] end end end end itamae-1.12.5/lib/itamae/resource/group.rb0000644000004100000410000000177314167043240020417 0ustar www-datawww-datamodule Itamae module Resource class Group < Base define_attribute :action, default: :create define_attribute :groupname, type: String, default_name: true define_attribute :gid, type: Integer def set_current_attributes current.exist = exist? if current.exist current.gid = run_specinfra(:get_group_gid, attributes.groupname).stdout.strip.to_i end end def action_create(options) if run_specinfra(:check_group_exists, attributes.groupname) if attributes.gid && attributes.gid != current.gid run_specinfra(:update_group_gid, attributes.groupname, attributes.gid) updated! end else options = { gid: attributes.gid, } run_specinfra(:add_group, attributes.groupname, options) updated! end end private def exist? run_specinfra(:check_group_exists, attributes.groupname) end end end end itamae-1.12.5/lib/itamae/resource/http_request.rb0000644000004100000410000000525214167043240022006 0ustar www-datawww-datarequire 'uri' require 'net/https' module Itamae module Resource class HttpRequest < File RedirectLimitExceeded = Class.new(StandardError) HTTPClientError = Class.new(StandardError) HTTPServerError = Class.new(StandardError) HTTPUnknownError = Class.new(StandardError) alias_method :_action_create, :action_create undef_method :action_create, :action_delete, :action_edit define_attribute :action, default: :get define_attribute :headers, type: Hash, default: {} define_attribute :message, type: String, default: "" define_attribute :redirect_limit, type: Integer, default: 10 define_attribute :url, type: String, required: true def pre_action attributes.exist = true attributes.content = fetch_content send_tempfile compare_file end def show_differences current.mode = current.mode.rjust(4, '0') if current.mode attributes.mode = attributes.mode.rjust(4, '0') if attributes.mode super show_content_diff end def fetch_content uri = URI.parse(attributes.url) response = nil redirects_followed = 0 loop do http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true if uri.scheme == "https" case attributes.action when :delete, :get, :options response = http.method(attributes.action).call(uri.request_uri, attributes.headers) when :post, :put response = http.method(attributes.action).call(uri.request_uri, attributes.message, attributes.headers) end case response when Net::HTTPSuccess break when Net::HTTPRedirection if redirects_followed < attributes.redirect_limit uri = URI.parse(response["location"]) redirects_followed += 1 Itamae.logger.debug "Following redirect #{redirects_followed}/#{attributes.redirect_limit}" else raise RedirectLimitExceeded end when Net::HTTPClientError raise HTTPClientError when Net::HTTPServerError raise HTTPServerError else raise HTTPUnknownError end end response.body end def action_delete(options) _action_create(options) end def action_get(options) _action_create(options) end def action_options(options) _action_create(options) end def action_post(options) _action_create(options) end def action_put(options) _action_create(options) end end end end itamae-1.12.5/lib/itamae/resource/execute.rb0000644000004100000410000000075214167043240020721 0ustar www-datawww-datamodule Itamae module Resource class Execute < Base define_attribute :action, default: :run define_attribute :command, type: String, default_name: true def pre_action case @current_action when :run attributes.executed = true end end def set_current_attributes current.executed = false end def action_run(options) run_command(attributes.command) updated! end end end end itamae-1.12.5/lib/itamae/resource/gem_package.rb0000644000004100000410000000524114167043240021500 0ustar www-datawww-datamodule Itamae module Resource class GemPackage < Base define_attribute :action, default: :install define_attribute :package_name, type: String, default_name: true define_attribute :gem_binary, type: [String, Array], default: 'gem' define_attribute :options, type: [String, Array], default: [] define_attribute :version, type: String define_attribute :source, type: String def pre_action case @current_action when :install attributes.installed = true when :uninstall attributes.installed = false end end def set_current_attributes installed = installed_gems.find {|g| g[:name] == attributes.package_name } current.installed = !!installed if current.installed versions = installed[:versions] if versions.include?(attributes.version) current.version = attributes.version else current.version = versions.first end end end def action_install(action_options) if current.installed if attributes.version && current.version != attributes.version install! updated! end else install! updated! end end def action_uninstall(action_options) uninstall! if current.installed end def action_upgrade(action_options) return if current.installed && attributes.version && current.version == attributes.version install! updated! end def installed_gems gems = [] run_command([*Array(attributes.gem_binary), 'list', '-l']).stdout.each_line do |line| if /\A([^ ]+) \(([^\)]+)\)\z/ =~ line.strip name = $1 versions = $2.split(', ') gems << {name: name, versions: versions} end end gems rescue Backend::CommandExecutionError [] end def install! cmd = [*Array(attributes.gem_binary), 'install', *Array(attributes.options)] if attributes.version cmd << '-v' << attributes.version end if attributes.source cmd << '--source' << attributes.source end cmd << attributes.package_name run_command(cmd) end def uninstall! cmd = [*Array(attributes.gem_binary), 'uninstall', '--ignore-dependencies', '--executables', *Array(attributes.options)] if attributes.version cmd << '-v' << attributes.version else cmd << '--all' end cmd << attributes.package_name run_command(cmd) end end end end itamae-1.12.5/lib/itamae/resource/user.rb0000644000004100000410000000630414167043240020234 0ustar www-datawww-datamodule Itamae module Resource class User < Base define_attribute :action, default: :create define_attribute :username, type: String, default_name: true define_attribute :gid, type: [Integer, String] define_attribute :home, type: String define_attribute :password, type: String define_attribute :system_user, type: [TrueClass, FalseClass] define_attribute :uid, type: Integer define_attribute :shell, type: String define_attribute :create_home, type: [TrueClass, FalseClass], default: false def pre_action case @current_action when :create attributes.exist = true end if attributes.gid.is_a?(String) # convert name to gid attributes.gid = run_specinfra(:get_group_gid, attributes.gid).stdout.to_i end end def set_current_attributes current.exist = exist? if current.exist current.uid = run_specinfra(:get_user_uid, attributes.username).stdout.strip.to_i current.gid = run_specinfra(:get_user_gid, attributes.username).stdout.strip.to_i current.home = run_specinfra(:get_user_home_directory, attributes.username).stdout.strip current.shell = run_specinfra(:get_user_login_shell, attributes.username).stdout.strip current.password = current_password end end def action_create(options) if run_specinfra(:check_user_exists, attributes.username) if attributes.uid && attributes.uid != current.uid run_specinfra(:update_user_uid, attributes.username, attributes.uid) updated! end if attributes.gid && attributes.gid != current.gid run_specinfra(:update_user_gid, attributes.username, attributes.gid) updated! end if attributes.password && attributes.password != current.password run_specinfra(:update_user_encrypted_password, attributes.username, attributes.password) updated! end if attributes.home && attributes.home != current.home run_specinfra(:update_user_home_directory, attributes.username, attributes.home) updated! end if attributes.shell && attributes.shell != current.shell run_specinfra(:update_user_login_shell, attributes.username, attributes.shell) updated! end else options = { gid: attributes.gid, home_directory: attributes.home, password: attributes.password, system_user: attributes.system_user, uid: attributes.uid, shell: attributes.shell, create_home: attributes.create_home, } run_specinfra(:add_user, attributes.username, options) updated! end end private def exist? run_specinfra(:check_user_exists, attributes.username) end def current_password result = run_specinfra(:get_user_encrypted_password, attributes.username) if result.success? result.stdout.strip else nil end end end end end itamae-1.12.5/lib/itamae/resource/base.rb0000644000004100000410000002474714167043240020203 0ustar www-datawww-datarequire 'shellwords' require 'hashie' module Itamae module Resource class Base class EvalContext attr_reader :attributes attr_reader :notifications attr_reader :subscriptions attr_reader :verify_commands attr_reader :only_if_command attr_reader :not_if_command def initialize(resource) @resource = resource @attributes = Itamae::Mash.new @notifications = [] @subscriptions = [] @verify_commands = [] end def respond_to_missing?(method, include_private = false) @resource.class.defined_attributes.has_key?(method) || super end def method_missing(method, *args, &block) if @resource.class.defined_attributes[method] if args.size == 1 return @attributes[method] = args.first elsif args.size == 0 && block_given? return @attributes[method] = block elsif args.size == 0 return @attributes[method] end end super end def notifies(action, resource_desc, timing = :delay) @notifications << Notification.create(@resource, action, resource_desc, timing) end def subscribes(action, resource_desc, timing = :delay) @subscriptions << Subscription.create(@resource, action, resource_desc, timing) end def only_if(command) @only_if_command = command end def not_if(command) @not_if_command = command end def node @resource.recipe.runner.node end def run_command(*args) @resource.recipe.runner.backend.run_command(*args) end # Experimental def verify(command) @verify_commands << command end end @defined_attributes ||= {} class << self attr_reader :defined_attributes attr_reader :supported_oses def inherited(subclass) subclass.instance_variable_set( :@defined_attributes, self.defined_attributes.dup ) end def define_attribute(name, options) current = @defined_attributes[name.to_sym] || {} @defined_attributes[name.to_sym] = current.merge(options) end end define_attribute :action, type: [Symbol, Array], required: true define_attribute :user, type: String define_attribute :cwd, type: String attr_reader :recipe attr_reader :resource_name attr_reader :attributes attr_reader :current_attributes attr_reader :subscriptions attr_reader :notifications attr_reader :updated def initialize(recipe, resource_name, &block) clear_current_attributes @recipe = recipe @resource_name = resource_name @updated = false EvalContext.new(self).tap do |context| context.instance_eval(&block) if block @attributes = context.attributes @notifications = context.notifications @subscriptions = context.subscriptions @only_if_command = context.only_if_command @not_if_command = context.not_if_command @verify_commands = context.verify_commands end process_attributes end def run(specific_action = nil) runner.handler.event(:resource, resource_type: resource_type, resource_name: resource_name) do Itamae.logger.debug "#{resource_type}[#{resource_name}]" Itamae.logger.with_indent_if(Itamae.logger.debug?) do if do_not_run_because_of_only_if? Itamae.logger.debug "#{resource_type}[#{resource_name}] Execution skipped because of only_if attribute" return elsif do_not_run_because_of_not_if? Itamae.logger.debug "#{resource_type}[#{resource_name}] Execution skipped because of not_if attribute" return end [specific_action || attributes.action].flatten.each do |action| run_action(action) end verify unless runner.dry_run? if updated? runner.diff_found! notify runner.handler.event(:resource_updated) end end @updated = false end rescue Backend::CommandExecutionError Itamae.logger.error "#{resource_type}[#{resource_name}] Failed." exit 2 end def action_nothing # do nothing end def resource_type self.class.name.split("::").last.scan(/[A-Z][^A-Z]+/).map(&:downcase).join('_') end def do_not_run_because_of_only_if? @only_if_command && run_command(@only_if_command, error: false).exit_status != 0 end def do_not_run_because_of_not_if? @not_if_command && run_command(@not_if_command, error: false).exit_status == 0 end private alias_method :current, :current_attributes def run_action(action) runner.handler.event(:action, action: action) do original_attributes = @attributes # preserve and restore later @current_action = action clear_current_attributes Itamae.logger.debug "#{resource_type}[#{resource_name}] action: #{action}" return if action == :nothing Itamae.logger.with_indent_if(Itamae.logger.debug?) do Itamae.logger.debug "(in pre_action)" pre_action Itamae.logger.debug "(in set_current_attributes)" set_current_attributes Itamae.logger.debug "(in show_differences)" show_differences method_name = "action_#{action}" Itamae.logger.debug "(in #{method_name})" if runner.dry_run? unless respond_to?(method_name) Itamae.logger.error "action #{action.inspect} is unavailable" end else args = [method_name] if method(method_name).arity == 1 # for plugin compatibility args << runner.options end public_send(*args) end if different? updated! runner.handler.event(:attribute_changed, from: @current_attributes, to: @attributes) end end @current_action = nil @attributes = original_attributes end end def clear_current_attributes @current_attributes = Itamae::Mash.new end def pre_action # do nothing end def set_current_attributes # do nothing end def different? @current_attributes.each_pair.any? do |key, current_value| !current_value.nil? && !@attributes[key].nil? && current_value != @attributes[key] end end def show_differences @current_attributes.each_pair do |key, current_value| value = @attributes[key] if current_value.nil? && value.nil? # ignore elsif current_value.nil? && !value.nil? Itamae.logger.color :green do Itamae.logger.info "#{resource_type}[#{resource_name}] #{key} will be '#{value}'" end elsif current_value == value || value.nil? Itamae.logger.debug "#{resource_type}[#{resource_name}] #{key} will not change (current value is '#{current_value}')" else Itamae.logger.color :green do Itamae.logger.info "#{resource_type}[#{resource_name}] #{key} will change from '#{current_value}' to '#{value}'" end end end end def process_attributes self.class.defined_attributes.each_pair do |key, details| @attributes[key] ||= @resource_name if details[:default_name] @attributes[key] = details[:default] if details.has_key?(:default) && !@attributes.has_key?(key) if details[:required] && !@attributes[key] raise Resource::AttributeMissingError, "'#{key}' attribute is required but it is not set." end if @attributes[key] && details[:type] valid_type = [details[:type]].flatten.any? do |type| @attributes[key].is_a?(type) end unless valid_type raise Resource::InvalidTypeError, "#{key} attribute should be #{details[:type]}." end end end end def backend runner.backend end def runner recipe.runner end def node runner.node end def run_command(*args) unless args.last.is_a?(Hash) args << {} end args.last[:user] ||= attributes.user args.last[:cwd] ||= attributes.cwd backend.run_command(*args) end def check_command(*args) unless args.last.is_a?(Hash) args << {} end args.last[:error] = false run_command(*args).exit_status == 0 end def run_specinfra(type, *args) command = backend.get_command(type, *args) if type.to_s.start_with?("check_") check_command(command) else run_command(command) end end def shell_escape(str) str.shellescape end def updated! Itamae.logger.debug "This resource is updated." @updated = true end def updated? @updated end def notify (notifications + runner.children.subscribing(self)).each do |notification| message = "Notifying #{notification.action} to #{notification.action_resource.resource_type} resource '#{notification.action_resource.resource_name}'" if notification.delayed? message << " (delayed)" elsif notification.immediately? message << " (immediately)" end Itamae.logger.info message if notification.instance_of?(Subscription) Itamae.logger.info "(because it subscribes this resource)" end if notification.delayed? @recipe.delayed_notifications << notification elsif notification.immediately? notification.run end end end def verify return if @verify_commands.empty? Itamae.logger.info "Verifying..." Itamae.logger.with_indent do @verify_commands.each do |command| run_command(command) end end end end end end itamae-1.12.5/lib/itamae/resource/remote_directory.rb0000644000004100000410000000511314167043240022632 0ustar www-datawww-datamodule Itamae module Resource class RemoteDirectory < Base define_attribute :action, default: :create define_attribute :path, type: String, default_name: true define_attribute :source, type: String, required: true define_attribute :mode, type: String define_attribute :owner, type: String define_attribute :group, type: String def pre_action directory = ::File.expand_path(attributes.source, ::File.dirname(@recipe.path)) src = ::File.expand_path(directory, ::File.dirname(@recipe.path)) @temppath = ::File.join(runner.tmpdir, Time.now.to_f.to_s) backend.send_directory(src, @temppath) case @current_action when :create attributes.exist = true end end def set_current_attributes current.exist = run_specinfra(:check_file_is_directory, attributes.path) if current.exist current.mode = run_specinfra(:get_file_mode, attributes.path).stdout.chomp current.owner = run_specinfra(:get_file_owner_user, attributes.path).stdout.chomp current.group = run_specinfra(:get_file_owner_group, attributes.path).stdout.chomp else current.mode = nil current.owner = nil current.group = nil end end def show_differences super if current.exist diff = run_command(["diff", "-u", "-r", attributes.path, @temppath], error: false) if diff.exit_status == 0 # no change Itamae.logger.debug "directory content will not change" else Itamae.logger.info "diff:" diff.stdout.each_line do |line| Itamae.logger.info "#{line.strip}" end end end end def action_create(options) if attributes.mode run_specinfra(:change_file_mode, @temppath, attributes.mode) end if attributes.owner || attributes.group run_specinfra(:change_file_owner, @temppath, attributes.owner, attributes.group) end if run_specinfra(:check_file_is_file, attributes.path) unless check_command(["diff", "-q", @temppath, attributes.path]) updated! end else updated! end run_specinfra(:remove_file, attributes.path) run_specinfra(:move_file, @temppath, attributes.path) end def action_delete(options) if run_specinfra(:check_file_is_directory, attributes.path) run_specinfra(:remove_file, attributes.path) end end end end end itamae-1.12.5/lib/itamae/resource/package.rb0000644000004100000410000000236414167043240020653 0ustar www-datawww-datamodule Itamae module Resource class Package < Base define_attribute :action, default: :install define_attribute :name, type: String, default_name: true define_attribute :version, type: String define_attribute :options, type: String def pre_action case @current_action when :install attributes.installed = true when :remove attributes.installed = false end end def set_current_attributes current.installed = run_specinfra(:check_package_is_installed, attributes.name) if current.installed current.version = run_specinfra(:get_package_version, attributes.name).stdout.strip end end def action_install(action_options) return if !attributes.version && current.installed unless run_specinfra(:check_package_is_installed, attributes.name, attributes.version) run_specinfra(:install_package, attributes.name, attributes.version, attributes.options) updated! end end def action_remove(action_options) if current.installed run_specinfra(:remove_package, attributes.name, attributes.options) updated! end end end end end itamae-1.12.5/lib/itamae/resource/local_ruby_block.rb0000644000004100000410000000036214167043240022561 0ustar www-datawww-datamodule Itamae module Resource class LocalRubyBlock < Base define_attribute :action, default: :run define_attribute :block, type: Proc def action_run(options) attributes.block.call end end end end itamae-1.12.5/lib/itamae/resource/file.rb0000644000004100000410000001640014167043240020173 0ustar www-datawww-datamodule Itamae module Resource class File < Base define_attribute :action, default: :create define_attribute :path, type: String, default_name: true define_attribute :content, type: String, default: nil define_attribute :sensitive, default: false define_attribute :mode, type: String define_attribute :owner, type: String define_attribute :group, type: String define_attribute :block, type: Proc, default: proc {} class << self attr_accessor :sha256sum_available end def pre_action current.exist = run_specinfra(:check_file_is_file, attributes.path) case @current_action when :create attributes.exist = true when :delete attributes.exist = false when :edit attributes.exist = true if !runner.dry_run? || current.exist content = backend.receive_file(attributes.path) attributes.block.call(content) attributes.content = content end end if exists_and_not_modified? attributes.modified = false return end send_tempfile compare_file end def set_current_attributes current.modified = false if current.exist current.mode = run_specinfra(:get_file_mode, attributes.path).stdout.chomp current.owner = run_specinfra(:get_file_owner_user, attributes.path).stdout.chomp current.group = run_specinfra(:get_file_owner_group, attributes.path).stdout.chomp else current.mode = nil current.owner = nil current.group = nil end end def show_differences current.mode = current.mode.rjust(4, '0') if current.mode attributes.mode = attributes.mode.rjust(4, '0') if attributes.mode super if @temppath && @current_action != :delete show_content_diff end end def action_create(options) if !current.exist && !@temppath run_command(["touch", attributes.path]) end change_target = attributes.modified ? @temppath : attributes.path if attributes.owner || attributes.group run_specinfra(:change_file_owner, change_target, attributes.owner, attributes.group) end if attributes.mode run_specinfra(:change_file_mode, change_target, attributes.mode) end if attributes.modified run_specinfra(:move_file, @temppath, attributes.path) end end def action_delete(options) if run_specinfra(:check_file_is_file, attributes.path) run_specinfra(:remove_file, attributes.path) end end def action_edit(options) change_target = attributes.modified ? @temppath : attributes.path if attributes.owner || attributes.group || attributes.modified owner = attributes.owner || run_specinfra(:get_file_owner_user, attributes.path).stdout.chomp group = attributes.group || run_specinfra(:get_file_owner_group, attributes.path).stdout.chomp run_specinfra(:change_file_owner, change_target, owner, group) end if attributes.mode || attributes.modified mode = attributes.mode || run_specinfra(:get_file_mode, attributes.path).stdout.chomp run_specinfra(:change_file_mode, change_target, mode) end if attributes.modified run_specinfra(:move_file, @temppath, attributes.path) end end private def compare_to if current.exist attributes.path else '/dev/null' end end def compare_file attributes.modified = false unless @temppath return end # When the path currently doesn't exist yet, :change_file_xxx should be performed against `@temppath`. # Checking that by `diff -q /dev/null xxx` doesn't work when xxx's content is "", because /dev/null's content is also "". if !current.exist && attributes.exist attributes.modified = true return end case run_command(["diff", "-q", compare_to, @temppath], error: false).exit_status when 1 # diff found attributes.modified = true when 2 # error raise Itamae::Backend::CommandExecutionError, "diff command exited with 2" end end def exists_and_not_modified? return false unless current.exist && sha256sum_available? current_digest = run_command(["sha256sum", attributes.path]).stdout.split(/\s/, 2).first digest = if content_file Digest::SHA256.file(content_file).hexdigest else Digest::SHA256.hexdigest(attributes.content.to_s) end current_digest == digest end def show_content_diff if attributes.sensitive Itamae.logger.info("diff exists, but not displaying sensitive content") return end if attributes.modified Itamae.logger.info "diff:" diff = run_command(["diff", "-u", "--label=#{attributes.path} (BEFORE)", compare_to, "--label=#{attributes.path} (AFTER)", @temppath], error: false) diff.stdout.each_line do |line| color = if line.start_with?('+') :green elsif line.start_with?('-') :red else :clear end Itamae.logger.color(color) do Itamae.logger.info line.chomp end end runner.handler.event(:file_content_changed, diff: diff.stdout) else # no change Itamae.logger.debug "file content will not change" end end # will be overridden def content_file nil end def send_tempfile if !attributes.content && !content_file @temppath = nil return end begin src = if content_file content_file else f = if Gem.win_platform? Tempfile.open('itamae', :mode=>IO::BINARY) else Tempfile.open('itamae') end f.write(attributes.content) f.close f.path end @temppath = ::File.join(runner.tmpdir, Time.now.to_f.to_s) if backend.is_a?(Itamae::Backend::Docker) run_command(["mkdir", @temppath]) backend.send_file(src, @temppath, user: attributes.user) @temppath = ::File.join(@temppath, ::File.basename(src)) else run_command(["touch", @temppath]) run_specinfra(:change_file_mode, @temppath, '0600') backend.send_file(src, @temppath, user: attributes.user) end run_specinfra(:change_file_mode, @temppath, '0600') ensure f.unlink if f end end def sha256sum_available? return self.class.sha256sum_available unless self.class.sha256sum_available.nil? self.class.sha256sum_available = run_command(["sha256sum", "--version"], error: false).exit_status == 0 end end end end itamae-1.12.5/lib/itamae/resource/service.rb0000644000004100000410000000341114167043240020712 0ustar www-datawww-datamodule Itamae module Resource class Service < Base define_attribute :action, default: :nothing define_attribute :name, type: String, default_name: true define_attribute :provider, type: Symbol, default: nil def initialize(*args) super @under = attributes.provider ? "_under_#{attributes.provider}" : "" end def pre_action case @current_action when :start, :restart attributes.running = true when :stop attributes.running = false when :enable attributes.enabled = true when :disable attributes.enabled = false end end def set_current_attributes current.running = run_specinfra(:"check_service_is_running#{@under}", attributes.name) current.enabled = run_specinfra(:"check_service_is_enabled#{@under}", attributes.name) end def action_start(options) unless current.running run_specinfra(:"start_service#{@under}", attributes.name) end end def action_stop(options) if current.running run_specinfra(:"stop_service#{@under}", attributes.name) end end def action_restart(options) run_specinfra(:"restart_service#{@under}", attributes.name) end def action_reload(options) if current.running run_specinfra(:"reload_service#{@under}", attributes.name) end end def action_enable(options) unless current.enabled run_specinfra(:"enable_service#{@under}", attributes.name) end end def action_disable(options) if current.enabled run_specinfra(:"disable_service#{@under}", attributes.name) end end end end end itamae-1.12.5/lib/itamae/resource/git.rb0000644000004100000410000000571414167043240020045 0ustar www-datawww-datamodule Itamae module Resource class Git < Base DEPLOY_BRANCH = "deploy" define_attribute :action, default: :sync define_attribute :destination, type: String, default_name: true define_attribute :repository, type: String, required: true define_attribute :revision, type: String define_attribute :recursive, type: [TrueClass, FalseClass], default: false define_attribute :depth, type: Integer def pre_action case @current_action when :sync attributes.exist = true end end def set_current_attributes current.exist = run_specinfra(:check_file_is_directory, attributes.destination) end def action_sync(options) ensure_git_available new_repository = false if check_empty_dir cmd = ['git', 'clone'] cmd << '--recursive' if attributes.recursive cmd += ['--depth', attributes.depth.to_s] if attributes.depth cmd << attributes.repository << attributes.destination run_command(cmd) new_repository = true end target = if attributes.revision get_revision(attributes.revision) else fetch_origin! run_command_in_repo("git ls-remote origin HEAD | cut -f1").stdout.strip end if new_repository || target != get_revision('HEAD') updated! deploy_old_created = false if current_branch == DEPLOY_BRANCH run_command_in_repo("git branch -m deploy-old") deploy_old_created = true end fetch_origin! run_command_in_repo(["git", "checkout", target, "-b", DEPLOY_BRANCH]) if deploy_old_created run_command_in_repo("git branch -d deploy-old") end end end private def ensure_git_available unless run_command("which git", error: false).exit_status == 0 raise "`git` command is not available. Please install git." end end def check_empty_dir run_command("test -z \"$(ls -A #{shell_escape(attributes.destination)})\"", error: false).success? end def run_command_in_repo(*args) unless args.last.is_a?(Hash) args << {} end args.last[:cwd] = attributes.destination run_command(*args) end def current_branch run_command_in_repo("git rev-parse --abbrev-ref HEAD").stdout.strip end def get_revision(branch) result = run_command_in_repo("git rev-parse #{shell_escape(branch)}", error: false) return result.stdout.strip if result.exit_status == 0 fetch_origin! run_command_in_repo("git rev-parse #{shell_escape(branch)}").stdout.strip end def fetch_origin! return if @origin_fetched @origin_fetched = true run_command_in_repo(['git', 'fetch', 'origin']) end end end end itamae-1.12.5/lib/itamae/resource/link.rb0000644000004100000410000000172314167043240020213 0ustar www-datawww-datamodule Itamae module Resource class Link < Base define_attribute :action, default: :create define_attribute :link, type: String, default_name: true define_attribute :to, type: String, required: true define_attribute :force, type: [TrueClass, FalseClass], default: false def pre_action case @current_action when :create attributes.exist = true end end def set_current_attributes current.exist = run_specinfra(:check_file_is_link, attributes.link) if current.exist current.to = run_specinfra(:get_file_link_target, attributes.link).stdout.strip end end def action_create(options) unless run_specinfra(:check_file_is_linked_to, attributes.link, attributes.to) run_specinfra(:link_file_to, attributes.link, attributes.to, force: attributes.force, no_dereference: attributes.force) end end end end end itamae-1.12.5/lib/itamae/resource/directory.rb0000644000004100000410000000355014167043240021262 0ustar www-datawww-datamodule Itamae module Resource class Directory < Base define_attribute :action, default: :create define_attribute :path, type: String, default_name: true define_attribute :mode, type: String define_attribute :owner, type: String define_attribute :group, type: String def pre_action case @current_action when :create attributes.exist = true when :delete attributes.exist = false end end def show_differences current.mode = current.mode.rjust(4, '0') if current.mode attributes.mode = attributes.mode.rjust(4, '0') if attributes.mode super end def set_current_attributes exist = run_specinfra(:check_file_is_directory, attributes.path) current.exist = exist if exist current.mode = run_specinfra(:get_file_mode, attributes.path).stdout.chomp current.owner = run_specinfra(:get_file_owner_user, attributes.path).stdout.chomp current.group = run_specinfra(:get_file_owner_group, attributes.path).stdout.chomp else current.mode = nil current.owner = nil current.group = nil end end def action_create(options) if !run_specinfra(:check_file_is_directory, attributes.path) run_specinfra(:create_file_as_directory, attributes.path) end if attributes.mode run_specinfra(:change_file_mode, attributes.path, attributes.mode) end if attributes.owner || attributes.group run_specinfra(:change_file_owner, attributes.path, attributes.owner, attributes.group) end end def action_delete(options) if run_specinfra(:check_file_is_directory, attributes.path) run_specinfra(:remove_file, attributes.path) end end end end end itamae-1.12.5/lib/itamae/mash.rb0000644000004100000410000000013314167043240016351 0ustar www-datawww-datarequire 'hashie' module Itamae class Mash < Hashie::Mash disable_warnings end end itamae-1.12.5/lib/itamae/handler.rb0000644000004100000410000000057614167043240017051 0ustar www-datawww-datarequire 'itamae/handler/base' module Itamae module Handler def self.from_type(type) first_time = true class_name = type.split('_').map(&:capitalize).join self.const_get(class_name) rescue NameError require "itamae/handler/#{type}" if first_time first_time = false retry else raise end end end end itamae-1.12.5/lib/itamae/logger.rb0000644000004100000410000000550314167043240016706 0ustar www-datawww-datarequire 'logger' require 'ansi/code' module Itamae module Logger module Helper def with_indent indent yield ensure outdent end def with_indent_if(condition, &block) if condition with_indent(&block) else block.call end end def indent self.indent_depth += 1 end def outdent self.indent_depth -= 1 self.indent_depth = 0 if self.indent_depth < 0 end def indent_depth @indent_depth ||= 0 end def indent_depth=(val) @indent_depth = val end def color(code, &block) if self.formatter.respond_to?(:color) self.formatter.color(code, &block) else block.call end end %w!debug info warn error fatal unknown!.each do |level| module_eval(<<-EOC, __FILE__, __LINE__ + 1) def #{level}(msg) super(indent_msg(msg)) end EOC end private def indent_msg(msg) spaces = " " * indent_depth case msg when ::String "#{spaces}#{msg}" when ::Exception "#{spaces}#{msg.message} (#{msg.class})\n" << (msg.backtrace || []).map {|f| "#{spaces}#{f}"}.join("\n") else "#{spaces}#{msg.inspect}" end end end class Formatter attr_accessor :colored def initialize @color = nil end def call(severity, datetime, progname, msg) log = "%s : %s" % ["%5s" % severity, msg2str(msg)] (colored ? colorize(log, severity) : log) + "\n" end def color(code) prev_color = @color @color = code yield ensure @color = prev_color end private def msg2str(msg) case msg when ::String msg when ::Exception "#{ msg.message } (#{ msg.class })\n" << (msg.backtrace || []).join("\n") else msg.inspect end end def colorize(str, severity) if @color color_code = @color else color_code = case severity when "INFO" :clear when "WARN" :magenta when "ERROR" :red else :clear end end ANSI.public_send(color_code) { str } end end end @logger = ::Logger.new($stdout).tap do |l| l.formatter = Itamae::Logger::Formatter.new end.extend(Itamae::Logger::Helper) $stdout.sync = true class << self def logger @logger end def logger=(l) @logger = l.extend(Itamae::Logger::Helper) end end end itamae-1.12.5/lib/itamae/ext/0000755000004100000410000000000014167043240015677 5ustar www-datawww-dataitamae-1.12.5/lib/itamae/ext/specinfra.rb0000644000004100000410000000157514167043240020206 0ustar www-datawww-data# TODO: Send patches to Specinfra module Specinfra module Backend class Base def receive_file(from, to = nil) raise NotImplementedError end end class Exec < Base def receive_file(from, to = nil) if to FileUtils.cp(from, to) else ::File.read(from) end end end class Docker < Exec def receive_file(from, to = nil) if to send_file(from, to) else run_command("cat #{from}").stdout end end end class Ssh < Exec def receive_file(from, to = nil) scp_download!(from, to) end private def scp_download!(from, to, opt={}) if get_config(:scp).nil? set_config(:scp, create_scp) end scp = get_config(:scp) scp.download!(from, to, opt) end end end end itamae-1.12.5/lib/itamae/notification.rb0000644000004100000410000000163014167043240020112 0ustar www-datawww-datamodule Itamae class Notification < Struct.new(:defined_in_resource, :action, :target_resource_desc, :timing) def self.create(*args) self.new(*args).tap(&:validate!) end def resource runner.children.find_resource_by_description(target_resource_desc) end def run action_resource.run(action) end def action_resource resource end def runner defined_in_resource.recipe.runner end def delayed? [:delay, :delayed].include?(timing) end def immediately? timing == :immediately end def validate! unless [:delay, :delayed, :immediately].include?(timing) Itamae.logger.error "'#{timing}' is not valid notification timing. (Valid option is delayed or immediately)" abort end end end class Subscription < Notification def action_resource defined_in_resource end end end itamae-1.12.5/lib/itamae.rb0000644000004100000410000000066214167043240015430 0ustar www-datawww-datarequire "itamae/version" require "itamae/runner" require "itamae/recipe" require "itamae/resource" require "itamae/handler" require "itamae/handler_proxy" require "itamae/recipe_children" require "itamae/logger" require "itamae/node" require "itamae/backend" require "itamae/notification" require "itamae/definition" require "itamae/ext" require "itamae/generators" require "itamae/mash" module Itamae # Your code goes here... end itamae-1.12.5/Gemfile0000644000004100000410000000036014167043240014363 0ustar www-datawww-datasource 'https://rubygems.org' # Specify your gem's dependencies in itamae.gemspec gemspec path = Pathname.new("Gemfile.local") eval(path.read) if path.exist? group :test do if RUBY_PLATFORM.include?('darwin') gem 'growl' end end itamae-1.12.5/.github/0000755000004100000410000000000014167043240014431 5ustar www-datawww-dataitamae-1.12.5/.github/workflows/0000755000004100000410000000000014167043240016466 5ustar www-datawww-dataitamae-1.12.5/.github/workflows/test.yml0000644000004100000410000001133714167043240020175 0ustar www-datawww-dataname: test on: push: branches: - master pull_request: types: - opened - synchronize - reopened schedule: - cron: "0 0 * * 5" # JST 9:00 (Fri) jobs: unit: runs-on: ubuntu-latest strategy: fail-fast: false matrix: ruby: - "2.3" - "2.4" - "2.5" - "2.6" - "2.7" - "3.0" - "3.1" rubyopt: - "" - "--jit" exclude: # --jit is available since MRI 2.6 - ruby: "2.3" rubyopt: "--jit" - ruby: "2.4" rubyopt: "--jit" - ruby: "2.5" rubyopt: "--jit" env: RUBYOPT: ${{ matrix.rubyopt }} steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - run: bundle update - run: bundle exec rake spec:unit - name: Slack Notification (not success) uses: lazy-actions/slatify@master if: "! success()" continue-on-error: true with: job_name: ${{ format('*unit* ({0},{1})', matrix.ruby, matrix.rubyopt) }} type: ${{ job.status }} icon_emoji: ":octocat:" url: ${{ secrets.SLACK_WEBHOOK }} token: ${{ secrets.GITHUB_TOKEN }} integration-docker: runs-on: ubuntu-latest strategy: fail-fast: false matrix: ruby: - "2.3" - "2.4" - "2.5" - "2.6" - "2.7" - "3.0" rubyopt: - "" - "--jit" image: - ubuntu:trusty exclude: # --jit is available since MRI 2.6 - ruby: "2.3" rubyopt: "--jit" - ruby: "2.4" rubyopt: "--jit" - ruby: "2.5" rubyopt: "--jit" env: RUBYOPT: ${{ matrix.rubyopt }} TEST_IMAGE: ${{ matrix.image }} steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - run: bundle update - run: bundle exec rake spec:integration:docker:boot - run: bundle exec rake spec:integration:docker:provision env: # FIXME: avoid error for "Command `chmod 777 /tmp/itamae_tmp` failed. (exit status: 1)" ITAMAE_TMP_DIR: /var/tmp/itamae_tmp - run: bundle exec rake spec:integration:docker:serverspec - run: bundle exec rake spec:integration:docker:clean_docker_container - name: Slack Notification (not success) uses: lazy-actions/slatify@master if: "! success()" continue-on-error: true with: job_name: ${{ format('*integration-docker* ({0},{1},{2})', matrix.ruby, matrix.rubyopt, matrix.image) }} type: ${{ job.status }} icon_emoji: ":octocat:" url: ${{ secrets.SLACK_WEBHOOK }} token: ${{ secrets.GITHUB_TOKEN }} integration-local: runs-on: ubuntu-latest strategy: fail-fast: false matrix: ruby: - "2.3" - "2.4" - "2.5" - "2.6" - "2.7" - "3.0" rubyopt: - "" - "--jit" exclude: # --jit is available since MRI 2.6 - ruby: "2.3" rubyopt: "--jit" - ruby: "2.4" rubyopt: "--jit" - ruby: "2.5" rubyopt: "--jit" env: RUBYOPT: ${{ matrix.rubyopt }} steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - run: bundle update - run: bundle exec rake spec:integration:local:main - run: bundle exec rake spec:integration:local:ordinary_user - name: Slack Notification (not success) uses: lazy-actions/slatify@master if: "! success()" continue-on-error: true with: job_name: ${{ format('*integration-local* ({0},{1})', matrix.ruby, matrix.rubyopt) }} type: ${{ job.status }} icon_emoji: ":octocat:" url: ${{ secrets.SLACK_WEBHOOK }} token: ${{ secrets.GITHUB_TOKEN }} notify: needs: - unit - integration-docker - integration-local runs-on: ubuntu-latest steps: - name: Slack Notification (success) uses: lazy-actions/slatify@master if: always() continue-on-error: true with: job_name: '*notify*' type: ${{ job.status }} icon_emoji: ":octocat:" url: ${{ secrets.SLACK_WEBHOOK }} token: ${{ secrets.GITHUB_TOKEN }} itamae-1.12.5/LICENSE.txt0000644000004100000410000000206014167043240014712 0ustar www-datawww-dataCopyright (c) 2013-2015 Ryota Arai MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. itamae-1.12.5/itamae.gemspec0000644000004100000410000000337414167043240015705 0ustar www-datawww-data# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'itamae/version' Gem::Specification.new do |spec| spec.name = "itamae" spec.version = Itamae::VERSION spec.authors = ["Ryota Arai", "Yusuke Nakamura", "sue445"] spec.email = ["ryota.arai@gmail.com", "yusuke1994525@gmail.com", "sue445@sue445.net"] spec.summary = %q{Simple Configuration Management Tool} spec.homepage = "https://itamae.kitchen/" spec.license = "MIT" if spec.respond_to?(:metadata) spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = "https://github.com/itamae-kitchen/itamae" spec.metadata["changelog_uri"] = "https://github.com/itamae-kitchen/itamae/blob/master/CHANGELOG.md" else raise "RubyGems 2.0 or newer is required to protect against " \ "public gem pushes." end spec.files = `git ls-files`.split($/) spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ["lib"] spec.add_runtime_dependency "thor" spec.add_runtime_dependency "specinfra", [">= 2.64.0", "< 3.0.0"] spec.add_runtime_dependency "hashie" spec.add_runtime_dependency "ansi" spec.add_runtime_dependency "schash", "~> 0.1.0" spec.add_development_dependency "bundler", ">= 1.3" spec.add_development_dependency "rake" spec.add_development_dependency "rspec", "~> 3.0" spec.add_development_dependency "serverspec", "~> 2.1" spec.add_development_dependency "pry-byebug" spec.add_development_dependency "docker-api", "~> 2" spec.add_development_dependency "fakefs" spec.add_development_dependency "fluent-logger" end