itamae 1.10.0 → 1.12.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +203 -0
- data/.gitignore +1 -0
- data/CHANGELOG.md +144 -1
- data/README.md +2 -3
- data/Rakefile +50 -42
- data/bin/itamae +0 -1
- data/itamae.gemspec +2 -2
- data/lib/itamae/backend.rb +20 -5
- data/lib/itamae/cli.rb +14 -9
- data/lib/itamae/definition.rb +0 -2
- data/lib/itamae/logger.rb +5 -1
- data/lib/itamae/mash.rb +7 -0
- data/lib/itamae/node.rb +4 -4
- data/lib/itamae/notification.rb +0 -2
- data/lib/itamae/recipe.rb +20 -3
- data/lib/itamae/resource/base.rb +3 -3
- data/lib/itamae/resource/directory.rb +0 -2
- data/lib/itamae/resource/execute.rb +0 -2
- data/lib/itamae/resource/file.rb +43 -6
- data/lib/itamae/resource/gem_package.rb +0 -2
- data/lib/itamae/resource/git.rb +5 -7
- data/lib/itamae/resource/group.rb +0 -2
- data/lib/itamae/resource/http_request.rb +12 -3
- data/lib/itamae/resource/link.rb +0 -2
- data/lib/itamae/resource/local_ruby_block.rb +0 -2
- data/lib/itamae/resource/package.rb +3 -3
- data/lib/itamae/resource/remote_directory.rb +1 -3
- data/lib/itamae/resource/remote_file.rb +0 -2
- data/lib/itamae/resource/service.rb +0 -2
- data/lib/itamae/resource/template.rb +8 -4
- data/lib/itamae/resource/user.rb +0 -2
- data/lib/itamae/resource.rb +0 -1
- data/lib/itamae/runner.rb +3 -4
- data/lib/itamae/version.rb +1 -1
- data/lib/itamae.rb +1 -1
- data/spec/integration/default_spec.rb +26 -32
- data/spec/integration/docker_spec.rb +29 -0
- data/spec/integration/local_spec.rb +6 -0
- data/spec/integration/ordinary_user_spec.rb +108 -0
- data/spec/integration/recipes/default.rb +10 -48
- data/spec/integration/recipes/docker.rb +44 -0
- data/spec/integration/recipes/local.rb +19 -0
- data/spec/integration/recipes/ordinary_user.rb +109 -0
- data/spec/integration/recipes/toplevel_module.rb +6 -0
- data/spec/integration/recipes/variables.rb +14 -0
- data/spec/unit/lib/itamae/backend_spec.rb +10 -10
- data/tasks/integration_local_spec.rb +117 -0
- metadata +26 -9
- data/.travis.yml +0 -39
data/lib/itamae/mash.rb
ADDED
data/lib/itamae/node.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
require 'itamae'
|
2
1
|
require 'hashie'
|
3
2
|
require 'json'
|
4
3
|
require 'schash'
|
4
|
+
require 'itamae/mash'
|
5
5
|
|
6
6
|
module Itamae
|
7
7
|
class Node
|
@@ -10,7 +10,7 @@ module Itamae
|
|
10
10
|
attr_reader :mash
|
11
11
|
|
12
12
|
def initialize(hash, backend)
|
13
|
-
@mash =
|
13
|
+
@mash = Itamae::Mash.new(hash)
|
14
14
|
@backend = backend
|
15
15
|
end
|
16
16
|
|
@@ -43,7 +43,7 @@ module Itamae
|
|
43
43
|
private
|
44
44
|
|
45
45
|
def _reverse_merge(other_hash)
|
46
|
-
|
46
|
+
Itamae::Mash.new(other_hash).merge(@mash)
|
47
47
|
end
|
48
48
|
|
49
49
|
def method_missing(method, *args)
|
@@ -63,7 +63,7 @@ module Itamae
|
|
63
63
|
def fetch_inventory_value(key)
|
64
64
|
value = @backend.host_inventory[key]
|
65
65
|
if value.is_a?(Hash)
|
66
|
-
value =
|
66
|
+
value = Itamae::Mash.new(value)
|
67
67
|
end
|
68
68
|
|
69
69
|
value
|
data/lib/itamae/notification.rb
CHANGED
data/lib/itamae/recipe.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'itamae'
|
2
|
-
|
3
1
|
module Itamae
|
4
2
|
class Recipe
|
5
3
|
NotFoundError = Class.new(StandardError)
|
@@ -55,7 +53,7 @@ module Itamae
|
|
55
53
|
|
56
54
|
def load(vars = {})
|
57
55
|
context = EvalContext.new(self, vars)
|
58
|
-
|
56
|
+
InstanceEval.new(File.read(path), path, 1, context: context).call
|
59
57
|
end
|
60
58
|
|
61
59
|
def run
|
@@ -153,6 +151,25 @@ module Itamae
|
|
153
151
|
end
|
154
152
|
end
|
155
153
|
|
154
|
+
class InstanceEval
|
155
|
+
def initialize(src, path, lineno, context:)
|
156
|
+
# Using instance_eval + eval to allow top-level class/module definition without `::`.
|
157
|
+
# To pass args without introducing any local/instance variables, this code is also eval-ed.
|
158
|
+
@code = <<-RUBY
|
159
|
+
@context.instance_eval do
|
160
|
+
eval(#{src.dump}, nil, #{path.dump}, #{lineno})
|
161
|
+
end
|
162
|
+
RUBY
|
163
|
+
@context = context
|
164
|
+
end
|
165
|
+
|
166
|
+
# This method has no local variables to avoid spilling them to recipes.
|
167
|
+
def call
|
168
|
+
eval(@code)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
private_constant :InstanceEval
|
172
|
+
|
156
173
|
class RecipeFromDefinition < Recipe
|
157
174
|
attr_accessor :definition
|
158
175
|
|
data/lib/itamae/resource/base.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'itamae'
|
2
1
|
require 'shellwords'
|
3
2
|
require 'hashie'
|
4
3
|
|
@@ -16,7 +15,7 @@ module Itamae
|
|
16
15
|
def initialize(resource)
|
17
16
|
@resource = resource
|
18
17
|
|
19
|
-
@attributes =
|
18
|
+
@attributes = Itamae::Mash.new
|
20
19
|
@notifications = []
|
21
20
|
@subscriptions = []
|
22
21
|
@verify_commands = []
|
@@ -196,6 +195,7 @@ module Itamae
|
|
196
195
|
show_differences
|
197
196
|
|
198
197
|
method_name = "action_#{action}"
|
198
|
+
Itamae.logger.debug "(in #{method_name})"
|
199
199
|
if runner.dry_run?
|
200
200
|
unless respond_to?(method_name)
|
201
201
|
Itamae.logger.error "action #{action.inspect} is unavailable"
|
@@ -222,7 +222,7 @@ module Itamae
|
|
222
222
|
end
|
223
223
|
|
224
224
|
def clear_current_attributes
|
225
|
-
@current_attributes =
|
225
|
+
@current_attributes = Itamae::Mash.new
|
226
226
|
end
|
227
227
|
|
228
228
|
def pre_action
|
data/lib/itamae/resource/file.rb
CHANGED
@@ -1,16 +1,19 @@
|
|
1
|
-
require 'itamae'
|
2
|
-
|
3
1
|
module Itamae
|
4
2
|
module Resource
|
5
3
|
class File < Base
|
6
4
|
define_attribute :action, default: :create
|
7
5
|
define_attribute :path, type: String, default_name: true
|
8
6
|
define_attribute :content, type: String, default: nil
|
7
|
+
define_attribute :sensitive, default: false
|
9
8
|
define_attribute :mode, type: String
|
10
9
|
define_attribute :owner, type: String
|
11
10
|
define_attribute :group, type: String
|
12
11
|
define_attribute :block, type: Proc, default: proc {}
|
13
12
|
|
13
|
+
class << self
|
14
|
+
attr_accessor :sha256sum_available
|
15
|
+
end
|
16
|
+
|
14
17
|
def pre_action
|
15
18
|
current.exist = run_specinfra(:check_file_is_file, attributes.path)
|
16
19
|
|
@@ -29,6 +32,11 @@ module Itamae
|
|
29
32
|
end
|
30
33
|
end
|
31
34
|
|
35
|
+
if exists_and_not_modified?
|
36
|
+
attributes.modified = false
|
37
|
+
return
|
38
|
+
end
|
39
|
+
|
32
40
|
send_tempfile
|
33
41
|
compare_file
|
34
42
|
end
|
@@ -135,10 +143,28 @@ module Itamae
|
|
135
143
|
end
|
136
144
|
end
|
137
145
|
|
146
|
+
def exists_and_not_modified?
|
147
|
+
return false unless current.exist && sha256sum_available?
|
148
|
+
|
149
|
+
current_digest = run_command(["sha256sum", attributes.path]).stdout.split(/\s/, 2).first
|
150
|
+
digest = if content_file
|
151
|
+
Digest::SHA256.file(content_file).hexdigest
|
152
|
+
else
|
153
|
+
Digest::SHA256.hexdigest(attributes.content.to_s)
|
154
|
+
end
|
155
|
+
|
156
|
+
current_digest == digest
|
157
|
+
end
|
158
|
+
|
138
159
|
def show_content_diff
|
160
|
+
if attributes.sensitive
|
161
|
+
Itamae.logger.info("diff exists, but not displaying sensitive content")
|
162
|
+
return
|
163
|
+
end
|
164
|
+
|
139
165
|
if attributes.modified
|
140
166
|
Itamae.logger.info "diff:"
|
141
|
-
diff = run_command(["diff", "-u", compare_to, @temppath], error: false)
|
167
|
+
diff = run_command(["diff", "-u", "--label=#{attributes.path} (BEFORE)", compare_to, "--label=#{attributes.path} (AFTER)", @temppath], error: false)
|
142
168
|
diff.stdout.each_line do |line|
|
143
169
|
color = if line.start_with?('+')
|
144
170
|
:green
|
@@ -173,7 +199,12 @@ module Itamae
|
|
173
199
|
src = if content_file
|
174
200
|
content_file
|
175
201
|
else
|
176
|
-
f =
|
202
|
+
f =
|
203
|
+
if Gem.win_platform?
|
204
|
+
Tempfile.open('itamae', :mode=>IO::BINARY)
|
205
|
+
else
|
206
|
+
Tempfile.open('itamae')
|
207
|
+
end
|
177
208
|
f.write(attributes.content)
|
178
209
|
f.close
|
179
210
|
f.path
|
@@ -183,12 +214,12 @@ module Itamae
|
|
183
214
|
|
184
215
|
if backend.is_a?(Itamae::Backend::Docker)
|
185
216
|
run_command(["mkdir", @temppath])
|
186
|
-
backend.send_file(src, @temppath)
|
217
|
+
backend.send_file(src, @temppath, user: attributes.user)
|
187
218
|
@temppath = ::File.join(@temppath, ::File.basename(src))
|
188
219
|
else
|
189
220
|
run_command(["touch", @temppath])
|
190
221
|
run_specinfra(:change_file_mode, @temppath, '0600')
|
191
|
-
backend.send_file(src, @temppath)
|
222
|
+
backend.send_file(src, @temppath, user: attributes.user)
|
192
223
|
end
|
193
224
|
|
194
225
|
run_specinfra(:change_file_mode, @temppath, '0600')
|
@@ -196,6 +227,12 @@ module Itamae
|
|
196
227
|
f.unlink if f
|
197
228
|
end
|
198
229
|
end
|
230
|
+
|
231
|
+
def sha256sum_available?
|
232
|
+
return self.class.sha256sum_available unless self.class.sha256sum_available.nil?
|
233
|
+
|
234
|
+
self.class.sha256sum_available = run_command(["sha256sum", "--version"], error: false).exit_status == 0
|
235
|
+
end
|
199
236
|
end
|
200
237
|
end
|
201
238
|
end
|
data/lib/itamae/resource/git.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'itamae'
|
2
|
-
|
3
1
|
module Itamae
|
4
2
|
module Resource
|
5
3
|
class Git < Base
|
@@ -86,11 +84,11 @@ module Itamae
|
|
86
84
|
end
|
87
85
|
|
88
86
|
def get_revision(branch)
|
89
|
-
result = run_command_in_repo("git rev-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
run_command_in_repo("git rev-
|
87
|
+
result = run_command_in_repo("git rev-parse #{shell_escape(branch)}", error: false)
|
88
|
+
return result.stdout.strip if result.exit_status == 0
|
89
|
+
|
90
|
+
fetch_origin!
|
91
|
+
run_command_in_repo("git rev-parse #{shell_escape(branch)}").stdout.strip
|
94
92
|
end
|
95
93
|
|
96
94
|
def fetch_origin!
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'itamae'
|
2
1
|
require 'uri'
|
3
2
|
require 'net/https'
|
4
3
|
|
@@ -6,6 +5,9 @@ module Itamae
|
|
6
5
|
module Resource
|
7
6
|
class HttpRequest < File
|
8
7
|
RedirectLimitExceeded = Class.new(StandardError)
|
8
|
+
HTTPClientError = Class.new(StandardError)
|
9
|
+
HTTPServerError = Class.new(StandardError)
|
10
|
+
HTTPUnknownError = Class.new(StandardError)
|
9
11
|
|
10
12
|
alias_method :_action_create, :action_create
|
11
13
|
undef_method :action_create, :action_delete, :action_edit
|
@@ -49,7 +51,10 @@ module Itamae
|
|
49
51
|
response = http.method(attributes.action).call(uri.request_uri, attributes.message, attributes.headers)
|
50
52
|
end
|
51
53
|
|
52
|
-
|
54
|
+
case response
|
55
|
+
when Net::HTTPSuccess
|
56
|
+
break
|
57
|
+
when Net::HTTPRedirection
|
53
58
|
if redirects_followed < attributes.redirect_limit
|
54
59
|
uri = URI.parse(response["location"])
|
55
60
|
redirects_followed += 1
|
@@ -57,8 +62,12 @@ module Itamae
|
|
57
62
|
else
|
58
63
|
raise RedirectLimitExceeded
|
59
64
|
end
|
65
|
+
when Net::HTTPClientError
|
66
|
+
raise HTTPClientError
|
67
|
+
when Net::HTTPServerError
|
68
|
+
raise HTTPServerError
|
60
69
|
else
|
61
|
-
|
70
|
+
raise HTTPUnknownError
|
62
71
|
end
|
63
72
|
end
|
64
73
|
|
data/lib/itamae/resource/link.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'itamae'
|
2
|
-
|
3
1
|
module Itamae
|
4
2
|
module Resource
|
5
3
|
class Package < Base
|
@@ -26,6 +24,8 @@ module Itamae
|
|
26
24
|
end
|
27
25
|
|
28
26
|
def action_install(action_options)
|
27
|
+
return if !attributes.version && current.installed
|
28
|
+
|
29
29
|
unless run_specinfra(:check_package_is_installed, attributes.name, attributes.version)
|
30
30
|
run_specinfra(:install_package, attributes.name, attributes.version, attributes.options)
|
31
31
|
updated!
|
@@ -33,7 +33,7 @@ module Itamae
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def action_remove(action_options)
|
36
|
-
if
|
36
|
+
if current.installed
|
37
37
|
run_specinfra(:remove_package, attributes.name, attributes.options)
|
38
38
|
updated!
|
39
39
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'itamae'
|
2
|
-
|
3
1
|
module Itamae
|
4
2
|
module Resource
|
5
3
|
class RemoteDirectory < Base
|
@@ -41,7 +39,7 @@ module Itamae
|
|
41
39
|
super
|
42
40
|
|
43
41
|
if current.exist
|
44
|
-
diff = run_command(["diff", "-u", attributes.path, @temppath], error: false)
|
42
|
+
diff = run_command(["diff", "-u", "-r", attributes.path, @temppath], error: false)
|
45
43
|
if diff.exit_status == 0
|
46
44
|
# no change
|
47
45
|
Itamae.logger.debug "directory content will not change"
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'itamae'
|
2
1
|
require 'erb'
|
3
2
|
require 'tempfile'
|
4
3
|
|
@@ -38,9 +37,14 @@ module Itamae
|
|
38
37
|
|
39
38
|
def render_file(src)
|
40
39
|
template = ::File.read(src)
|
41
|
-
|
42
|
-
|
43
|
-
|
40
|
+
erb =
|
41
|
+
if ERB.instance_method(:initialize).parameters.assoc(:key) # Ruby 2.6+
|
42
|
+
ERB.new(template, trim_mode: '-')
|
43
|
+
else
|
44
|
+
ERB.new(template, nil, '-')
|
45
|
+
end
|
46
|
+
erb.filename = src
|
47
|
+
erb.result(binding)
|
44
48
|
end
|
45
49
|
|
46
50
|
def node
|
data/lib/itamae/resource/user.rb
CHANGED
data/lib/itamae/resource.rb
CHANGED
data/lib/itamae/runner.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'itamae'
|
2
1
|
require 'json'
|
3
2
|
require 'yaml'
|
4
3
|
|
@@ -31,7 +30,7 @@ module Itamae
|
|
31
30
|
prepare_handler
|
32
31
|
|
33
32
|
@node = create_node
|
34
|
-
@tmpdir =
|
33
|
+
@tmpdir = options[:tmp_dir] || '/tmp/itamae_tmp'
|
35
34
|
@children = RecipeChildren.new
|
36
35
|
@diff = false
|
37
36
|
|
@@ -99,11 +98,11 @@ module Itamae
|
|
99
98
|
unless @backend.run_command("which ohai", error: false).exit_status == 0
|
100
99
|
# install Ohai
|
101
100
|
Itamae.logger.info "Installing Chef package... (to use Ohai)"
|
102
|
-
@backend.run_command("curl -L https://
|
101
|
+
@backend.run_command("curl -L https://omnitruck.chef.io/install.sh | bash")
|
103
102
|
end
|
104
103
|
|
105
104
|
Itamae.logger.info "Loading node data via ohai..."
|
106
|
-
hash.merge!(JSON.parse(@backend.run_command("ohai").stdout))
|
105
|
+
hash.merge!(JSON.parse(@backend.run_command("ohai 2>/dev/null").stdout))
|
107
106
|
end
|
108
107
|
|
109
108
|
if @options[:node_json]
|
data/lib/itamae/version.rb
CHANGED
data/lib/itamae.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require "itamae/version"
|
2
2
|
require "itamae/runner"
|
3
|
-
require "itamae/cli"
|
4
3
|
require "itamae/recipe"
|
5
4
|
require "itamae/resource"
|
6
5
|
require "itamae/handler"
|
@@ -13,6 +12,7 @@ require "itamae/notification"
|
|
13
12
|
require "itamae/definition"
|
14
13
|
require "itamae/ext"
|
15
14
|
require "itamae/generators"
|
15
|
+
require "itamae/mash"
|
16
16
|
|
17
17
|
module Itamae
|
18
18
|
# Your code goes here...
|
@@ -104,7 +104,7 @@ describe file('/tmp/http_request_headers.html') do
|
|
104
104
|
its(:content) { should match(/"User-Agent":\s*"Itamae"/) }
|
105
105
|
end
|
106
106
|
|
107
|
-
|
107
|
+
xdescribe file('/tmp/http_request_redirect.html') do
|
108
108
|
it { should be_file }
|
109
109
|
its(:content) { should match(/"from":\s*"itamae"/) }
|
110
110
|
end
|
@@ -119,34 +119,6 @@ describe file('/tmp/subscribes') do
|
|
119
119
|
its(:content) { should eq("2431") }
|
120
120
|
end
|
121
121
|
|
122
|
-
describe file('/tmp/cron_stopped') do
|
123
|
-
it { should be_file }
|
124
|
-
its(:content) do
|
125
|
-
expect(subject.content.lines.size).to eq 1
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
# FIXME: cron service is not running in docker...
|
130
|
-
#
|
131
|
-
# root@3450c6da6ea5:/# ps -C cron
|
132
|
-
# PID TTY TIME CMD
|
133
|
-
# root@3450c6da6ea5:/# service cron start
|
134
|
-
# Rather than invoking init scripts through /etc/init.d, use the service(8)
|
135
|
-
# utility, e.g. service cron start
|
136
|
-
#
|
137
|
-
# Since the script you are attempting to invoke has been converted to an
|
138
|
-
# Upstart job, you may also use the start(8) utility, e.g. start cron
|
139
|
-
# root@3450c6da6ea5:/# ps -C cron
|
140
|
-
# PID TTY TIME CMD
|
141
|
-
# root@3450c6da6ea5:/#
|
142
|
-
|
143
|
-
# describe file('/tmp/cron_running') do
|
144
|
-
# it { should be_file }
|
145
|
-
# its(:content) do
|
146
|
-
# expect(subject.content.lines.size).to eq 2
|
147
|
-
# end
|
148
|
-
# end
|
149
|
-
|
150
122
|
describe file('/tmp-link') do
|
151
123
|
it { should be_linked_to '/tmp' }
|
152
124
|
its(:content) do
|
@@ -206,15 +178,19 @@ describe command('gem list') do
|
|
206
178
|
end
|
207
179
|
|
208
180
|
describe command('gem list') do
|
209
|
-
its(:stdout) { should
|
181
|
+
its(:stdout) { should match(/^rake \(.*11.1.0.*\)/) }
|
210
182
|
end
|
211
183
|
|
212
184
|
describe command('gem list') do
|
213
185
|
its(:stdout) { should_not include('test-unit') }
|
214
186
|
end
|
215
187
|
|
216
|
-
describe command('
|
217
|
-
its(:
|
188
|
+
describe command('gem list') do
|
189
|
+
its(:stdout) { should include('ast (2.0.0)') }
|
190
|
+
end
|
191
|
+
|
192
|
+
describe command('ri AST') do
|
193
|
+
its(:stderr) { should eq("Nothing known about AST\n") }
|
218
194
|
end
|
219
195
|
|
220
196
|
describe file('/tmp/created_by_definition') do
|
@@ -357,3 +333,21 @@ describe file('/tmp/empty_file3') do
|
|
357
333
|
it { should be_file }
|
358
334
|
its(:content) { should eq "" }
|
359
335
|
end
|
336
|
+
|
337
|
+
describe file('/tmp/toplevel_module') do
|
338
|
+
it { should exist }
|
339
|
+
it { should be_file }
|
340
|
+
its(:content) { should eq "helper" }
|
341
|
+
end
|
342
|
+
|
343
|
+
describe file('/tmp/local_variables') do
|
344
|
+
it { should exist }
|
345
|
+
it { should be_file }
|
346
|
+
its(:content) { should eq "[]" }
|
347
|
+
end
|
348
|
+
|
349
|
+
describe file('/tmp/instance_variables') do
|
350
|
+
it { should exist }
|
351
|
+
it { should be_file }
|
352
|
+
its(:content) { should eq "[:@recipe]" } # backward compatibility
|
353
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe file('/tmp/cron_stopped') do
|
4
|
+
it { should be_file }
|
5
|
+
its(:content) do
|
6
|
+
expect(subject.content.lines.size).to eq 1
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
# FIXME: cron service is not running in docker...
|
11
|
+
#
|
12
|
+
# root@3450c6da6ea5:/# ps -C cron
|
13
|
+
# PID TTY TIME CMD
|
14
|
+
# root@3450c6da6ea5:/# service cron start
|
15
|
+
# Rather than invoking init scripts through /etc/init.d, use the service(8)
|
16
|
+
# utility, e.g. service cron start
|
17
|
+
#
|
18
|
+
# Since the script you are attempting to invoke has been converted to an
|
19
|
+
# Upstart job, you may also use the start(8) utility, e.g. start cron
|
20
|
+
# root@3450c6da6ea5:/# ps -C cron
|
21
|
+
# PID TTY TIME CMD
|
22
|
+
# root@3450c6da6ea5:/#
|
23
|
+
|
24
|
+
# describe file('/tmp/cron_running') do
|
25
|
+
# it { should be_file }
|
26
|
+
# its(:content) do
|
27
|
+
# expect(subject.content.lines.size).to eq 2
|
28
|
+
# end
|
29
|
+
# end
|