itamae 1.0.0.beta1 → 1.0.0.beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
-