stackmate 0.0.1 → 0.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/README.md +7 -3
- data/bin/stackmate +2 -0
- data/bin/stackmate.rb +74 -0
- data/lib/stackmate/participants/cloudstack.rb +16 -11
- data/lib/stackmate/participants/common.rb +16 -8
- data/lib/stackmate/stack.rb +5 -12
- data/lib/stackmate/stack_executor.rb +18 -4
- data/lib/stackmate/version.rb +1 -1
- data/stackmate.gemspec +3 -2
- metadata +10 -7
data/README.md
CHANGED
@@ -64,9 +64,13 @@ $ export URL="http://localhost:8080/client/api"
|
|
64
64
|
You need a couple of mappings from AWS ids to your CloudStack implementation:
|
65
65
|
|
66
66
|
```bash
|
67
|
-
$ cat local.
|
68
|
-
|
69
|
-
|
67
|
+
$ cat local.yml
|
68
|
+
---
|
69
|
+
service_offerings:
|
70
|
+
m1.small: 1c8db272-f95a-406c-bce3-39192ce965fa
|
71
|
+
templates:
|
72
|
+
ami-1b814f72: 3ea4563e-c7eb-11e2-b0ed-7f3baba63e45
|
73
|
+
zoneid: b3409835-02b0-4d21-bba4-1f659402117e
|
70
74
|
```
|
71
75
|
|
72
76
|
* Ensure you have a ssh keypair called 'Foo' (used in the template parameter below) for your account FIRST:
|
data/bin/stackmate
ADDED
data/bin/stackmate.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'ruote'
|
2
|
+
require 'ruote/storage/hash_storage'
|
3
|
+
require 'optparse'
|
4
|
+
require 'stackmate'
|
5
|
+
require 'stackmate/classmap'
|
6
|
+
require 'stackmate/waitcondition_server'
|
7
|
+
|
8
|
+
|
9
|
+
options = {}
|
10
|
+
stack_name = ''
|
11
|
+
opt_parser = OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: stackmate.rb STACK_NAME [options]"
|
13
|
+
opts.separator ""
|
14
|
+
opts.separator "Specific options:"
|
15
|
+
opts.on(:REQUIRED, "--template-file FILE", String, "Path to the file that contains the template") do |f|
|
16
|
+
options[:file] = f
|
17
|
+
end
|
18
|
+
opts.on("-p", "--parameters [KEY1=VALUE1 KEY2=VALUE2..]", "Parameter values used to create the stack.") do |p|
|
19
|
+
options[:params] = p
|
20
|
+
puts p
|
21
|
+
end
|
22
|
+
options[:wait_conditions] = true
|
23
|
+
opts.on("-n", "--no-wait-conditions", "Do not create any wait conditions") do
|
24
|
+
options[:wait_conditions] = false
|
25
|
+
end
|
26
|
+
options[:dry_run] = false
|
27
|
+
opts.on("-r", "--dry-run", "Parse and pretend to execute but not actually do anything. Useful for validating the template") do
|
28
|
+
options[:dry_run] = true
|
29
|
+
end
|
30
|
+
opts.on("-h", "--help", "Show this message") do
|
31
|
+
puts opts
|
32
|
+
exit
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
begin
|
37
|
+
opt_parser.parse!(ARGV)
|
38
|
+
if ARGV.size == 1
|
39
|
+
stack_name = ARGV[0]
|
40
|
+
end
|
41
|
+
rescue => e
|
42
|
+
puts e.message.capitalize
|
43
|
+
puts opt_parser.help()
|
44
|
+
exit 1
|
45
|
+
end
|
46
|
+
|
47
|
+
if options[:file] && stack_name != ''
|
48
|
+
if options[:wait_conditions]
|
49
|
+
Thread.new do
|
50
|
+
StackMate::WaitConditionServer.run!
|
51
|
+
end
|
52
|
+
end
|
53
|
+
engine = Ruote::Dashboard.new(
|
54
|
+
Ruote::Worker.new(
|
55
|
+
Ruote::HashStorage.new))
|
56
|
+
engine.noisy = ENV['NOISY'] == 'true'
|
57
|
+
|
58
|
+
unknown = nil
|
59
|
+
unresolved = catch(:unresolved) do
|
60
|
+
unknown = catch(:unknown) do
|
61
|
+
StackMate.configure('NOOP') if options[:dry_run]
|
62
|
+
opts = {}
|
63
|
+
api_opts = {:APIKEY => "#{ENV['APIKEY']}", :SECKEY => "#{ENV['SECKEY']}", :URL => "#{ENV['URL']}" }
|
64
|
+
p = StackMate::StackExecutor.new(options[:file], stack_name, options[:params], engine, options[:wait_conditions], api_opts)
|
65
|
+
p.launch()
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
puts 'Failed to resolve parameters ' + unresolved.to_s if unresolved
|
71
|
+
print "Sorry, I don't know how to create resources of type: ", unknown, "\n" if unknown
|
72
|
+
else
|
73
|
+
puts opt_parser.help()
|
74
|
+
end
|
@@ -16,10 +16,11 @@ class CloudStackResource < Ruote::Participant
|
|
16
16
|
|
17
17
|
attr_reader :name
|
18
18
|
|
19
|
-
def initialize()
|
20
|
-
@
|
21
|
-
@
|
22
|
-
@
|
19
|
+
def initialize(opts)
|
20
|
+
@opts = opts
|
21
|
+
@url = opts['URL'] || ENV['URL'] or raise ArgumentError.new("CloudStackResources: no URL supplied for CloudStack API")
|
22
|
+
@apikey = opts['APIKEY'] || ENV['APIKEY'] or raise ArgumentError.new("CloudStackResources: no api key supplied for CloudStack API")
|
23
|
+
@seckey = opts['SECKEY'] || ENV['SECKEY'] or raise ArgumentError.new("CloudStackResources: no secret key supplied for CloudStack API")
|
23
24
|
@client = CloudstackRubyClient::Client.new(@url, @apikey, @seckey, false)
|
24
25
|
end
|
25
26
|
|
@@ -65,17 +66,17 @@ class CloudStackResource < Ruote::Participant
|
|
65
66
|
end
|
66
67
|
|
67
68
|
class CloudStackInstance < CloudStackResource
|
68
|
-
def initialize()
|
69
|
-
super
|
69
|
+
def initialize(opts)
|
70
|
+
super (opts)
|
70
71
|
@localized = {}
|
71
72
|
load_local_mappings()
|
72
73
|
end
|
73
74
|
|
74
75
|
def on_workitem
|
75
|
-
|
76
|
+
workitem[participant_name] = {}
|
77
|
+
myname = participant_name
|
76
78
|
@name = myname
|
77
79
|
resolved = workitem.fields['ResolvedNames']
|
78
|
-
resolved['AWS::StackId'] = workitem.fei.wfid #TODO put this at launch time
|
79
80
|
props = workitem.fields['Resources'][workitem.participant_name]['Properties']
|
80
81
|
security_group_names = []
|
81
82
|
props['SecurityGroups'].each do |sg|
|
@@ -103,6 +104,8 @@ class CloudStackInstance < CloudStackResource
|
|
103
104
|
resultobj = make_request('deployVirtualMachine', args)
|
104
105
|
logger.debug("Created resource #{myname}")
|
105
106
|
|
107
|
+
logger.debug("result = #{resultobj.inspect}")
|
108
|
+
workitem[participant_name][:physical_id] = resultobj['virtualmachine']['id']
|
106
109
|
reply
|
107
110
|
end
|
108
111
|
|
@@ -118,9 +121,9 @@ class CloudStackInstance < CloudStackResource
|
|
118
121
|
|
119
122
|
def load_local_mappings()
|
120
123
|
begin
|
121
|
-
@localized = YAML.load_file('local.
|
124
|
+
@localized = YAML.load_file('local.yml')
|
122
125
|
rescue
|
123
|
-
|
126
|
+
logger.warning "Warning: Failed to load localized mappings from local.yaml\n"
|
124
127
|
end
|
125
128
|
end
|
126
129
|
|
@@ -166,6 +169,7 @@ end
|
|
166
169
|
class CloudStackSecurityGroup < CloudStackResource
|
167
170
|
def on_workitem
|
168
171
|
myname = workitem.participant_name
|
172
|
+
workitem[participant_name] = {}
|
169
173
|
logger.debug("Going to create resource #{myname}")
|
170
174
|
@name = myname
|
171
175
|
p myname
|
@@ -176,7 +180,7 @@ class CloudStackSecurityGroup < CloudStackResource
|
|
176
180
|
args = { 'name' => name,
|
177
181
|
'description' => props['GroupDescription']
|
178
182
|
}
|
179
|
-
make_request('createSecurityGroup', args)
|
183
|
+
sg_resp = make_request('createSecurityGroup', args)
|
180
184
|
logger.debug("created resource #{myname}")
|
181
185
|
props['SecurityGroupIngress'].each do |rule|
|
182
186
|
cidrIp = rule['CidrIp']
|
@@ -194,6 +198,7 @@ class CloudStackSecurityGroup < CloudStackResource
|
|
194
198
|
#TODO handle usersecuritygrouplist
|
195
199
|
make_request('authorizeSecurityGroupIngress', args)
|
196
200
|
end
|
201
|
+
workitem[participant_name][:physical_id] = sg_resp['securitygroup']['id']
|
197
202
|
reply
|
198
203
|
end
|
199
204
|
end
|
@@ -7,13 +7,14 @@ class WaitConditionHandle < Ruote::Participant
|
|
7
7
|
include Logging
|
8
8
|
|
9
9
|
def on_workitem
|
10
|
-
|
11
|
-
|
12
|
-
presigned_url = 'http://localhost:4567/waitcondition/' + workitem.fei.wfid + '/' +
|
13
|
-
workitem.fields['ResolvedNames'][
|
10
|
+
logger.debug "Entering #{participant_name} "
|
11
|
+
workitem[participant_name] = {}
|
12
|
+
presigned_url = 'http://localhost:4567/waitcondition/' + workitem.fei.wfid + '/' + participant_name
|
13
|
+
workitem.fields['ResolvedNames'][participant_name] = presigned_url
|
14
14
|
logger.info "Your pre-signed URL is: #{presigned_url} "
|
15
15
|
logger.info "Try: \ncurl -X PUT --data 'foo' #{presigned_url}"
|
16
|
-
WaitCondition.create_handle(
|
16
|
+
WaitCondition.create_handle(participant_name, presigned_url)
|
17
|
+
workitem[participant_name][:physical_id] = presigned_url
|
17
18
|
|
18
19
|
reply
|
19
20
|
end
|
@@ -23,10 +24,13 @@ class WaitCondition < Ruote::Participant
|
|
23
24
|
include Logging
|
24
25
|
@@handles = {}
|
25
26
|
@@conditions = []
|
27
|
+
|
26
28
|
def on_workitem
|
27
29
|
logger.debug "Entering #{workitem.participant_name} "
|
30
|
+
workitem[participant_name] = {}
|
28
31
|
@@conditions << self
|
29
|
-
|
32
|
+
stackname = workitem.fields['ResolvedNames']['AWS::StackName']
|
33
|
+
workitem[participant_name][:physical_id] = stackname + '-' + 'WaitCondition'
|
30
34
|
end
|
31
35
|
|
32
36
|
def self.create_handle(handle_name, handle)
|
@@ -34,7 +38,7 @@ class WaitCondition < Ruote::Participant
|
|
34
38
|
end
|
35
39
|
|
36
40
|
def set_handle(handle_name)
|
37
|
-
reply(
|
41
|
+
reply(workitem) if @@handles[handle_name]
|
38
42
|
end
|
39
43
|
|
40
44
|
def self.get_conditions()
|
@@ -56,7 +60,11 @@ class NoOpResource < Ruote::Participant
|
|
56
60
|
include Logging
|
57
61
|
|
58
62
|
def on_workitem
|
59
|
-
logger.debug "Entering #{workitem.
|
63
|
+
logger.debug "Entering #{participant_name} wfid=#{workitem.fei.wfid} fei=#{workitem.fei.to_h}"
|
64
|
+
workitem[participant_name] = {}
|
65
|
+
stackname = workitem.fields['ResolvedNames']['AWS::StackName']
|
66
|
+
logger.debug "physical id is #{stackname}-#{participant_name} "
|
67
|
+
workitem[participant_name][:physical_id] = stackname + '-' + participant_name
|
60
68
|
reply
|
61
69
|
end
|
62
70
|
end
|
data/lib/stackmate/stack.rb
CHANGED
@@ -9,28 +9,21 @@ class Stacker
|
|
9
9
|
include TSort
|
10
10
|
include Logging
|
11
11
|
|
12
|
-
|
12
|
+
attr_accessor :templ
|
13
|
+
|
14
|
+
def initialize(stackstr, stackname, params)
|
13
15
|
@stackname = stackname
|
14
|
-
@resolved =
|
15
|
-
stackstr = File.read(templatefile)
|
16
|
+
@resolved = params
|
16
17
|
@templ = JSON.parse(stackstr)
|
17
18
|
@templ['StackName'] = @stackname
|
18
19
|
@param_names = @templ['Parameters']
|
19
20
|
@deps = {}
|
20
21
|
@pdeps = {}
|
21
|
-
resolve_param_refs(params)
|
22
22
|
validate_param_values
|
23
23
|
resolve_dependencies()
|
24
24
|
@templ['ResolvedNames'] = @resolved
|
25
25
|
end
|
26
26
|
|
27
|
-
def resolve_param_refs(params)
|
28
|
-
params.split(';').each do |p|
|
29
|
-
i = p.split('=')
|
30
|
-
@resolved[i[0]] = i[1]
|
31
|
-
end
|
32
|
-
@resolved['AWS::Region'] = 'us-east-1' #TODO handle this better
|
33
|
-
end
|
34
27
|
|
35
28
|
def validate_param_values
|
36
29
|
#TODO CloudFormation parameters have validity constraints specified
|
@@ -72,7 +65,7 @@ class Stacker
|
|
72
65
|
#TODO Fn::GetAtt
|
73
66
|
if k == "Ref"
|
74
67
|
#only resolve dependencies on other resources for now
|
75
|
-
if !@param_names.keys.index(jsn[k]) && jsn[k] != 'AWS::Region' && jsn[k] != 'AWS::StackId'
|
68
|
+
if !@param_names.keys.index(jsn[k]) && jsn[k] != 'AWS::Region' && jsn[k] != 'AWS::StackId' && jsn[k] != 'AWS::StackName'
|
76
69
|
deps << jsn[k]
|
77
70
|
#print parent, ": ", deps.to_a, "\n"
|
78
71
|
else if @param_names.keys.index(jsn[k])
|
@@ -11,10 +11,24 @@ module StackMate
|
|
11
11
|
class StackExecutor < StackMate::Stacker
|
12
12
|
include Logging
|
13
13
|
|
14
|
-
def initialize(templatefile, stackname, params, engine, create_wait_conditions)
|
15
|
-
|
14
|
+
def initialize(templatefile, stackname, params, engine, create_wait_conditions, api_opts)
|
15
|
+
stackstr = File.read(templatefile)
|
16
|
+
super(stackstr, stackname, resolve_param_refs(params, stackname))
|
16
17
|
@engine = engine
|
17
18
|
@create_wait_conditions = create_wait_conditions
|
19
|
+
@api_opts = api_opts
|
20
|
+
end
|
21
|
+
|
22
|
+
def resolve_param_refs(params, stackname)
|
23
|
+
resolved_params = {}
|
24
|
+
params.split(';').each do |p|
|
25
|
+
i = p.split('=')
|
26
|
+
resolved_params[i[0]] = i[1]
|
27
|
+
end
|
28
|
+
resolved_params['AWS::Region'] = 'us-east-1' #TODO handle this better
|
29
|
+
resolved_params['AWS::StackName'] = stackname
|
30
|
+
resolved_params['AWS::StackId'] = stackname
|
31
|
+
resolved_params
|
18
32
|
end
|
19
33
|
|
20
34
|
def pdef
|
@@ -29,13 +43,13 @@ class StackExecutor < StackMate::Stacker
|
|
29
43
|
participants.each do |p|
|
30
44
|
t = @templ['Resources'][p]['Type']
|
31
45
|
throw :unknown, t if !StackMate.class_for(t)
|
32
|
-
@engine.register_participant p, StackMate.class_for(t)
|
46
|
+
@engine.register_participant p, StackMate.class_for(t), @api_opts
|
33
47
|
end
|
34
48
|
|
35
49
|
@engine.register_participant 'Output', 'StackMate::Output'
|
36
50
|
participants << 'Output'
|
37
51
|
@pdef = Ruote.define @stackname.to_s() do
|
38
|
-
cursor do
|
52
|
+
cursor :timeout => '300s' do
|
39
53
|
participants.collect{ |name| __send__(name) }
|
40
54
|
end
|
41
55
|
end
|
data/lib/stackmate/version.rb
CHANGED
data/stackmate.gemspec
CHANGED
@@ -11,9 +11,10 @@ Gem::Specification.new do |s|
|
|
11
11
|
s.authors = ["Chiradeep Vittal"]
|
12
12
|
s.email = 'chiradeepv@gmail.com'
|
13
13
|
s.files = Dir[
|
14
|
-
'lib/**/*.rb', 'test/**/*.rb',
|
14
|
+
'lib/**/*.rb', 'test/**/*.rb', 'bin/**',
|
15
15
|
'*.gemspec', '*.txt', '*.rdoc', '*.md'
|
16
16
|
]
|
17
|
+
s.executables << 'stackmate'
|
17
18
|
s.homepage =
|
18
19
|
'https://github.com/chiradeep/stackmate'
|
19
20
|
s.platform = Gem::Platform::RUBY
|
@@ -22,7 +23,7 @@ Gem::Specification.new do |s|
|
|
22
23
|
s.add_runtime_dependency 'cloudstack_ruby_client', '>= 0.0.4'
|
23
24
|
s.add_runtime_dependency 'json'
|
24
25
|
s.add_runtime_dependency 'ruote', '>= 2.3.0'
|
25
|
-
s.add_runtime_dependency 'sinatra', '
|
26
|
+
s.add_runtime_dependency 'sinatra', '~> 1.3.6'
|
26
27
|
s.add_runtime_dependency 'yajl-ruby', '= 1.1.0'
|
27
28
|
|
28
29
|
s.add_development_dependency 'json'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stackmate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-05-
|
12
|
+
date: 2013-05-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: cloudstack_ruby_client
|
@@ -64,17 +64,17 @@ dependencies:
|
|
64
64
|
requirement: !ruby/object:Gem::Requirement
|
65
65
|
none: false
|
66
66
|
requirements:
|
67
|
-
- -
|
67
|
+
- - ~>
|
68
68
|
- !ruby/object:Gem::Version
|
69
|
-
version: 1.
|
69
|
+
version: 1.3.6
|
70
70
|
type: :runtime
|
71
71
|
prerelease: false
|
72
72
|
version_requirements: !ruby/object:Gem::Requirement
|
73
73
|
none: false
|
74
74
|
requirements:
|
75
|
-
- -
|
75
|
+
- - ~>
|
76
76
|
- !ruby/object:Gem::Version
|
77
|
-
version: 1.
|
77
|
+
version: 1.3.6
|
78
78
|
- !ruby/object:Gem::Dependency
|
79
79
|
name: yajl-ruby
|
80
80
|
requirement: !ruby/object:Gem::Requirement
|
@@ -109,7 +109,8 @@ dependencies:
|
|
109
109
|
version: '0'
|
110
110
|
description: Parse and execute CloudFormation templates on CloudStack and other clouds
|
111
111
|
email: chiradeepv@gmail.com
|
112
|
-
executables:
|
112
|
+
executables:
|
113
|
+
- stackmate
|
113
114
|
extensions: []
|
114
115
|
extra_rdoc_files: []
|
115
116
|
files:
|
@@ -122,6 +123,8 @@ files:
|
|
122
123
|
- lib/stackmate/version.rb
|
123
124
|
- lib/stackmate/waitcondition_server.rb
|
124
125
|
- lib/stackmate.rb
|
126
|
+
- bin/stackmate
|
127
|
+
- bin/stackmate.rb
|
125
128
|
- stackmate.gemspec
|
126
129
|
- LICENSE.txt
|
127
130
|
- README.md
|