itamae 1.0.0.beta1 → 1.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e8b0f4978f8d00ca41234de5b288befcdc1f0f68
4
- data.tar.gz: 20ad47b014f03d7af8ac547152281c5c0fbd9f99
3
+ metadata.gz: 240aa7e6557872fd35c36c24692c8a0e3f714809
4
+ data.tar.gz: 516ef413cfc3a830f63fb83b9d1207fff66a5f00
5
5
  SHA512:
6
- metadata.gz: dd694c3e13fa40bbee8862338c59ff8079dd30763ba2710c9868e9a4dda8bda080baef7a46a8a21c4b67fdecab051447eb2064015625e1e5f843f741de767c85
7
- data.tar.gz: 3d95d7a8a67337c18b56d569e3b6e45dfe65331e9a522a5d7f5a8586c330698c81199699584f94080b52befdaf561ffd5d4c9b683958244f1175515e8abf735f
6
+ metadata.gz: 769d71f69c3452ebfbfae501b1278a1931ca5306ce00f2c88a681586e1da78ecc83f723b0f04616c4216c2901158dde1f5e98fb62c8f52e5c299699eb99abc8b
7
+ data.tar.gz: 16cde3481cf61c3d8aa60adaa3e630e293f36263674dea4dda08cde2e83cc54bf048746501fc0de098dbc6b7ec84ee45dda63da65653195dbd6414392b9dc46c
data/README.md CHANGED
@@ -1,13 +1,13 @@
1
- # Itamae
1
+ # Itamae [![Gem Version](https://badge.fury.io/rb/itamae.svg)](http://badge.fury.io/rb/itamae)
2
2
 
3
- Configuration management tool like Chef which is simpler and lighter than Chef
3
+ Simple and lightweight configuration management tool inspired by Chef.
4
4
 
5
5
  ## Concept
6
6
 
7
- * Good DSL like Chef
8
- * Simpler and lighter than Chef
9
- * It's just like Chef. No compatibility.
10
- * Idempotent.
7
+ - Chef-like DSL
8
+ - Simpler and lighter weight than Chef
9
+ - Not compatible with Chef
10
+ - Idempotent
11
11
 
12
12
  ## Installation
13
13
 
@@ -22,7 +22,7 @@ $ gem install itamae
22
22
  ```
23
23
  $ sudo itamae execute -j example/node.json example/recipe.rb
24
24
  D, [2013-12-24T14:05:50.859587 #7156] DEBUG -- : Loading node data from /vagrant/example/node.json ...
25
- I, [2013-12-24T14:05:50.862072 #7156] INFO -- : >>> Executing Itamae::Resources::Package ({:action=>:install, :name=>"git"})...
25
+ I, [2013-12-24T14:05:50.862072 #7156] INFO -- : >>> Executing Itamae::Resource::Package ({:action=>:install, :name=>"git"})...
26
26
  D, [2013-12-24T14:05:51.335070 #7156] DEBUG -- : Command `apt-get -y install git` succeeded
27
27
  D, [2013-12-24T14:05:51.335251 #7156] DEBUG -- : STDOUT> Reading package lists...
28
28
  Building dependency tree...
@@ -31,7 +31,7 @@ git is already the newest version.
31
31
  0 upgraded, 0 newly installed, 0 to remove and 156 not upgraded.
32
32
  D, [2013-12-24T14:05:51.335464 #7156] DEBUG -- : STDERR>
33
33
  I, [2013-12-24T14:05:51.335531 #7156] INFO -- : <<< Succeeded.
34
- I, [2013-12-24T14:05:51.335728 #7156] INFO -- : >>> Executing Itamae::Resources::File ({:action=>:create, :source=>"foo", :path=>"/home/vagrant/foo"})...
34
+ I, [2013-12-24T14:05:51.335728 #7156] INFO -- : >>> Executing Itamae::Resource::File ({:action=>:create, :source=>"foo", :path=>"/home/vagrant/foo"})...
35
35
  D, [2013-12-24T14:05:51.335842 #7156] DEBUG -- : Copying a file from '/vagrant/example/foo' to '/home/vagrant/foo'...
36
36
  I, [2013-12-24T14:05:51.339119 #7156] INFO -- : <<< Succeeded.
37
37
  ```
data/Rakefile CHANGED
@@ -40,6 +40,7 @@ namespace :spec do
40
40
  cmd << " -u #{options[:user]}"
41
41
  cmd << " -p #{options[:port]}"
42
42
  cmd << " -i #{options[:keys].first}"
43
+ cmd << " -l #{ENV['LOG_LEVEL'] || 'debug'}"
43
44
  cmd << " -j spec/integration/recipes/node.json"
44
45
  cmd << " spec/integration/recipes/default.rb"
45
46
 
data/itamae.gemspec CHANGED
@@ -18,8 +18,9 @@ Gem::Specification.new do |spec|
18
18
  spec.require_paths = ["lib"]
19
19
 
20
20
  spec.add_runtime_dependency "thor"
21
- spec.add_runtime_dependency "specinfra", "2.0.0.beta20"
21
+ spec.add_runtime_dependency "specinfra", "2.0.0.beta32"
22
22
  spec.add_runtime_dependency "hashie"
23
+ spec.add_runtime_dependency "ansi"
23
24
 
24
25
  # TODO: move to specinfra
25
26
  spec.add_runtime_dependency "net-scp"
data/lib/itamae.rb CHANGED
@@ -2,7 +2,8 @@ require "itamae/version"
2
2
  require "itamae/runner"
3
3
  require "itamae/cli"
4
4
  require "itamae/recipe"
5
- require "itamae/resources"
5
+ require "itamae/resource"
6
+ require "itamae/resource_collection"
6
7
  require "itamae/logger"
7
8
  require "itamae/node"
8
9
  require "itamae/specinfra"
data/lib/itamae/cli.rb CHANGED
@@ -3,6 +3,14 @@ require 'thor'
3
3
 
4
4
  module Itamae
5
5
  class CLI < Thor
6
+ class_option :log_level, type: :string, aliases: ['-l'], default: 'info'
7
+
8
+ def initialize(*args)
9
+ super
10
+
11
+ Itamae::Logger.level = ::Logger.const_get(options[:log_level].upcase)
12
+ end
13
+
6
14
  desc "local RECIPE [RECIPE...]", "Run Itamae locally"
7
15
  option :node_json, type: :string, aliases: ['-j']
8
16
  def local(*recipe_files)
data/lib/itamae/logger.rb CHANGED
@@ -1,16 +1,17 @@
1
1
  require 'itamae'
2
2
  require 'logger'
3
+ require 'ansi/code'
3
4
 
4
5
  module Itamae
5
6
  module Logger
6
7
  class Formatter
7
8
  def call(severity, datetime, progname, msg)
8
- "[%s] %5s : %s\n" % [format_datetime(datetime), severity, msg2str(msg)]
9
+ "[%s] %s : %s\n" % [format_datetime(datetime), color("%5s" % severity, severity), msg2str(msg)]
9
10
  end
10
11
 
11
12
  private
12
13
  def format_datetime(time)
13
- time.strftime("%Y-%m-%dT%H:%M:%S.") << "%06d" % time.usec
14
+ time.strftime("%Y-%m-%dT%H:%M:%S%:z")
14
15
  end
15
16
 
16
17
  def msg2str(msg)
@@ -24,6 +25,18 @@ module Itamae
24
25
  msg.inspect
25
26
  end
26
27
  end
28
+
29
+ def color(str, severity)
30
+ color_code = case severity
31
+ when "INFO"
32
+ :green
33
+ when "ERROR"
34
+ :red
35
+ else
36
+ :clear
37
+ end
38
+ ANSI.public_send(color_code) { str }
39
+ end
27
40
  end
28
41
 
29
42
  def self.logger
data/lib/itamae/recipe.rb CHANGED
@@ -4,11 +4,15 @@ module Itamae
4
4
  class Recipe
5
5
  attr_reader :path
6
6
  attr_reader :runner
7
+ attr_reader :resources
8
+ attr_reader :delayed_actions
7
9
 
8
10
  def initialize(runner, path)
9
11
  @runner = runner
10
12
  @path = path
11
- @resources = []
13
+ @resources = ResourceCollection.new
14
+ @delayed_actions = []
15
+
12
16
  load_resources
13
17
  end
14
18
 
@@ -18,15 +22,11 @@ module Itamae
18
22
 
19
23
  def run
20
24
  @resources.each do |resource|
21
- Logger.info ">>> Executing #{resource.class.name} (#{resource.options})..."
22
- begin
23
- resource.run
24
- rescue Resources::CommandExecutionError
25
- Logger.error "<<< Failed."
26
- exit 2
27
- else
28
- Logger.info "<<< Succeeded."
29
- end
25
+ resource.run
26
+ end
27
+
28
+ @delayed_actions.uniq.each do |action, resource|
29
+ resource.run(action)
30
30
  end
31
31
  end
32
32
 
@@ -37,7 +37,7 @@ module Itamae
37
37
  end
38
38
 
39
39
  def method_missing(method, name, &block)
40
- klass = Resources.get_resource_class(method)
40
+ klass = Resource.get_resource_class(method)
41
41
  resource = klass.new(self, name, &block)
42
42
  @resources << resource
43
43
  end
@@ -0,0 +1,37 @@
1
+ require 'itamae'
2
+ require 'itamae/resource/base'
3
+ require 'itamae/resource/file'
4
+ require 'itamae/resource/package'
5
+ require 'itamae/resource/remote_file'
6
+ require 'itamae/resource/directory'
7
+ require 'itamae/resource/template'
8
+ require 'itamae/resource/execute'
9
+ require 'itamae/resource/mail_alias'
10
+
11
+ module Itamae
12
+ module Resource
13
+ Error = Class.new(StandardError)
14
+ CommandExecutionError = Class.new(StandardError)
15
+ AttributeMissingError = Class.new(StandardError)
16
+ InvalidTypeError = Class.new(StandardError)
17
+ ParseError = Class.new(StandardError)
18
+
19
+ class << self
20
+ def get_resource_class_name(method)
21
+ method.to_s.split('_').map {|part| part.capitalize}.join
22
+ end
23
+
24
+ def get_resource_class(method)
25
+ const_get(get_resource_class_name(method))
26
+ end
27
+
28
+ def parse_description(desc)
29
+ if /\A([^\[]+)\[([^\]]+)\]\z/ =~ desc
30
+ [$1, $2]
31
+ else
32
+ raise ParseError, "'#{desc}' doesn't represent a resource."
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,257 @@
1
+ require 'itamae'
2
+ require 'shellwords'
3
+
4
+ module Itamae
5
+ module Resource
6
+ class Base
7
+ @defined_attributes ||= {}
8
+
9
+ class << self
10
+ attr_reader :defined_attributes
11
+ attr_reader :supported_oses
12
+
13
+ def inherited(subclass)
14
+ subclass.instance_variable_set(
15
+ :@defined_attributes,
16
+ self.defined_attributes.dup
17
+ )
18
+ end
19
+
20
+ def define_attribute(name, options)
21
+ current = @defined_attributes[name.to_sym] || {}
22
+ @defined_attributes[name.to_sym] = current.merge(options)
23
+ end
24
+ end
25
+
26
+ define_attribute :action, type: Symbol, required: true
27
+
28
+ attr_reader :resource_name
29
+ attr_reader :attributes
30
+ attr_reader :current_attributes
31
+
32
+ def initialize(recipe, resource_name, &block)
33
+ @attributes = {}
34
+ @current_attributes = {}
35
+ @recipe = recipe
36
+ @resource_name = resource_name
37
+ @notifies = []
38
+ @subscribes = []
39
+ @updated = false
40
+
41
+ instance_eval(&block) if block_given?
42
+
43
+ process_attributes
44
+ end
45
+
46
+ def run(specific_action = nil)
47
+ Logger.info "> Executing #{resource_type} (#{attributes})..."
48
+
49
+ if do_not_run_because_of_only_if?
50
+ Logger.info "< Execution skipped because of only_if attribute"
51
+ return
52
+ elsif do_not_run_because_of_not_if?
53
+ Logger.info "< Execution skipped because of not_if attribute"
54
+ return
55
+ end
56
+
57
+ set_current_attributes
58
+ show_differences
59
+
60
+ begin
61
+ public_send("#{specific_action || action}_action".to_sym)
62
+ rescue Resource::CommandExecutionError
63
+ Logger.error "< Failed."
64
+ exit 2
65
+ end
66
+
67
+ notify if updated?
68
+
69
+ Logger.info "< Succeeded."
70
+ end
71
+
72
+ def nothing_action
73
+ # do nothing
74
+ end
75
+
76
+ def resource_type
77
+ humps = []
78
+ self.class.name.split("::").last.each_char do |c|
79
+ if "A" <= c && c <= "Z"
80
+ humps << c.downcase
81
+ else
82
+ humps.last << c
83
+ end
84
+ end
85
+ humps.join('_')
86
+ end
87
+
88
+ def notifies_resources
89
+ @notifies.map do |action, resource_desc, timing|
90
+ resource = resources.find_by_description(resource_desc)
91
+ [action, resource, timing]
92
+ end
93
+ end
94
+
95
+ def subscribes_resources
96
+ @subscribes.map do |action, resource_desc, timing|
97
+ resource = resources.find_by_description(resource_desc)
98
+ [action, resource, timing]
99
+ end
100
+ end
101
+
102
+
103
+ private
104
+
105
+ def method_missing(method, *args)
106
+ if args.size == 1 && self.class.defined_attributes[method]
107
+ return @attributes[method] = args.first
108
+ elsif args.size == 0 && @attributes.has_key?(method)
109
+ return @attributes[method]
110
+ end
111
+ super
112
+ end
113
+
114
+ def set_current_attributes
115
+ end
116
+
117
+ def show_differences
118
+ @current_attributes.each_pair do |key, current_value|
119
+ value = @attributes[key]
120
+ Logger.info " #{key} will change from '#{current_value}' to '#{value}'"
121
+ end
122
+ end
123
+
124
+ def process_attributes
125
+ self.class.defined_attributes.each_pair do |key, details|
126
+ @attributes[key] ||= @resource_name if details[:default_name]
127
+ @attributes[key] ||= details[:default]
128
+
129
+ if details[:required] && !@attributes[key]
130
+ raise Resource::AttributeMissingError, "'#{key}' attribute is required but it is not set."
131
+ end
132
+
133
+ if @attributes[key] && details[:type] && !@attributes[key].is_a?(details[:type])
134
+ raise Resource::InvalidTypeError, "#{key} attribute should be #{details[:type]}."
135
+ end
136
+ end
137
+ end
138
+
139
+ def run_specinfra(type, *args)
140
+ command = Specinfra.command.get(type, *args)
141
+
142
+ if type.to_s.start_with?("check_")
143
+ result = run_command(command, error: false)
144
+ result.exit_status == 0
145
+ else
146
+ run_command(command)
147
+ end
148
+ end
149
+
150
+ def run_command(command, options = {})
151
+ options = {error: true}.merge(options)
152
+
153
+ result = backend.run_command(command)
154
+ exit_status = result.exit_status
155
+
156
+ if exit_status == 0 || !options[:error]
157
+ method = :debug
158
+ message = " Command `#{command}` exited with #{exit_status}"
159
+ else
160
+ method = :error
161
+ message = " Command `#{command}` failed. (exit status: #{exit_status})"
162
+ end
163
+
164
+ Logger.public_send(method, message)
165
+
166
+ if result.stdout && result.stdout != ''
167
+ Logger.public_send(method, " STDOUT> #{result.stdout.chomp}")
168
+ end
169
+
170
+ if result.stderr && result.stderr != ''
171
+ Logger.public_send(method, " STDERR> #{result.stderr.chomp}")
172
+ end
173
+
174
+ if options[:error] && exit_status != 0
175
+ raise CommandExecutionError
176
+ end
177
+
178
+ result
179
+ end
180
+
181
+ def copy_file(src, dst)
182
+ Logger.debug " Copying a file from '#{src}' to '#{dst}'..."
183
+ unless ::File.exist?(src)
184
+ raise Error, "The file '#{src}' doesn't exist."
185
+ end
186
+ backend.copy_file(src, dst)
187
+ end
188
+
189
+ def only_if(command)
190
+ @only_if_command = command
191
+ end
192
+
193
+ def not_if(command)
194
+ @not_if_command = command
195
+ end
196
+
197
+ def do_not_run_because_of_only_if?
198
+ @only_if_command &&
199
+ run_command(@only_if_command, error: false).exit_status != 0
200
+ end
201
+
202
+ def do_not_run_because_of_not_if?
203
+ @not_if_command &&
204
+ run_command(@not_if_command, error: false).exit_status == 0
205
+ end
206
+
207
+ def notifies(action, resource_desc, timing = :delay)
208
+ @notifies << [action, resource_desc, timing]
209
+ end
210
+
211
+ def subscribes(action, resource_desc, timing = :delay)
212
+ @subscribes << [action, resource_desc, timing]
213
+ end
214
+
215
+ def node
216
+ runner.node
217
+ end
218
+
219
+ def backend
220
+ Itamae.backend
221
+ end
222
+
223
+ def runner
224
+ @recipe.runner
225
+ end
226
+
227
+ def resources
228
+ @recipe.resources
229
+ end
230
+
231
+ def shell_escape(str)
232
+ Shellwords.escape(str)
233
+ end
234
+
235
+ def updated!
236
+ @updated = true
237
+ end
238
+
239
+ def updated?
240
+ @updated
241
+ end
242
+
243
+ def notify
244
+ action_resource_timing = notifies_resources + resources.subscribing(self)
245
+ action_resource_timing.uniq.each do |action, resource, timing|
246
+ case timing
247
+ when :immediately
248
+ resource.run(action)
249
+ when :delay
250
+ @recipe.delayed_actions << [action, resource]
251
+ end
252
+ end
253
+ end
254
+ end
255
+ end
256
+ end
257
+
@@ -0,0 +1,35 @@
1
+ require 'itamae'
2
+
3
+ module Itamae
4
+ module Resource
5
+ class Directory < Base
6
+ define_attribute :action, default: :create
7
+ define_attribute :path, type: String, default_name: true
8
+ define_attribute :mode, type: String
9
+ define_attribute :owner, type: String
10
+ define_attribute :group, type: String
11
+
12
+ def set_current_attributes
13
+ escaped_path = shell_escape(path)
14
+ if run_command("test -d #{escaped_path}", error: false).exit_status == 0
15
+ @current_attributes[:mode] = run_command("stat --format '%a' #{escaped_path}").stdout.chomp
16
+ @current_attributes[:owner] = run_command("stat --format '%U' #{escaped_path}").stdout.chomp
17
+ @current_attributes[:group] = run_command("stat --format '%G' #{escaped_path}").stdout.chomp
18
+ end
19
+ end
20
+
21
+ def create_action
22
+ if ! run_specinfra(:check_file_is_directory, path)
23
+ run_specinfra(:create_file_as_directory, path)
24
+ end
25
+ if attributes[:mode]
26
+ run_specinfra(:change_file_mode, path, attributes[:mode])
27
+ end
28
+ if attributes[:owner] || attributes[:group]
29
+ run_specinfra(:change_file_owner, path, attributes[:owner], attributes[:group])
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+
@@ -0,0 +1,16 @@
1
+ require 'itamae'
2
+
3
+ module Itamae
4
+ module Resource
5
+ class Execute < Base
6
+ define_attribute :action, default: :run
7
+ define_attribute :command, type: String, default_name: true
8
+
9
+ def run_action
10
+ run_command(command)
11
+ updated!
12
+ end
13
+ end
14
+ end
15
+ end
16
+
@@ -1,15 +1,15 @@
1
1
  require 'itamae'
2
2
 
3
3
  module Itamae
4
- module Resources
4
+ module Resource
5
5
  class File < Base
6
- define_option :action, default: :create
7
- define_option :path, type: String, default_name: true
8
- define_option :content, type: String, default: ''
9
- define_option :content_file, type: String
10
- define_option :mode, type: String
11
- define_option :owner, type: String
12
- define_option :group, type: String
6
+ define_attribute :action, default: :create
7
+ define_attribute :path, type: String, default_name: true
8
+ define_attribute :content, type: String, default: ''
9
+ define_attribute :content_file, type: String
10
+ define_attribute :mode, type: String
11
+ define_attribute :owner, type: String
12
+ define_attribute :group, type: String
13
13
 
14
14
  def create_action
15
15
  src = if content_file
@@ -0,0 +1,17 @@
1
+ require 'itamae'
2
+
3
+ module Itamae
4
+ module Resource
5
+ class MailAlias < Base
6
+ define_attribute :action, default: :create
7
+ define_attribute :mail_alias, type: String, default_name: true
8
+ define_attribute :recipient, type: String, required: true
9
+
10
+ def create_action
11
+ if ! backend.check_mail_alias_is_aliased_to(mail_alias, recipient)
12
+ backend.add_mail_alias(mail_alias, recipient)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,10 +1,10 @@
1
1
  require 'itamae'
2
2
 
3
3
  module Itamae
4
- module Resources
4
+ module Resource
5
5
  class Package < Base
6
- define_option :action, default: :install
7
- define_option :name, type: String, default_name: true
6
+ define_attribute :action, default: :install
7
+ define_attribute :name, type: String, default_name: true
8
8
 
9
9
  def install_action
10
10
  run_specinfra(:install_package, name)
@@ -1,9 +1,9 @@
1
1
  require 'itamae'
2
2
 
3
3
  module Itamae
4
- module Resources
4
+ module Resource
5
5
  class RemoteFile < File
6
- define_option :source, type: String, required: true
6
+ define_attribute :source, type: String, required: true
7
7
 
8
8
  def create_action
9
9
  content_file(::File.expand_path(source, ::File.dirname(@recipe.path)))
@@ -3,9 +3,9 @@ require 'erb'
3
3
  require 'tempfile'
4
4
 
5
5
  module Itamae
6
- module Resources
6
+ module Resource
7
7
  class Template < File
8
- define_option :source, type: String, required: true
8
+ define_attribute :source, type: String, required: true
9
9
 
10
10
  def create_action
11
11
  src = ::File.expand_path(source, ::File.dirname(@recipe.path))
@@ -0,0 +1,27 @@
1
+ module Itamae
2
+ class ResourceCollection < Array
3
+ NotFoundError = Class.new(StandardError)
4
+
5
+ def find_by_description(desc)
6
+ # desc is like 'resource_type[name]'
7
+ self.find do |resource|
8
+ type, name = Itamae::Resource.parse_description(desc)
9
+ resource.resource_type == type && resource.resource_name == name
10
+ end.tap do |resource|
11
+ unless resource
12
+ raise NotFoundError, "'#{desc}' resource is not found."
13
+ end
14
+ end
15
+ end
16
+
17
+ def subscribing(target)
18
+ self.map do |resource|
19
+ resource.subscribes_resources.map do |action, r, timing|
20
+ if r == target
21
+ [action, resource, timing]
22
+ end
23
+ end.compact
24
+ end.flatten(1)
25
+ end
26
+ end
27
+ end
@@ -26,7 +26,7 @@ module Itamae
26
26
  private
27
27
  def self.create_backend(type)
28
28
  Specinfra.configuration.backend = type
29
- Itamae.backend = Specinfra.backend
29
+ Itamae.backend = Specinfra::Runner
30
30
  end
31
31
 
32
32
  module SpecinfraHelpers
@@ -1,3 +1,3 @@
1
1
  module Itamae
2
- VERSION = "1.0.0.beta1"
2
+ VERSION = "1.0.0.beta2"
3
3
  end
@@ -30,4 +30,26 @@ describe file('/tmp/file') do
30
30
  its(:content) { should match(/Hello World/) }
31
31
  end
32
32
 
33
+ describe file('/tmp/execute') do
34
+ it { should be_file }
35
+ its(:content) { should match(/Hello Execute/) }
36
+ end
37
+
38
+ describe file('/tmp/never_exist1') do
39
+ it { should_not be_file }
40
+ end
41
+
42
+ describe file('/tmp/never_exist2') do
43
+ it { should_not be_file }
44
+ end
45
+
46
+ describe file('/tmp/notifies') do
47
+ it { should be_file }
48
+ its(:content) { should eq("2431") }
49
+ end
50
+
51
+ describe file('/tmp/subscribes') do
52
+ it { should be_file }
53
+ its(:content) { should eq("2431") }
54
+ end
33
55
 
@@ -4,6 +4,46 @@ end
4
4
 
5
5
  package 'sl'
6
6
 
7
+ ######
8
+
9
+ execute "echo -n > /tmp/notifies"
10
+
11
+ execute "echo -n 1 >> /tmp/notifies" do
12
+ action :nothing
13
+ end
14
+
15
+ execute "echo -n 2 >> /tmp/notifies" do
16
+ notifies :run, "execute[echo -n 1 >> /tmp/notifies]"
17
+ end
18
+
19
+ execute "echo -n 3 >> /tmp/notifies" do
20
+ action :nothing
21
+ end
22
+
23
+ execute "echo -n 4 >> /tmp/notifies" do
24
+ notifies :run, "execute[echo -n 3 >> /tmp/notifies]", :immediately
25
+ end
26
+
27
+ ######
28
+
29
+ execute "echo -n > /tmp/subscribes"
30
+
31
+ execute "echo -n 1 >> /tmp/subscribes" do
32
+ action :nothing
33
+ subscribes :run, "execute[echo -n 2 >> /tmp/subscribes]"
34
+ end
35
+
36
+ execute "echo -n 2 >> /tmp/subscribes"
37
+
38
+ execute "echo -n 3 >> /tmp/subscribes" do
39
+ action :nothing
40
+ subscribes :run, "execute[echo -n 4 >> /tmp/subscribes]", :immediately
41
+ end
42
+
43
+ execute "echo -n 4 >> /tmp/subscribes"
44
+
45
+ ######
46
+
7
47
  remote_file "/tmp/remote_file" do
8
48
  source "hello.txt"
9
49
  end
@@ -22,5 +62,14 @@ file "/tmp/file" do
22
62
  content "Hello World"
23
63
  end
24
64
 
65
+ execute "echo 'Hello Execute' > /tmp/execute"
66
+
67
+ file "/tmp/never_exist1" do
68
+ only_if "exit 1"
69
+ end
70
+
71
+ file "/tmp/never_exist2" do
72
+ not_if "exit 0"
73
+ end
25
74
 
26
75
 
@@ -1,37 +1,37 @@
1
1
  require 'itamae'
2
2
 
3
- class DefineOptionTestResource < Itamae::Resources::Base
4
- define_option :action, default: :create
5
- define_option :default_option, default: :something
6
- define_option :required_option, required: true
7
- define_option :typed_option, type: Numeric
8
- define_option :default_name_option, default_name: true
3
+ class DefineAttributeTestResource < Itamae::Resource::Base
4
+ define_attribute :action, default: :create
5
+ define_attribute :default_attribute, default: :something
6
+ define_attribute :required_attribute, required: true
7
+ define_attribute :typed_attribute, type: Numeric
8
+ define_attribute :default_name_attribute, default_name: true
9
9
  end
10
10
 
11
- describe DefineOptionTestResource do
12
- describe "define_option" do
11
+ describe DefineAttributeTestResource do
12
+ describe "define_attribute" do
13
13
  describe "default" do
14
14
  subject do
15
15
  described_class.new(double(:recipe), 'resource name') do
16
- required_option :required_value
16
+ required_attribute :required_value
17
17
  end
18
18
  end
19
19
  it "returns the default value" do
20
- expect(subject.options[:default_option]).to eq(:something)
20
+ expect(subject.attributes[:default_attribute]).to eq(:something)
21
21
  end
22
22
  end
23
23
 
24
24
  describe "required" do
25
25
  subject do
26
26
  described_class.new(double(:recipe), 'resource name') do
27
- #required_option :required_value
27
+ #required_attribute :required_value
28
28
  end
29
29
  end
30
- context "without setting required option" do
30
+ context "without setting required attribute" do
31
31
  it "raises an error" do
32
32
  expect do
33
33
  subject
34
- end.to raise_error(Itamae::Resources::OptionMissingError)
34
+ end.to raise_error(Itamae::Resource::AttributeMissingError)
35
35
  end
36
36
  end
37
37
  end
@@ -40,26 +40,26 @@ describe DefineOptionTestResource do
40
40
  context "with correct type value" do
41
41
  subject do
42
42
  described_class.new(double(:recipe), 'resource name') do
43
- required_option :required_value
44
- typed_option 10
43
+ required_attribute :required_value
44
+ typed_attribute 10
45
45
  end
46
46
  end
47
47
  it "returns the value" do
48
- expect(subject.options[:typed_option]).to eq(10)
48
+ expect(subject.attributes[:typed_attribute]).to eq(10)
49
49
  end
50
50
  end
51
51
 
52
52
  context "with incorrect type value" do
53
53
  subject do
54
54
  described_class.new(double(:recipe), 'resource name') do
55
- required_option :required_value
56
- typed_option "string"
55
+ required_attribute :required_value
56
+ typed_attribute "string"
57
57
  end
58
58
  end
59
59
  it "raises an error" do
60
60
  expect do
61
61
  subject
62
- end.to raise_error(Itamae::Resources::InvalidTypeError)
62
+ end.to raise_error(Itamae::Resource::InvalidTypeError)
63
63
  end
64
64
  end
65
65
  end
@@ -68,11 +68,11 @@ describe DefineOptionTestResource do
68
68
  context "without setting the value" do
69
69
  subject do
70
70
  described_class.new(double(:recipe), 'resource name') do
71
- required_option :required_value
71
+ required_attribute :required_value
72
72
  end
73
73
  end
74
74
  it "returns the resource name" do
75
- expect(subject.options[:default_name_option]).
75
+ expect(subject.attributes[:default_name_attribute]).
76
76
  to eq("resource name")
77
77
  end
78
78
  end
@@ -80,9 +80,9 @@ describe DefineOptionTestResource do
80
80
  end
81
81
  end
82
82
 
83
- class TestResource < Itamae::Resources::Base
84
- define_option :action, default: :create
85
- define_option :option_key, required: false
83
+ class TestResource < Itamae::Resource::Base
84
+ define_attribute :action, default: :create
85
+ define_attribute :attribute_key, required: false
86
86
  end
87
87
 
88
88
  describe TestResource do
@@ -116,19 +116,19 @@ describe TestResource do
116
116
 
117
117
  describe "#run_specinfra" do
118
118
  it "runs specinfra's command by specinfra's backend" do
119
- expect(Specinfra.command).to receive(:cmd).and_return("command")
119
+ expect(Specinfra.command).to receive(:get).with(:cmd).and_return("command")
120
120
  expect(Itamae.backend).to receive(:run_command).with("command").
121
121
  and_return(Specinfra::CommandResult.new(exit_status: 0))
122
122
  subject.send(:run_specinfra, :cmd)
123
123
  end
124
124
  context "when the command execution failed" do
125
125
  it "raises CommandExecutionError" do
126
- expect(Specinfra.command).to receive(:cmd).and_return("command")
126
+ expect(Specinfra.command).to receive(:get).with(:cmd).and_return("command")
127
127
  expect(Itamae.backend).to receive(:run_command).with("command").
128
128
  and_return(Specinfra::CommandResult.new(exit_status: 1))
129
129
  expect do
130
130
  subject.send(:run_specinfra, :cmd)
131
- end.to raise_error(Itamae::Resources::CommandExecutionError)
131
+ end.to raise_error(Itamae::Resource::CommandExecutionError)
132
132
  end
133
133
  end
134
134
  end
@@ -1,7 +1,7 @@
1
1
  require 'itamae'
2
2
 
3
3
  module Itamae
4
- describe Resources::Package do
4
+ describe Resource::Package do
5
5
  let(:recipe) { double(:recipe) }
6
6
  subject(:resource) { described_class.new(recipe, "name") }
7
7
 
@@ -1,7 +1,7 @@
1
1
  require 'itamae'
2
2
 
3
3
  module Itamae
4
- describe Resources::RemoteFile do
4
+ describe Resource::RemoteFile do
5
5
  let(:recipe) { double(:recipe) }
6
6
  subject(:resource) do
7
7
  described_class.new(recipe, "name") do
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ module Itamae
4
+ describe Resource do
5
+ describe ".get_resource_class_name" do
6
+ let(:method) { :foo_bar_baz }
7
+ it "returns camel-cased string" do
8
+ expect(described_class.get_resource_class_name(method)).
9
+ to eq("FooBarBaz")
10
+ end
11
+ end
12
+
13
+ describe ".parse_description" do
14
+ context "with valid description" do
15
+ it "returns type and name" do
16
+ expect(described_class.parse_description("this-is_type[this-is_name]")).
17
+ to eq(["this-is_type", "this-is_name"])
18
+ end
19
+ end
20
+
21
+ context "with invalid description" do
22
+ it "raises an error" do
23
+ expect do
24
+ described_class.parse_description("[this-is_type][this-is_name]")
25
+ end.to raise_error(Itamae::Resource::ParseError)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: itamae
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta1
4
+ version: 1.0.0.beta2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryota Arai
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-05 00:00:00.000000000 Z
11
+ date: 2014-08-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 2.0.0.beta20
33
+ version: 2.0.0.beta32
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: 2.0.0.beta20
40
+ version: 2.0.0.beta32
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: hashie
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: ansi
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: net-scp
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -161,13 +175,16 @@ files:
161
175
  - lib/itamae/logger.rb
162
176
  - lib/itamae/node.rb
163
177
  - lib/itamae/recipe.rb
164
- - lib/itamae/resources.rb
165
- - lib/itamae/resources/base.rb
166
- - lib/itamae/resources/directory.rb
167
- - lib/itamae/resources/file.rb
168
- - lib/itamae/resources/package.rb
169
- - lib/itamae/resources/remote_file.rb
170
- - lib/itamae/resources/template.rb
178
+ - lib/itamae/resource.rb
179
+ - lib/itamae/resource/base.rb
180
+ - lib/itamae/resource/directory.rb
181
+ - lib/itamae/resource/execute.rb
182
+ - lib/itamae/resource/file.rb
183
+ - lib/itamae/resource/mail_alias.rb
184
+ - lib/itamae/resource/package.rb
185
+ - lib/itamae/resource/remote_file.rb
186
+ - lib/itamae/resource/template.rb
187
+ - lib/itamae/resource_collection.rb
171
188
  - lib/itamae/runner.rb
172
189
  - lib/itamae/specinfra.rb
173
190
  - lib/itamae/version.rb
@@ -181,10 +198,10 @@ files:
181
198
  - spec/unit/lib/itamae/logger_spec.rb
182
199
  - spec/unit/lib/itamae/node_spec.rb
183
200
  - spec/unit/lib/itamae/recipe_spec.rb
184
- - spec/unit/lib/itamae/resources/base_spec.rb
185
- - spec/unit/lib/itamae/resources/package_spec.rb
186
- - spec/unit/lib/itamae/resources/remote_file_spec.rb
187
- - spec/unit/lib/itamae/resources_spec.rb
201
+ - spec/unit/lib/itamae/resource/base_spec.rb
202
+ - spec/unit/lib/itamae/resource/package_spec.rb
203
+ - spec/unit/lib/itamae/resource/remote_file_spec.rb
204
+ - spec/unit/lib/itamae/resource_spec.rb
188
205
  - spec/unit/lib/itamae/runner_spec.rb
189
206
  - spec/unit/spec_helper.rb
190
207
  homepage: https://github.com/ryotarai/itamae
@@ -222,9 +239,9 @@ test_files:
222
239
  - spec/unit/lib/itamae/logger_spec.rb
223
240
  - spec/unit/lib/itamae/node_spec.rb
224
241
  - spec/unit/lib/itamae/recipe_spec.rb
225
- - spec/unit/lib/itamae/resources/base_spec.rb
226
- - spec/unit/lib/itamae/resources/package_spec.rb
227
- - spec/unit/lib/itamae/resources/remote_file_spec.rb
228
- - spec/unit/lib/itamae/resources_spec.rb
242
+ - spec/unit/lib/itamae/resource/base_spec.rb
243
+ - spec/unit/lib/itamae/resource/package_spec.rb
244
+ - spec/unit/lib/itamae/resource/remote_file_spec.rb
245
+ - spec/unit/lib/itamae/resource_spec.rb
229
246
  - spec/unit/lib/itamae/runner_spec.rb
230
247
  - spec/unit/spec_helper.rb
@@ -1,25 +0,0 @@
1
- require 'itamae'
2
- require 'itamae/resources/base'
3
- require 'itamae/resources/file'
4
- require 'itamae/resources/package'
5
- require 'itamae/resources/remote_file'
6
- require 'itamae/resources/directory'
7
- require 'itamae/resources/template'
8
-
9
- module Itamae
10
- module Resources
11
- Error = Class.new(StandardError)
12
- CommandExecutionError = Class.new(StandardError)
13
- OptionMissingError = Class.new(StandardError)
14
- InvalidTypeError = Class.new(StandardError)
15
- NotSupportedOsError = Class.new(StandardError)
16
-
17
- def self.get_resource_class_name(method)
18
- method.to_s.split('_').map {|part| part.capitalize}.join
19
- end
20
-
21
- def self.get_resource_class(method)
22
- const_get(get_resource_class_name(method))
23
- end
24
- end
25
- end
@@ -1,151 +0,0 @@
1
- require 'itamae'
2
- require 'shellwords'
3
-
4
- module Itamae
5
- module Resources
6
- class Base
7
- @defined_options ||= {}
8
- @supported_oses ||= []
9
-
10
- class << self
11
- attr_reader :defined_options
12
- attr_reader :supported_oses
13
-
14
- def inherited(subclass)
15
- subclass.instance_variable_set(
16
- :@defined_options,
17
- self.defined_options.dup
18
- )
19
- end
20
-
21
- def define_option(name, options)
22
- current = @defined_options[name.to_sym] || {}
23
- @defined_options[name.to_sym] = current.merge(options)
24
- end
25
-
26
- def support_os(hash)
27
- @supported_oses << hash
28
- end
29
- end
30
-
31
- define_option :action, type: Symbol, required: true
32
-
33
- attr_reader :resource_name
34
- attr_reader :options
35
-
36
- def initialize(recipe, resource_name, &block)
37
- @options = {}
38
- @recipe = recipe
39
- @resource_name = resource_name
40
-
41
- instance_eval(&block) if block_given?
42
-
43
- process_options
44
- ensure_os
45
- end
46
-
47
- def run
48
- public_send("#{action}_action".to_sym)
49
- end
50
-
51
- def nothing_action
52
- # do nothing
53
- end
54
-
55
- private
56
-
57
- def method_missing(method, *args)
58
- if args.size == 1 && self.class.defined_options[method]
59
- return @options[method] = args.first
60
- elsif args.size == 0 && @options.has_key?(method)
61
- return @options[method]
62
- end
63
- super
64
- end
65
-
66
- def process_options
67
- self.class.defined_options.each_pair do |key, details|
68
- @options[key] ||= @resource_name if details[:default_name]
69
- @options[key] ||= details[:default]
70
-
71
- if details[:required] && !@options[key]
72
- raise Resources::OptionMissingError, "'#{key}' option is required but it is not set."
73
- end
74
-
75
- if @options[key] && details[:type] && !@options[key].is_a?(details[:type])
76
- raise Resources::InvalidTypeError, "#{key} option should be #{details[:type]}."
77
- end
78
- end
79
- end
80
-
81
- def run_specinfra(type, *args)
82
- command = Specinfra.command.public_send(type, *args)
83
- run_command(command)
84
- end
85
-
86
- def run_command(command)
87
- result = backend.run_command(command)
88
- exit_status = result.exit_status
89
-
90
- if exit_status == 0
91
- method = :debug
92
- Logger.public_send(method, "Command `#{command}` succeeded")
93
- else
94
- method = :error
95
- Logger.public_send(method, "Command `#{command}` failed. (exit status: #{exit_status})")
96
- end
97
-
98
- if result.stdout && result.stdout != ''
99
- Logger.public_send(method, "STDOUT> #{result.stdout.chomp}")
100
- end
101
- if result.stderr && result.stderr != ''
102
- Logger.public_send(method, "STDERR> #{result.stderr.chomp}")
103
- end
104
-
105
- unless exit_status == 0
106
- raise CommandExecutionError
107
- end
108
- end
109
-
110
- def copy_file(src, dst)
111
- Logger.debug "Copying a file from '#{src}' to '#{dst}'..."
112
- unless ::File.exist?(src)
113
- raise Error, "The file '#{src}' doesn't exist."
114
- end
115
- unless backend.copy_file(src, dst)
116
- raise Error, "Copying a file failed."
117
- end
118
- end
119
-
120
- def node
121
- runner.node
122
- end
123
-
124
- def backend
125
- Itamae.backend
126
- end
127
-
128
- def runner
129
- @recipe.runner
130
- end
131
-
132
- def shell_escape(str)
133
- Shellwords.escape(str)
134
- end
135
-
136
- def ensure_os
137
- return unless self.class.supported_oses
138
- ok = self.class.supported_oses.any? do |supported|
139
- supported.each_pair.all? do |k, v|
140
- backend.os[k] == v
141
- end
142
- end
143
-
144
- unless ok
145
- raise NotSupportedOsError, "#{self.class.name} resource doesn't support this OS now."
146
- end
147
- end
148
- end
149
- end
150
- end
151
-
@@ -1,26 +0,0 @@
1
- require 'itamae'
2
-
3
- module Itamae
4
- module Resources
5
- class Directory < Base
6
- define_option :action, default: :create
7
- define_option :path, type: String, default_name: true
8
- define_option :mode, type: String
9
- define_option :owner, type: String
10
- define_option :group, type: String
11
-
12
- def create_action
13
- if ! backend.check_file_is_directory(path)
14
- backend.create_file_as_directory(path)
15
- end
16
- if options[:mode]
17
- backend.change_file_mode(path, options[:mode])
18
- end
19
- if options[:owner] || options[:group]
20
- backend.change_file_owner(path, options[:owner], options[:group])
21
- end
22
- end
23
- end
24
- end
25
- end
26
-
@@ -1,14 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Itamae
4
- describe Resources do
5
- describe "#get_resource_class_name" do
6
- let(:method) { :foo_bar_baz }
7
- it "returns camel-cased string" do
8
- expect(described_class.get_resource_class_name(method)).
9
- to eq("FooBarBaz")
10
- end
11
- end
12
- end
13
- end
14
-