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 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.yaml
68
- service_offerings : {'m1.small' : '13954c5a-60f5-4ec8-9858-f45b12f4b846'}
69
- templates : {'ami-1b814f72': '7fc2c704-a950-11e2-8b38-0b06fbda5106'}
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
@@ -0,0 +1,2 @@
1
+ #!/bin/bash
2
+ bundle exec ruby -Ilib $(dirname $0)/stackmate.rb $*
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
- @url = ENV['URL']
21
- @apikey = ENV['APIKEY']
22
- @seckey = ENV['SECKEY']
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
- myname = workitem.participant_name
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.yaml')
124
+ @localized = YAML.load_file('local.yml')
122
125
  rescue
123
- print "Warning: Failed to load localized mappings from local.yaml\n"
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
- myname = workitem.participant_name
11
- logger.debug "Entering #{workitem.participant_name} "
12
- presigned_url = 'http://localhost:4567/waitcondition/' + workitem.fei.wfid + '/' + myname
13
- workitem.fields['ResolvedNames'][myname] = presigned_url
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(myname, presigned_url)
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
- @wi = workitem
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(@wi) if @@handles[handle_name]
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.participant_name} "
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
@@ -9,28 +9,21 @@ class Stacker
9
9
  include TSort
10
10
  include Logging
11
11
 
12
- def initialize(templatefile, stackname, params)
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
- super(templatefile, stackname, params)
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
@@ -1,3 +1,3 @@
1
1
  module StackMate
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
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', '>= 1.4.2'
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.1
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-16 00:00:00.000000000 Z
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.4.2
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.4.2
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