stacco 0.1.10 → 0.1.17

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1 +1,4 @@
1
+ *.swp
1
2
  *.gem
3
+ .bundle/
4
+ sites/
data/Dockerfile ADDED
@@ -0,0 +1,10 @@
1
+ FROM tsutsu/ubuntu:latest
2
+
3
+ RUN /lib/container/do-with-clean <<EOF \
4
+ apt-get update \
5
+ apt-get install -fqy openssh-client ruby2.0 bundler \
6
+ gem install -y --no-rdoc --no-ri stacco \
7
+ EOF
8
+
9
+ WORKDIR /var/lib/stacco
10
+ ENTRYPOINT ["/usr/local/bin/stacco"]
data/README.md ADDED
@@ -0,0 +1,5 @@
1
+
2
+
3
+ $ aws cloudformation create-stack --stack-name bexbugbounty --template-body "$(./bake-template sites/bexbugbounty.com.yml)"
4
+
5
+ $ ./follow-stack bexbugbounty --profile='bbb'
data/bin/stacco CHANGED
@@ -1,7 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ $:.unshift File.expand_path("../../lib", __FILE__)
4
+
3
5
  require 'stacco'
4
6
  require 'pathname'
7
+ require 'highline'
8
+ require 'inifile'
5
9
 
6
10
  class String
7
11
  def colored(color)
@@ -55,26 +59,81 @@ unless ARGV.length >= 1
55
59
  Kernel.exit 1
56
60
  end
57
61
 
62
+ console = HighLine.new
63
+
58
64
  subcommand = ARGV.shift.intern
59
- orch_config_path = Pathname.new 'orchestrator.yml'
65
+
66
+ orch_config = {
67
+ local: Pathname.new('orchestrator.yml'),
68
+ user: Pathname.new(ENV['HOME']) + '.config' + 'stacco' + 'orchestrator.yml'
69
+ }
70
+ orch_config = orch_config[:local].file? ? orch_config[:local] : orch_config[:user]
71
+ orch_config.parent.mkpath
60
72
 
61
73
  if subcommand == :init
62
- orch_config_path.open('w') do |f|
63
- f.write $stdin.read
74
+ if $stdin.tty?
75
+ new_orch_config = {aws: {}}
76
+
77
+ aws_config = Pathname.new(ENV['HOME']) + '.aws' + 'config'
78
+ if aws_config.file?
79
+ aws_config = IniFile.new(filename: aws_config.to_s)
80
+ aws_profiles = aws_config.sections - ["preview"]
81
+
82
+ if aws_profiles.length > 1
83
+ aws_profile = console.choose(aws_profiles) do |menu|
84
+ menu.prompt = "Local AWS profile to source: "
85
+ end
86
+ else
87
+ console.say "Sourcing AWS config from local AWS profile."
88
+ aws_profile = aws_profiles.first
89
+ end
90
+
91
+ new_orch_config[:aws][:access_key_id] = aws_config[aws_profile]['aws_access_key_id']
92
+ new_orch_config[:aws][:secret_access_key] = aws_config[aws_profile]['aws_secret_access_key']
93
+ new_orch_config[:aws][:region] = aws_config[aws_profile]['region']
94
+ end
95
+
96
+ new_orch_config[:aws][:access_key_id] = console.ask("AWS account ID: ") do |question|
97
+ question.default = new_orch_config[:aws][:access_key_id]
98
+ question.validate = /^[0-9A-Z]{20}$/
99
+ end
100
+
101
+ new_orch_config[:aws][:secret_access_key] = console.ask("AWS account secret: ") do |question|
102
+ question.default = new_orch_config[:aws][:secret_access_key]
103
+ question.validate = /^[=+\/0-9A-Za-z]{32,64}$/
104
+ end
105
+
106
+ new_orch_config[:aws][:region] = console.choose(*(AWS.regions.map{ |r| r.name }.sort)) do |menu|
107
+ menu.prompt = "Orchestrator storage-bucket region: "
108
+ menu.default = new_orch_config[:aws][:region]
109
+ menu.layout = :one_line
110
+ menu.select_by = :name_or_index
111
+ end
112
+
113
+ new_orch_config[:storage_bucket] = console.ask("Orchestrator storage-bucket name: ") do |question|
114
+ question.validate = /^[A-Za-z][-.0-9A-Za-z]+[A-Za-z0-9]$/
115
+ end
116
+
117
+ # convert symbol-keys to string-keys
118
+ new_orch_config = JSON.parse(new_orch_config.to_json)
119
+ else
120
+ new_orch_config = YAML.load($stdin.read)
64
121
  end
65
122
 
66
- orch = Stacco::Orchestrator.from_config(orch_config_path)
123
+ orch_config.open('w'){ |f| f.write(new_orch_config.to_yaml) }
124
+
125
+ orch = Stacco::Orchestrator.from_config(orch_config)
67
126
  puts "orchestrator initialized"
68
127
  Kernel.exit 0
69
128
  end
70
129
 
71
- unless orch_config_path.file?
130
+ unless orch_config.file?
72
131
  $stderr.puts "orchestrator not initialized!"
73
132
  $stderr.puts "Did you run 'stacco init'?"
74
133
  Kernel.exit 1
75
134
  end
76
135
 
77
- orch = Stacco::Orchestrator.from_config(orch_config_path)
136
+ orch = Stacco::Orchestrator.from_config(orch_config)
78
137
  stacks = orch.stacks
79
138
 
80
139
  case subcommand
@@ -132,9 +191,29 @@ when :edit
132
191
  stack.config = YAML.load(tmp_stackdef_path.read)
133
192
 
134
193
 
135
- when :"bake-template"
194
+ when :"template:bake"
136
195
  puts stack.cloudformation_template
137
196
 
197
+ when :"template:validate"
198
+ success, err, pos = stack.validate
199
+ if success
200
+ puts "template is valid"
201
+ else
202
+ $stderr.puts err.colored(:red)
203
+ if pos
204
+ $stderr.puts
205
+ line, column = pos
206
+ line_before, line_after = line[0...column], line[column..-1]
207
+ if line_after
208
+ token_at, rest_of_line = line_after.split(' ', 2)
209
+ $stderr.puts line_before + (token_at || '').colored(:red) + (rest_of_line || '')
210
+ else
211
+ $stderr.puts line_before + ' #'.colored(:blue) + ' <-'.colored(:red)
212
+ end
213
+ $stderr.puts
214
+ end
215
+ end
216
+
138
217
  when :repl
139
218
  Stack = stack
140
219
  require 'irb'
@@ -146,9 +225,12 @@ when :"cloudfront:init"
146
225
  when :connect
147
226
  conns = stack.connections
148
227
 
228
+ bastion_host = conns.find{|k,v| k =~ /bastion/i and v.dns_name }[1]
229
+
149
230
  unless host_logicalname = ARGV.shift
150
- conns.each do |resource_name, eip|
151
- puts "#{resource_name}: #{eip.ip_address}"
231
+ conns.each do |resource_name, inst|
232
+ connection_path = inst.dns_name ? inst.dns_name : ("%s -> %s" % [bastion_host.dns_name, inst.private_dns_name])
233
+ puts "#{resource_name}: #{connection_path}"
152
234
  end
153
235
 
154
236
  Kernel.exit 0
@@ -158,18 +240,45 @@ when :connect
158
240
  $stderr.puts "#{host_logicalname}: not available"
159
241
  Kernel.exit 1
160
242
  end
161
- use_eip = conns[host_logicalname]
162
243
 
163
- private_key_path = Pathname.new("id_rsa")
244
+ connect_to_host = conns[host_logicalname]
245
+
246
+ private_key_path = Pathname.new("id_rsa").expand_path
164
247
  private_key_path.open('wb'){ |f| f.write(stack.iam_private_key_material) }
165
248
  private_key_path.chmod(0600)
166
249
 
167
- Kernel.exec 'ssh', '-t', '-v',
168
- '-i', private_key_path.to_s,
169
- '-o', 'UserKnownHostsFile=/dev/null',
170
- '-o', 'StrictHostKeyChecking=no',
171
- "ubuntu@#{use_eip.ip_address}",
172
- 'sudo', 'su', '-'
250
+
251
+ begin
252
+ Kernel.system 'ssh-add', private_key_path.to_s
253
+ if connect_to_host.dns_name
254
+ Kernel.system 'ssh', '-A', '-tt', '-v',
255
+ '-i', private_key_path.to_s,
256
+ '-o', 'StrictHostKeyChecking=no',
257
+ '-o', 'UserKnownHostsFile=/dev/null',
258
+ "ubuntu@#{connect_to_host.dns_name}",
259
+ 'sudo', 'su', '-'
260
+ else
261
+ Kernel.system 'ssh', '-A', '-tt', '-v',
262
+ '-o', 'StrictHostKeyChecking=no',
263
+ '-o', 'UserKnownHostsFile=/dev/null',
264
+ "ubuntu@#{bastion_host.dns_name}",
265
+ 'ssh', '-A', '-tt', '-v',
266
+ '-o', 'StrictHostKeyChecking=no',
267
+ '-o', 'UserKnownHostsFile=/dev/null',
268
+ "ubuntu@#{connect_to_host.private_dns_name}",
269
+ 'sudo', 'su', '-'
270
+ end
271
+ ensure
272
+ Kernel.system 'ssh-add', '-d', private_key_path.to_s
273
+ end
274
+
275
+ when :"status:wait"
276
+ desired_status = ARGV.shift
277
+ if desired_status == 'down'
278
+ Kernel.sleep(2) while stack.up?
279
+ else
280
+ Kernel.sleep(2) until stack.up? and stack.aws_status == desired_status
281
+ end
173
282
 
174
283
  when :status
175
284
  stack.must_be_up!
@@ -0,0 +1,149 @@
1
+ require 'aws'
2
+
3
+ class AWS::CloudFormation::StackEvent
4
+ def operation
5
+ self.resource_status.split('_', 2).first
6
+ end
7
+
8
+ def status
9
+ stat = self.resource_status.split('_', 2).last
10
+ return "started" if (self.resource_type == "AWS::CloudFormation::Stack" and stat == "IN_PROGRESS")
11
+ return "WORKING" if stat == "IN_PROGRESS"
12
+ stat
13
+ end
14
+
15
+ def error
16
+ self.resource_status_reason if (self.resource_status_reason and self.resource_status_reason !~ / Initiated$/)
17
+ end
18
+ end
19
+
20
+ class AWS::CloudFormation::Stack
21
+ attr_accessor :service_registry
22
+
23
+ def resources_of_type(type_name)
24
+ self.resources.find_all{ |r| r.resource_type == "AWS::#{type_name}" && r.resource_status =~ /_COMPLETE/ && r.resource_status != "DELETE_COMPLETE" }
25
+ end
26
+
27
+ def distributions
28
+ self.resources_of_type("CloudFront::Distribution").map{ |res| @service_registry[:cloudfront].distributions[res.physical_resource_id] }
29
+ end
30
+
31
+ def buckets
32
+ self.resources_of_type("S3::Bucket").map{ |res| @service_registry[:s3].buckets[res.physical_resource_id] }.find_all{ |r| r.exists? }
33
+ end
34
+
35
+ def instances
36
+ self.resources_of_type("EC2::Instance").map{ |res| @service_registry[:ec2].instances[res.physical_resource_id] }
37
+ end
38
+
39
+ def elastic_ips
40
+ self.resources_of_type("EC2::EIP").map{ |res| @service_registry[:ec2].elastic_ips[res.physical_resource_id] }
41
+ end
42
+
43
+ def server_certificates(opts = {})
44
+ certs = @service_registry[:iam].server_certificates.find_all{ |cert| cert.path == "/cloudfront/#{self.name}/" }
45
+
46
+ if cnames = opts.delete(:domain)
47
+ certs = Hash[ *(certs.map{ |cert| [cert.name, cert] }.flatten) ]
48
+
49
+ cnames = [cnames] unless cnames.kind_of?(Array)
50
+ cnames = cnames.flatten
51
+
52
+ possible_cert_names = cnames.map{ |cname| parts = cname.split('.').reverse; (1..parts.length).map{ |len| parts[0, len].reverse.join('.') }.reverse }.flatten
53
+
54
+ possible_cert_names.map{ |cname| certs[cname] }.compact
55
+ else
56
+ certs
57
+ end
58
+ end
59
+ end
60
+
61
+ class AWS::CloudFront
62
+ def distributions
63
+ DistributionCollection.new(self)
64
+ end
65
+
66
+ class DistributionCollection
67
+ def initialize(svc)
68
+ @svc = svc
69
+ end
70
+
71
+ def [](k)
72
+ Distribution.new(@svc, @svc.client.get_distribution(id: k))
73
+ end
74
+
75
+ def each
76
+ dists = @svc.client.list_distributions[:items]
77
+ dists.each do |dist|
78
+ yield Distribution.new(@svc, @svc.client.get_distribution(id: dist[:id]))
79
+ end
80
+ end
81
+
82
+ include Enumerable
83
+ end
84
+ end
85
+
86
+ class AWS::CloudFront::Distribution
87
+ def initialize(svc, data)
88
+ @svc = svc
89
+ @data = data
90
+ end
91
+
92
+ def aliases
93
+ @data[:distribution_config][:aliases][:items]
94
+ end
95
+
96
+ def id
97
+ @data[:id]
98
+ end
99
+
100
+ def config
101
+ self.make_exportable(@data[:distribution_config])
102
+ end
103
+
104
+ def make_exportable(o)
105
+ case o
106
+ when nil
107
+ ""
108
+ when Hash
109
+ h = {}
110
+ o.each{ |k, v| h[k] = make_exportable(v) }
111
+ h
112
+ when Array
113
+ o.map{ |e| make_exportable(e) }
114
+ else
115
+ o
116
+ end
117
+ end
118
+
119
+ def update
120
+ begin
121
+ yield
122
+
123
+ @svc.client.update_distribution(
124
+ id: self.id,
125
+ distribution_config: self.config,
126
+ if_match: @data[:etag]
127
+ )
128
+ end
129
+ end
130
+
131
+ def price_class
132
+ @data[:distribution_config][:price_class].split('_').last.downcase.intern
133
+ end
134
+
135
+ def price_class=(new_class)
136
+ @data[:distribution_config][:price_class] = "PriceClass_#{new_class.to_s.capitalize}"
137
+ end
138
+
139
+ def certificate
140
+ @data[:distribution_config][:viewer_certificate][:iam_certificate_id]
141
+ end
142
+
143
+ def certificate=(cert_id)
144
+ @data[:distribution_config][:viewer_certificate] = {
145
+ iam_certificate_id: cert_id,
146
+ ssl_support_method: "sni-only"
147
+ }
148
+ end
149
+ end
data/lib/stacco.rb CHANGED
@@ -1,418 +1,4 @@
1
- require 'set'
2
- require 'ostruct'
3
- require 'base64'
4
- require 'pathname'
5
- require 'shellwords'
6
-
7
- require 'json'
8
- require 'yaml'
9
- require 'erb'
10
- require 'inifile'
11
-
12
- require 'aws'
13
-
14
- class AWS::CloudFormation::StackEvent
15
- def operation
16
- self.resource_status.split('_', 2).first
17
- end
18
-
19
- def status
20
- stat = self.resource_status.split('_', 2).last
21
- return "started" if (self.resource_type == "AWS::CloudFormation::Stack" and stat == "IN_PROGRESS")
22
- return "WORKING" if stat == "IN_PROGRESS"
23
- stat
24
- end
25
-
26
- def error
27
- self.resource_status_reason if (self.resource_status_reason and self.resource_status_reason !~ / Initiated$/)
28
- end
29
- end
30
-
31
- class AWS::CloudFormation::Stack
32
- attr_accessor :service_registry
33
-
34
- def resources_of_type(type_name)
35
- self.resources.find_all{ |r| r.resource_type == "AWS::#{type_name}" && r.resource_status =~ /_COMPLETE/ && r.resource_status != "DELETE_COMPLETE" }
36
- end
37
-
38
- def distributions
39
- self.resources_of_type("CloudFront::Distribution").map{ |res| @service_registry[:cloudfront].distributions[res.physical_resource_id] }
40
- end
41
-
42
- def buckets
43
- self.resources_of_type("S3::Bucket").map{ |res| @service_registry[:s3].buckets[res.physical_resource_id] }.find_all{ |r| r.exists? }
44
- end
45
-
46
- def elastic_ips
47
- self.resources_of_type("EC2::EIP").map{ |res| @service_registry[:ec2].elastic_ips[res.physical_resource_id] }
48
- end
49
- end
50
-
51
- class AWS::CloudFront
52
- def distributions
53
- self.client.list_distributions[:items].map{ |dist| Distribution.new(self.client, self.client.get_distribution(id: dist[:id])) }
54
- end
55
- end
56
-
57
- class AWS::CloudFront::Distribution
58
- def initialize(client, data)
59
- @client = client
60
- @data = data
61
- end
62
-
63
- def aliases
64
- @data[:distribution_config][:aliases][:items]
65
- end
66
-
67
- def id
68
- @data[:id]
69
- end
70
-
71
- def config
72
- self.make_exportable(@data[:distribution_config])
73
- end
74
-
75
- def make_exportable(o)
76
- case o
77
- when nil
78
- ""
79
- when Hash
80
- h = {}
81
- o.each{ |k, v| h[k] = make_exportable(v) }
82
- h
83
- when Array
84
- o.map{ |e| make_exportable(e) }
85
- else
86
- o
87
- end
88
- end
89
-
90
- def update
91
- begin
92
- yield
93
-
94
- @client.update_distribution(
95
- id: self.id,
96
- distribution_config: self.config,
97
- if_match: @data[:etag]
98
- )
99
- end
100
- end
101
-
102
- def price_class
103
- @data[:distribution_config][:price_class].split('_').last.downcase.intern
104
- end
105
-
106
- def price_class=(new_class)
107
- @data[:distribution_config][:price_class] = "PriceClass_#{new_class.to_s.capitalize}"
108
- end
109
-
110
- def certificate
111
- @data[:distribution_config][:viewer_certificate][:iam_certificate_id]
112
- end
113
-
114
- def certificate=(cert_id)
115
- @data[:distribution_config][:viewer_certificate] = {
116
- iam_certificate_id: cert_id,
117
- ssl_support_method: "sni-only"
118
- }
119
- end
120
- end
121
-
122
- module Stacco
123
- module Resources
124
- RootDir = Pathname.new(File.expand_path("../../priv", __FILE__))
125
-
126
- templates_dir = Stacco::Resources::RootDir + "templates"
127
- Templates = {
128
- cloudformation: (templates_dir + "cloudformation.json.erb").read
129
- }
130
-
131
- module CloudInit
132
- cloud_init_scripts_dir = Stacco::Resources::RootDir + "cloud-init"
133
-
134
- CommonScripts = {
135
- before: (cloud_init_scripts_dir + "common.before.sh").readlines,
136
- after: (cloud_init_scripts_dir + "common.after.sh").readlines
137
- }
138
-
139
- RoleScripts = {}
140
- (cloud_init_scripts_dir + "roles").children.each{ |path| RoleScripts[path.basename('.sh').to_s.intern] = path.readlines }
141
- end
142
- end
143
- end
144
-
145
- class Stacco::Orchestrator
146
- def self.from_config(config_body)
147
- config_body = config_body.read if config_body.respond_to?(:read)
148
- self.new YAML.load(config_body)
149
- end
150
-
151
- def initialize(config)
152
- @config = config
153
-
154
- storage_bucket_name = @config['storage_bucket']
155
-
156
- s3 = AWS::S3.new(
157
- access_key_id: @config['aws']['access_key_id'],
158
- secret_access_key: @config['aws']['secret_access_key'],
159
- region: @config['aws']['region']
160
- )
161
-
162
- @bucket = s3.buckets[storage_bucket_name]
163
- s3.buckets.create(storage_bucket_name) unless @bucket.exists?
164
- end
165
-
166
- def stacks
167
- @bucket.objects.with_prefix('stack/').to_a[1..-1].map{ |obj| Stacco::Stack.new(obj) }
168
- end
169
-
170
- def define_stack(config_body)
171
- config_body = config_body.read if config_body.respond_to?(:read)
172
-
173
- config = YAML.load(config_body)
174
-
175
- stack_name = config['name']
176
-
177
- stack_object = @bucket.objects["stack/#{stack_name}"]
178
- stack_object.write(config.to_yaml)
179
-
180
- Stacco::Stack.new(stack_object)
181
- end
182
- end
183
-
184
- class Stacco::Stack
185
- def initialize(config_object)
186
- @config_object = config_object
187
-
188
- aws_creds = self.aws_credentials
189
-
190
- @services = {
191
- ec2: AWS::EC2.new(aws_creds),
192
- s3: AWS::S3.new(aws_creds),
193
- cloudformation: AWS::CloudFormation.new(aws_creds),
194
- cloudfront: AWS::CloudFront.new(aws_creds)
195
- }
196
-
197
- @aws_stack = @services[:cloudformation].stacks[self.name]
198
- @aws_stack.service_registry = @services
199
- end
200
-
201
- def connections
202
- connections = {}
203
- @aws_stack.elastic_ips.each{ |eip| connections[eip.instance.tags["aws:cloudformation:logical-id"]] = eip }
204
- connections
205
- end
206
-
207
- def resource_summaries
208
- @aws_stack.resource_summaries
209
- end
210
-
211
- def must_be_up!
212
- unless self.up?
213
- $stderr.puts "stack #{self.name} is down"
214
- Kernel.exit 1
215
- end
216
- end
217
-
218
- def aws_status
219
- @aws_stack.status
220
- end
221
-
222
- def status
223
- self.up? ? self.aws_status : "DOWN"
224
- end
225
-
226
- def config
227
- YAML.load(@config_object.read)
228
- end
229
-
230
- def config=(new_config)
231
- @config_object.write(new_config.to_yaml)
232
- end
233
-
234
- def update_config
235
- # TODO
236
- end
237
-
238
- def aws_credentials
239
- Hash[ *(self.config['aws'].map{ |k, v| [k.intern, v] }.flatten) ]
240
- end
241
-
242
- def description
243
- self.config['description']
244
- end
245
-
246
- def name
247
- self.config['name']
248
- end
249
-
250
- def name=(new_name)
251
- update_config{ |config| config.merge("name" => new_name) }
252
- end
253
-
254
- def up?
255
- @aws_stack.exists?
256
- end
257
-
258
- def up!
259
- if @aws_stack.exists?
260
- @aws_stack.update(template: self.cloudformation_template)
261
- else
262
- @services[:cloudformation].stacks.create(self.name, self.cloudformation_template)
263
- end
264
- end
265
-
266
- def up_since
267
- @aws_stack.creation_time if @aws_stack.exists?
268
- end
269
-
270
- def initialize_distributions!
271
- cloudfront_certs = self.config['cloudfront']['certificates']
272
- @services[:cloudfront].distributions.each do |dist|
273
- dist.update do
274
- dist.price_class = :"100"
275
- dist.certificate = cloudfront_certs[dist.aliases.first]
276
- end
277
- end
278
- end
279
-
280
- def down!
281
- return false unless self.up?
282
-
283
- @aws_stack.buckets.each{ |bucket| bucket.delete! }
284
- @aws_stack.delete
285
-
286
- Kernel.sleep(2) while self.up?
287
-
288
- true
289
- end
290
-
291
- def cloudformation_template
292
- tpl = Stacco::Template.new(self, Stacco::Resources::Templates[:cloudformation])
293
- tpl.result(self.config)
294
- end
295
-
296
- def iam_private_key
297
- @config_object.bucket.objects.with_prefix("sshkey/#{self.name}-").to_a.sort_by{ |obj| obj.key.split('/').last.split('-').last.to_i }.last
298
- end
299
-
300
- def iam_keypair_name
301
- "stacco-" + self.iam_private_key.key.split('/').last
302
- end
303
-
304
- def iam_private_key_material
305
- self.iam_private_key.read
306
- end
307
-
308
- def stream_events
309
- Enumerator.new do |out|
310
- known_events = Set.new
311
- ticks_without_add = 0
312
-
313
- while self.up?
314
- added = 0
315
-
316
- @aws_stack.events.sort_by{ |ev| ev.timestamp }.each do |event|
317
- next if known_events.include? event.event_id
318
- out.yield event
319
-
320
- known_events.add event.event_id
321
- added += 1
322
- ticks_without_add = 0
323
-
324
- end
325
-
326
- ticks_without_add += 1 if added == 0
327
-
328
- if ticks_without_add >= 8 and (Math.log2(ticks_without_add) % 1) == 0.0
329
- jobs = @aws_stack.resource_summaries
330
- active_jobs = jobs.find_all{ |job| job[:resource_status] =~ /IN_PROGRESS$/ }.map{ |job| job[:logical_resource_id] }.sort
331
- unless active_jobs.empty?
332
- out.yield OpenStruct.new(
333
- logical_resource_id: "Scheduler",
334
- status: "WAIT",
335
- operation: "WAIT",
336
- timestamp: Time.now,
337
- error: "waiting on #{active_jobs.join(', ')}"
338
- )
339
- end
340
- end
341
-
342
- Kernel.sleep 2
343
- end
344
- end
345
- end
346
- end
347
-
348
- class Stacco::Template
349
- class RenderContext < OpenStruct
350
- def initialize(stack, config, vars)
351
- @stack = stack
352
- @config = config
353
- super(vars)
354
- end
355
-
356
- def j(str)
357
- str.to_json
358
- end
359
-
360
- def ja(indent_n, lns)
361
- indent = " " * indent_n
362
- newline = "\n".to_json
363
- lns.map do |ln|
364
- if ln.chomp.empty?
365
- "%s \n" % [indent]
366
- else
367
- "%s%s, %s,\n" % [indent, ln.chomp.to_json, newline]
368
- end
369
- end.join[indent_n .. -3]
370
- end
371
- end
372
-
373
- def udscript(roles, vars)
374
- roles = roles.map{ |role| role.intern }
375
-
376
- unless roles.include? :backend
377
- vars = vars.dup
378
- vars.delete 'wallet_data'
379
- end
380
-
381
- lns = []
382
- lns.concat(vars.map do |k, v|
383
- var_val = v.include?("\0") ? Base64.encode64(v) : v
384
- "export #{k.to_s.upcase}=#{Shellwords.escape(var_val)}\n"
385
- end)
386
- lns.concat Stacco::Resources::CloudInit::CommonScripts[:before]
387
- roles.each{ |role| lns.concat Stacco::Resources::CloudInit::RoleScripts[role] }
388
- lns.concat Stacco::Resources::CloudInit::CommonScripts[:after]
389
- lns
390
- end
391
-
392
- def initialize(stack, template_body)
393
- @stack = stack
394
- template_body = template_body.read if template_body.respond_to?(:read)
395
- @template_body = template_body
396
- end
397
-
398
- def result(vars)
399
- cloudformation_vars = {}
400
- cloudformation_vars.update vars
401
- cloudformation_vars.update vars['cloudformation']
402
-
403
- cloudinit_vars = {}
404
- cloudinit_vars.update vars
405
- cloudinit_vars.update vars['cloud-init']
406
- cloudinit_vars.each do |k, v|
407
- cloudinit_vars.delete(k) if v.kind_of?(Hash) or v.kind_of?(Array)
408
- end
409
-
410
- cloudformation_vars['userdata'] = {}
411
- cloudformation_vars['roles'].each do |host_name, host_roles|
412
- cloudformation_vars['userdata'][host_name] = udscript(host_roles, cloudinit_vars)
413
- end
414
-
415
- b = RenderContext.new(@stack, vars, cloudformation_vars).instance_eval{ binding }
416
- ERB.new(@template_body).result(b)
417
- end
418
- end
1
+ require 'stacco/resources'
2
+ require 'stacco/stack'
3
+ require 'stacco/orchestrator'
4
+ require 'stacco/template'