circus-deployment 0.0.1 → 0.2
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.
- data/lib/bundler/circus_util.rb +1 -2
- data/lib/bundler/gem_cacher.rb +46 -0
- data/lib/circus.rb +2 -1
- data/lib/circus/act.rb +29 -6
- data/lib/circus/application.rb +49 -6
- data/lib/circus/booth_tool.rb +14 -1
- data/lib/circus/cli.rb +21 -2
- data/lib/circus/clown_client.rb +4 -0
- data/lib/circus/external_util.rb +10 -0
- data/lib/circus/processes/daemontools.rb +65 -0
- data/lib/circus/profiles.rb +2 -0
- data/lib/circus/profiles/base.rb +20 -1
- data/lib/circus/profiles/chef_stack.rb +75 -0
- data/lib/circus/profiles/make_base.rb +4 -1
- data/lib/circus/profiles/maven_webapp.rb +117 -0
- data/lib/circus/profiles/maven_webapp_jetty.xml.erb +74 -0
- data/lib/circus/profiles/maven_webapp_jetty_app.xml.erb +56 -0
- data/lib/circus/profiles/python_base.rb +5 -5
- data/lib/circus/profiles/rack.rb +11 -7
- data/lib/circus/profiles/ruby_base.rb +13 -4
- data/lib/circus/repos/git.rb +2 -1
- data/lib/circus/version.rb +1 -1
- metadata +106 -72
- data/lib/circus/agents/agent.rb +0 -59
- data/lib/circus/agents/logger.rb +0 -52
data/lib/bundler/circus_util.rb
CHANGED
@@ -0,0 +1,46 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler/cli'
|
5
|
+
require 'bundler'
|
6
|
+
|
7
|
+
Bundler.ui = Bundler::UI::Shell.new(Thor::Shell::Basic.new)
|
8
|
+
|
9
|
+
# TODO: Make RUBY_FRAMEWORK_VERSION resolvable instead of using 1.8
|
10
|
+
gem_install_root = "vendor/bundle/ruby/1.8/gems"
|
11
|
+
spec_install_root = "vendor/bundle/ruby/1.8/specifications"
|
12
|
+
FileUtils.mkdir_p(gem_install_root)
|
13
|
+
FileUtils.mkdir_p(spec_install_root)
|
14
|
+
|
15
|
+
Bundler.definition.specs.each do |spec|
|
16
|
+
install_path = "#{gem_install_root}/#{spec.name}-#{spec.version}"
|
17
|
+
|
18
|
+
unless install_path == spec.full_gem_path
|
19
|
+
FileUtils.mkdir_p(File.dirname(install_path))
|
20
|
+
FileUtils.rm_rf install_path
|
21
|
+
FileUtils.cp_r spec.full_gem_path, install_path
|
22
|
+
end
|
23
|
+
|
24
|
+
File.open(File.join(spec_install_root, "#{spec.name}-#{spec.version}.spec"), 'w') do |sf|
|
25
|
+
sf.write(spec.to_ruby)
|
26
|
+
end
|
27
|
+
in_dir_spec = "#{install_path}/#{spec.name}.gemspec"
|
28
|
+
unless File.exists? in_dir_spec
|
29
|
+
File.open(in_dir_spec, 'w') do |sf|
|
30
|
+
sf.write(spec.to_ruby)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
File.open('Gemfile', 'w') do |f|
|
36
|
+
Bundler.definition.specs.each do |s|
|
37
|
+
f << "gem #{s.name.inspect}, #{s.version.to_s.inspect}, :path => '#{gem_install_root}/#{s.name}-#{s.version}'\n"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
# FileUtils.mkdir_p('.bundle')
|
41
|
+
# File.open('.bundle/config', 'w') do |f|
|
42
|
+
# f.puts '---'
|
43
|
+
# f.puts 'BUNDLE_FROZEN: "1"'
|
44
|
+
# f.puts 'BUNDLE_DISABLE_SHARED_GEMS: "1"'
|
45
|
+
# f.puts 'BUNDLE_PATH: vendor/bundle'
|
46
|
+
# end
|
data/lib/circus.rb
CHANGED
@@ -6,4 +6,5 @@ require File.expand_path('../circus/repos', __FILE__)
|
|
6
6
|
require File.expand_path('../circus/booth_client', __FILE__)
|
7
7
|
require File.expand_path('../circus/actstore_client', __FILE__)
|
8
8
|
require File.expand_path('../circus/local_config', __FILE__)
|
9
|
-
require File.expand_path('../circus/connection_builder', __FILE__)
|
9
|
+
require File.expand_path('../circus/connection_builder', __FILE__)
|
10
|
+
require File.expand_path('../circus/processes/daemontools', __FILE__)
|
data/lib/circus/act.rb
CHANGED
@@ -3,7 +3,7 @@ require 'yaml'
|
|
3
3
|
|
4
4
|
module Circus
|
5
5
|
class Act
|
6
|
-
attr_reader :name, :dir, :props
|
6
|
+
attr_reader :name, :dir, :props
|
7
7
|
|
8
8
|
def initialize(name, dir, props = {})
|
9
9
|
@name = name
|
@@ -14,9 +14,18 @@ module Circus
|
|
14
14
|
if File.exists? act_file
|
15
15
|
act_cfg = YAML.load(File.read(act_file))
|
16
16
|
@props.merge! act_cfg
|
17
|
+
|
18
|
+
# Allow act name to be overriden
|
19
|
+
@name = @props['name'] if @props['name']
|
17
20
|
end
|
18
21
|
end
|
19
22
|
|
23
|
+
def profile
|
24
|
+
detect! unless @profile
|
25
|
+
|
26
|
+
@profile
|
27
|
+
end
|
28
|
+
|
20
29
|
def should_package?
|
21
30
|
return false if @props['no-package']
|
22
31
|
true
|
@@ -47,14 +56,14 @@ module Circus
|
|
47
56
|
return false unless profile.package_for_deploy(logger, overlay_dir)
|
48
57
|
|
49
58
|
begin
|
50
|
-
#
|
51
|
-
include_dirs = ["#{overlay_dir}
|
52
|
-
include_dirs << "#{@dir}
|
53
|
-
include_dirs.concat(profile.extra_dirs)
|
59
|
+
# Package up the output file
|
60
|
+
include_dirs = ["-C #{overlay_dir} ."]
|
61
|
+
include_dirs << "-C #{@dir} ." if profile.package_base_dir?
|
62
|
+
include_dirs.concat(profile.extra_dirs.map { |d| "-C #{File.dirname(d)} #{File.basename(d)}"})
|
54
63
|
output_name = File.join(output_root_dir, "#{name}.act")
|
55
64
|
|
56
65
|
ExternalUtil.run_external(logger, 'Output packaging',
|
57
|
-
"
|
66
|
+
"tar -czf #{output_name} --exclude .circus #{include_dirs.join(' ')} 2>&1")
|
58
67
|
ensure
|
59
68
|
profile.cleanup_after_deploy(logger, overlay_dir)
|
60
69
|
end
|
@@ -70,5 +79,19 @@ module Circus
|
|
70
79
|
def act_file
|
71
80
|
File.join(@dir, 'act.yaml')
|
72
81
|
end
|
82
|
+
|
83
|
+
def pause(logger, run_root)
|
84
|
+
working_dir = File.join(run_root, name)
|
85
|
+
process_mgmt = Circus::Processes::Daemontools.new
|
86
|
+
|
87
|
+
process_mgmt.pause_service(name, working_dir, logger)
|
88
|
+
end
|
89
|
+
|
90
|
+
def resume(logger, run_root)
|
91
|
+
working_dir = File.join(run_root, name)
|
92
|
+
process_mgmt = Circus::Processes::Daemontools.new
|
93
|
+
|
94
|
+
process_mgmt.resume_service(name, working_dir, logger)
|
95
|
+
end
|
73
96
|
end
|
74
97
|
end
|
data/lib/circus/application.rb
CHANGED
@@ -29,20 +29,61 @@ module Circus
|
|
29
29
|
# Instructs the application to startup in place and activate all of its associated
|
30
30
|
# acts.
|
31
31
|
def go!(logger)
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
32
|
+
# Ensure that we have svscan available. If we don't, die nice and early
|
33
|
+
`which svscan`
|
34
|
+
if $? != 0
|
35
|
+
logger.error 'The svscan utility (usually provided by daemontools) is not available. ' +
|
36
|
+
' This utility must be installed before the circus go command can function.'
|
37
|
+
# TODO: Detect OS and suggest installation mechanism?
|
38
|
+
|
39
|
+
return
|
37
40
|
end
|
38
|
-
|
41
|
+
|
42
|
+
load! unless @acts
|
43
|
+
|
44
|
+
run_acts = acts.select { |a| a.profile.supported_for_development? }
|
45
|
+
run_acts.each do |act|
|
39
46
|
logger.info "Starting act #{act.name} at #{act.dir} using profile #{act.profile.name}"
|
40
47
|
end
|
41
48
|
logger.info "---------------------"
|
49
|
+
FileUtils.rm_rf(private_run_root)
|
50
|
+
run_acts.each do |act|
|
51
|
+
return unless act.package_for_dev(logger, private_run_root)
|
52
|
+
end
|
42
53
|
|
54
|
+
# If we've loaded bundler ourselves, then we need to remove its environment variables; otherwise,
|
55
|
+
# it will screw up child applications!
|
56
|
+
ENV['BUNDLE_GEMFILE'] = nil
|
57
|
+
ENV['BUNDLE_BIN_PATH'] = nil
|
43
58
|
system("svscan #{private_run_root}")
|
44
59
|
end
|
45
60
|
|
61
|
+
# Instructs the application to stop the given act that is running under development via go.
|
62
|
+
def pause(logger, act_name)
|
63
|
+
load! unless @acts
|
64
|
+
|
65
|
+
target_act = acts.find { |a| a.name == act_name }
|
66
|
+
unless target_act
|
67
|
+
logger.error "Act #{act_name} could not be found"
|
68
|
+
return
|
69
|
+
end
|
70
|
+
|
71
|
+
target_act.pause(logger, private_run_root)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Instructs the application to resume the given act that is running under development via go.
|
75
|
+
def resume(logger, act_name)
|
76
|
+
load! unless @acts
|
77
|
+
|
78
|
+
target_act = acts.find { |a| a.name == act_name }
|
79
|
+
unless target_act
|
80
|
+
logger.error "Act #{act_name} could not be found"
|
81
|
+
return
|
82
|
+
end
|
83
|
+
|
84
|
+
target_act.resume(logger, private_run_root)
|
85
|
+
end
|
86
|
+
|
46
87
|
# Instructs the application to assemble it's components for deployment and generate
|
47
88
|
# act output files.
|
48
89
|
def assemble!(output_dir, logger, dev = false, only_acts = nil)
|
@@ -58,6 +99,8 @@ module Circus
|
|
58
99
|
|
59
100
|
assembly_acts = acts.select {|a| only_acts.nil? or only_acts.include? a.name }
|
60
101
|
|
102
|
+
FileUtils.rm_rf(output_dir)
|
103
|
+
FileUtils.rm_rf(private_overlay_root)
|
61
104
|
FileUtils.mkdir_p(output_dir)
|
62
105
|
assembly_acts.each do |act|
|
63
106
|
act.detect!
|
data/lib/circus/booth_tool.rb
CHANGED
@@ -25,6 +25,11 @@ module Circus
|
|
25
25
|
repo_helper.repo_url
|
26
26
|
end
|
27
27
|
|
28
|
+
unless repo_url
|
29
|
+
@logger.error("Could not detect repository source url")
|
30
|
+
return false
|
31
|
+
end
|
32
|
+
|
28
33
|
app_name = options[:app_name] || File.basename(File.expand_path('.'))
|
29
34
|
|
30
35
|
@logger.info("Creating booth connection #{name} on #{booth} for #{repo_url} as #{app_name}")
|
@@ -42,10 +47,15 @@ module Circus
|
|
42
47
|
|
43
48
|
def admit(name, apps, options)
|
44
49
|
booth = get_booth_or_default(name)
|
45
|
-
return unless booth
|
50
|
+
return false unless booth
|
46
51
|
|
47
52
|
repo_helper = Repos.find_repo_by_id(booth[:repo_type]).new(File.expand_path('.'))
|
48
53
|
current_rev = repo_helper.current_revision
|
54
|
+
|
55
|
+
unless current_rev
|
56
|
+
@logger.error("Could not detect current repository version")
|
57
|
+
return false
|
58
|
+
end
|
49
59
|
|
50
60
|
@logger.info("Starting admission into #{name} of version #{current_rev}")
|
51
61
|
connection = ConnectionBuilder.new(booth).build(booth[:booth])
|
@@ -61,12 +71,15 @@ module Circus
|
|
61
71
|
end
|
62
72
|
admitted = Circus::Agents::Encoding.decode(client.admit(booth[:booth], booth[:booth_id], current_rev, apply_patch_fn, apps).result)
|
63
73
|
|
74
|
+
return false if booth[:target] == 'none'
|
64
75
|
clown_connection = ConnectionBuilder.new(options).build(booth[:target])
|
65
76
|
clown_client = ClownClient.new(clown_connection, @logger)
|
66
77
|
admitted.each do |name, url|
|
67
78
|
@logger.info("Executing deployment of #{name} from #{url} to #{booth[:target]}")
|
68
79
|
clown_client.deploy(booth[:target], name, url).result
|
69
80
|
end
|
81
|
+
|
82
|
+
true
|
70
83
|
end
|
71
84
|
|
72
85
|
private
|
data/lib/circus/cli.rb
CHANGED
@@ -27,6 +27,18 @@ module Circus
|
|
27
27
|
@app.go!(LOGGER)
|
28
28
|
end
|
29
29
|
|
30
|
+
desc "pause ACT_NAME", "Temporarily halts an act that is running in development (via go)"
|
31
|
+
def pause(act_name)
|
32
|
+
load!
|
33
|
+
@app.pause(LOGGER, act_name)
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "resume ACT_NAME", "Resumes a paused act that is running in development (via go)"
|
37
|
+
def resume(act_name)
|
38
|
+
load!
|
39
|
+
@app.resume(LOGGER, act_name)
|
40
|
+
end
|
41
|
+
|
30
42
|
desc "assemble", "Assemble the application's acts for deployment"
|
31
43
|
method_option :output, :default => '.circus/acts/', :desc => 'Destination directory for generated acts'
|
32
44
|
method_option :actstore, :desc => 'URL of the act store to upload the built acts to'
|
@@ -82,7 +94,7 @@ module Circus
|
|
82
94
|
method_option :uncommitted, :desc => 'Indicates that the current uncommitted changes should be sent to the booth and included in the app'
|
83
95
|
def admit(name = nil, *apps)
|
84
96
|
tool = BoothTool.new(LOGGER, LocalConfig.new)
|
85
|
-
tool.admit(name, apps, options)
|
97
|
+
exit 1 unless tool.admit(name, apps, options)
|
86
98
|
end
|
87
99
|
|
88
100
|
desc "exec TARGET [ACT] [CMD]", "Executes the given command in the deployed context of the given act"
|
@@ -95,7 +107,14 @@ module Circus
|
|
95
107
|
|
96
108
|
connection = ConnectionBuilder.new(options).build(target)
|
97
109
|
client = ClownClient.new(connection, LOGGER)
|
98
|
-
client.exec(target, act_name, cmd).result
|
110
|
+
exit 1 unless client.exec(target, act_name, cmd).result
|
111
|
+
end
|
112
|
+
|
113
|
+
desc "reset TARGET ACT", "Restarts the given act on the target host"
|
114
|
+
def reset(target, act_name)
|
115
|
+
connection = ConnectionBuilder.new(options).build(target)
|
116
|
+
client = ClownClient.new(connection, LOGGER)
|
117
|
+
exit 1 unless client.reset(target, act_name).result
|
99
118
|
end
|
100
119
|
|
101
120
|
desc "deploy TARGET NAME ACT", "Deploy the named object using the given act onto the given target server"
|
data/lib/circus/clown_client.rb
CHANGED
@@ -23,5 +23,9 @@ module Circus
|
|
23
23
|
def exec(node, name, cmd)
|
24
24
|
@connection.call(node, 'Clown', 'Clown', 'exec', {'name' => name, 'command' => cmd}, @logger)
|
25
25
|
end
|
26
|
+
|
27
|
+
def reset(node, name)
|
28
|
+
@connection.call(node, 'Clown', 'Clown', 'reset', {'name' => name}, @logger)
|
29
|
+
end
|
26
30
|
end
|
27
31
|
end
|
data/lib/circus/external_util.rb
CHANGED
@@ -0,0 +1,65 @@
|
|
1
|
+
module Circus
|
2
|
+
module Processes
|
3
|
+
# Support for working with Daemontools
|
4
|
+
class Daemontools
|
5
|
+
def pause_service(name, working_dir, logger)
|
6
|
+
res = `svc -d #{working_dir}`
|
7
|
+
if $? != 0
|
8
|
+
logger.error("Failed to initiate service shutdown for #{name}")
|
9
|
+
return
|
10
|
+
end
|
11
|
+
|
12
|
+
unless await_service_pause(name, working_dir, logger)
|
13
|
+
logger.error("Service did not shutdown")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def resume_service(name, working_dir, logger)
|
18
|
+
res = `svc -u #{working_dir}`
|
19
|
+
if $? != 0
|
20
|
+
logger.error("Failed to initiate service startup for #{name}")
|
21
|
+
return
|
22
|
+
end
|
23
|
+
|
24
|
+
unless await_service_startup(name, working_dir, logger)
|
25
|
+
logger.error("Service did not resume")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def await_service_startup(name, working_dir, logger)
|
30
|
+
logger.info("Waiting for startup of #{name}")
|
31
|
+
|
32
|
+
(1..100).each do |i|
|
33
|
+
return true if is_service_running?(working_dir)
|
34
|
+
sleep 1
|
35
|
+
end
|
36
|
+
|
37
|
+
return false
|
38
|
+
end
|
39
|
+
|
40
|
+
def await_service_pause(name, working_dir, logger)
|
41
|
+
logger.info("Waiting for shutdown of #{name}")
|
42
|
+
|
43
|
+
(1..5).each do |i|
|
44
|
+
return true unless is_service_running?(working_dir)
|
45
|
+
sleep 1
|
46
|
+
end
|
47
|
+
|
48
|
+
(1..100).each do |i|
|
49
|
+
# Attempt a more brutal shutdown
|
50
|
+
`svc -k #{working_dir}`
|
51
|
+
|
52
|
+
return true unless is_service_running?(working_dir)
|
53
|
+
sleep 1
|
54
|
+
end
|
55
|
+
|
56
|
+
return false
|
57
|
+
end
|
58
|
+
|
59
|
+
def is_service_running?(working_dir)
|
60
|
+
res = `svstat #{working_dir}`
|
61
|
+
$? == 0 && res.include?('up (pid')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/circus/profiles.rb
CHANGED
@@ -8,3 +8,5 @@ require File.expand_path('../profiles/make_base', __FILE__)
|
|
8
8
|
require File.expand_path('../profiles/shell', __FILE__)
|
9
9
|
require File.expand_path('../profiles/django', __FILE__)
|
10
10
|
require File.expand_path('../profiles/jekyll', __FILE__)
|
11
|
+
require File.expand_path('../profiles/maven_webapp', __FILE__)
|
12
|
+
require File.expand_path('../profiles/chef_stack', __FILE__)
|
data/lib/circus/profiles/base.rb
CHANGED
@@ -14,6 +14,16 @@ module Circus
|
|
14
14
|
@props = props
|
15
15
|
end
|
16
16
|
|
17
|
+
def mark_for_persistent_run?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
# Whether acts using the given profile can be run during development. If this returns false,
|
22
|
+
# then local only commands (such as go) will ignore these items.
|
23
|
+
def supported_for_development?
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
17
27
|
def package_base_dir?
|
18
28
|
true
|
19
29
|
end
|
@@ -34,8 +44,10 @@ module Circus
|
|
34
44
|
write_run_script(run_dir) do |f|
|
35
45
|
f.write(deploy_run_script_content.strip)
|
36
46
|
end
|
47
|
+
|
48
|
+
reqs = build_default_requirements.merge(requirements)
|
37
49
|
File.open(File.join(run_dir, 'requirements.yaml'), 'w') do |f|
|
38
|
-
f.write(
|
50
|
+
f.write(reqs.to_yaml)
|
39
51
|
end
|
40
52
|
end
|
41
53
|
|
@@ -110,6 +122,13 @@ module Circus
|
|
110
122
|
def run_external(logger, desc, cmd)
|
111
123
|
ExternalUtil.run_external(logger, desc, cmd)
|
112
124
|
end
|
125
|
+
|
126
|
+
private
|
127
|
+
def build_default_requirements
|
128
|
+
{
|
129
|
+
'persistent-run' => mark_for_persistent_run?
|
130
|
+
}
|
131
|
+
end
|
113
132
|
end
|
114
133
|
end
|
115
134
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Circus
|
4
|
+
module Profiles
|
5
|
+
# A Chef Stack provides the ability to trigger deployment of system packages and configuration.
|
6
|
+
class ChefStack < Base
|
7
|
+
CHEF_STACK_PROPERTY='chef-stack'
|
8
|
+
|
9
|
+
# Checks if this is a chef stack applcation. Will accept the application if it
|
10
|
+
# has a file named stack.yaml, or has a 'chef-stack' property describing the entry point.
|
11
|
+
def self.accepts?(name, dir, props)
|
12
|
+
return true if props.include? CHEF_STACK_PROPERTY
|
13
|
+
return File.exists?(File.join(dir, "stack.yaml"))
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(name, dir, props)
|
17
|
+
super(name, dir, props)
|
18
|
+
|
19
|
+
@stack_script = props[CHEF_STACK_PROPERTY] || "stack.yaml"
|
20
|
+
end
|
21
|
+
|
22
|
+
# The name of this profile
|
23
|
+
def name
|
24
|
+
"chef-stack"
|
25
|
+
end
|
26
|
+
|
27
|
+
def requirements
|
28
|
+
reqs = super
|
29
|
+
reqs['execution-user'] = 'root'
|
30
|
+
reqs
|
31
|
+
end
|
32
|
+
|
33
|
+
def mark_for_persistent_run?
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
# Stacks are not useful in development
|
38
|
+
def supported_for_development?
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
def dev_run_script_content
|
43
|
+
shell_run_script do
|
44
|
+
<<-EOT
|
45
|
+
cd #{@dir}
|
46
|
+
exec echo "Stacks cannot be run in development"
|
47
|
+
EOT
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def prepare_for_deploy(logger, overlay_dir)
|
52
|
+
# Generate a chef configuration file
|
53
|
+
stack_config = YAML::load(File.read(File.join(@dir, @stack_script)))
|
54
|
+
File.open(File.join(overlay_dir, 'stack.json'), 'w') do |f|
|
55
|
+
f.write(stack_config.to_json)
|
56
|
+
end
|
57
|
+
File.open(File.join(overlay_dir, 'solo-stack.rb'), 'w') do |f|
|
58
|
+
f << "cookbook_path File.expand_path('../cookbooks', __FILE__)"
|
59
|
+
end
|
60
|
+
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
64
|
+
def deploy_run_script_content
|
65
|
+
shell_run_script do
|
66
|
+
<<-EOT
|
67
|
+
exec chef-solo -c solo-stack.rb -j stack.json
|
68
|
+
EOT
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
PROFILES << ChefStack
|
74
|
+
end
|
75
|
+
end
|