stacco 0.1.10 → 0.1.17

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/.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'