sumomo 0.8.22 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ca6682714d521c07d70bb0dfd270897a53389ecd2b53f7581a260841e0c170c
4
- data.tar.gz: b43744c1f57eb638db6d7d3199e7542279f532e08603a9c6b753d266d6c76793
3
+ metadata.gz: 346a647def575daedb5dc3e3eba04a73024c441be3ae92740cf9edb668992e44
4
+ data.tar.gz: 379cbf589f7ace6b8da717054ab71754604ed6b863808aadf3b6996a18df292f
5
5
  SHA512:
6
- metadata.gz: 7b3b1e2abb745a50ba2f245a2dda925d15ac15842abe8e57f1343edffd1e8bc7d59bb9403741d201e794640addab6bf6edc7b7c83d44b625041589134d4ff5f1
7
- data.tar.gz: f12ad5cafa9f2963fcd0f9718b47b0be4ba522952a2c960511cde6ba57bc5ebcb1039136545b395901e3066e8a1e2b3333b74d5a8c61b97ab8fd2bc5fa9d3885
6
+ metadata.gz: a0cd59b9b12bf2ec4e1b968f37962c505e9c9e6b7e47ed8846ad7af3737c162f5947bd4e01bfa1cdddfd4d347c378803db10c25ceadf56c5d7d31e0cecbe6b26
7
+ data.tar.gz: ed8757d5ca20a22791c1ded35a453ffd00f51260e6f5a96fb38adc9cc55df4617d30dbfa4a83bad353d2758bdc1232f51183e1bdbf09591c6142f6e92d98ff84
data/README.md CHANGED
@@ -40,19 +40,28 @@ output "Haha", x
40
40
 
41
41
  To create a stack that acquires an IP Address and outputs it
42
42
 
43
- $ sumomo create mystack
43
+ $ sumomo create mystack
44
44
 
45
45
  To view a stack's outputs
46
46
 
47
- $ sumomo outputs mystack
47
+ $ sumomo outputs mystack
48
48
 
49
49
  To update a stack
50
50
 
51
- $ sumomo update mystack
51
+ $ sumomo update mystack
52
52
 
53
53
  To delete a stack
54
54
 
55
- $ sumomo delete mystack
55
+ $ sumomo delete mystack
56
+
57
+ To view what changes your stuff will cause to your stack
58
+
59
+ $ sumomo diff mystack
60
+
61
+ To see the raw cloudformation template
62
+
63
+ $ sumomo show mystack
64
+
56
65
 
57
66
  ## Library Usage
58
67
 
@@ -2,6 +2,7 @@ var s3 = new aws.S3();
2
2
  var name = request.ResourceProperties.BucketName;
3
3
  var copy_from_dir = request.ResourceProperties.CopyFromDirectory;
4
4
  var copy_from_bucket = request.ResourceProperties.CopyFromBucket;
5
+ var disable_acl = request.ResourceProperties.DisableACL;
5
6
  var success_obj = {
6
7
  Arn: "arn:aws:s3:::" + name,
7
8
  DomainName: name + ".s3-" + request.ResourceProperties.Region + ".amazonaws.com"
@@ -72,6 +73,38 @@ function copy_files(success, fail)
72
73
  );
73
74
  }
74
75
 
76
+ function apply_ownership_policy(name, success, fail)
77
+ {
78
+ if (disable_acl)
79
+ {
80
+ // ACL is disabled by default, so we simply skip this step.
81
+ return copy_files(success, fail);
82
+ }
83
+
84
+ var params = {
85
+ Bucket: name,
86
+ OwnershipControls: {
87
+ Rules: [
88
+ {
89
+ ObjectOwnership: "BucketOwnerPreferred"
90
+ }
91
+ ]
92
+ }
93
+ };
94
+
95
+ s3.putBucketOwnershipControls(params, function(err, data)
96
+ {
97
+ if (err)
98
+ {
99
+ fail(err);
100
+ }
101
+ else
102
+ {
103
+ copy_files(success, fail);
104
+ }
105
+ });
106
+ }
107
+
75
108
  function create_bucket(name, success, fail)
76
109
  {
77
110
  var create_params = {
@@ -89,7 +122,7 @@ function create_bucket(name, success, fail)
89
122
  }
90
123
  else
91
124
  {
92
- copy_files(success, fail);
125
+ apply_ownership_policy(name, success, fail);
93
126
  }
94
127
  });
95
128
  }
data/exe/sumomo CHANGED
@@ -5,12 +5,12 @@ require 'trollop'
5
5
  require 'sumomo'
6
6
  require 'yaml'
7
7
 
8
- SUB_COMMANDS = %w[delete create update outputs testapi].freeze
8
+ SUB_COMMANDS = %w[delete create update outputs show diff testapi].freeze
9
9
  global_opts = Trollop.options do
10
10
  banner <<-USAGE
11
11
  Sumomo v#{Sumomo::VERSION}
12
12
 
13
- Usage: sumomo [options] <create|update|delete|outputs> <stackname>
13
+ Usage: sumomo [options] <#{SUB_COMMANDS.join('|')}> <stackname>
14
14
  USAGE
15
15
 
16
16
  opt :region, 'AWS region to use', type: :string, default: 'ap-northeast-1'
@@ -30,21 +30,12 @@ cmd_opts = case cmd
30
30
  when 'delete'
31
31
  Sumomo.delete_stack(name: ARGV[0], region: global_opts[:region])
32
32
 
33
- when 'create'
34
- local_opts = Trollop.options do
35
- opt :filename, 'File that describes the stack', type: :string, default: 'Sumomofile'
36
- end
37
- Sumomo.create_stack(name: ARGV[0], region: global_opts[:region]) do
38
- proc = proc {}
39
- eval File.read(local_opts[:filename]), proc.binding, local_opts[:filename]
40
- end
41
-
42
- when 'update'
33
+ when 'create', 'update', 'show', 'diff'
43
34
  local_opts = Trollop.options do
44
35
  opt :filename, 'File that describes the stack', type: :string, default: 'Sumomofile'
45
36
  opt :changeset, 'Create a changeset instead of directly update', type: :boolean, default: false
46
37
  end
47
- Sumomo.update_stack(name: ARGV[0], changeset: !!local_opts[:changeset], region: global_opts[:region]) do
38
+ Sumomo.manage_stack(name: ARGV[0], cmd: cmd, changeset: !!local_opts[:changeset], region: global_opts[:region]) do
48
39
  proc = proc {}
49
40
  eval File.read(local_opts[:filename]), proc.binding, local_opts[:filename]
50
41
  end
@@ -80,4 +71,6 @@ cmd_opts = case cmd
80
71
  Trollop.die "Unknown subcommand #{cmd.inspect}"
81
72
  end
82
73
 
83
- Sumomo.wait_for_stack(name: ARGV[0], region: global_opts[:region])
74
+ unless %w[show diff].include?(cmd)
75
+ Sumomo.wait_for_stack(name: ARGV[0], region: global_opts[:region])
76
+ end
data/lib/sumomo/ec2.rb CHANGED
@@ -277,7 +277,7 @@ module Sumomo
277
277
  @ami_lookup_resources ||= {}
278
278
 
279
279
  unless @ami_lookup_resources[type]
280
- @ami_lookup_resources[type] = make 'Custom::AMILookup' do
280
+ @ami_lookup_resources[type] = make 'Custom::AMILookup', name: "#{name}AmiLookup" do
281
281
  InstanceType type
282
282
  end
283
283
  end
@@ -335,14 +335,14 @@ module Sumomo
335
335
  end
336
336
  raise 'ec2: egress option needs to be an array' unless egress.is_a? Array
337
337
 
338
- web_sec_group = make 'AWS::EC2::SecurityGroup' do
338
+ web_sec_group = make 'AWS::EC2::SecurityGroup', name: "#{name}SecurityGroup" do
339
339
  GroupDescription "Security group for layer: #{layer}"
340
340
  SecurityGroupIngress ingress
341
341
  SecurityGroupEgress egress
342
342
  VpcId network.vpc
343
343
  end
344
344
 
345
- wait_handle = make 'AWS::CloudFormation::WaitConditionHandle'
345
+ wait_handle = make 'AWS::CloudFormation::WaitConditionHandle', name: "#{name}WaitConditionHandle"
346
346
 
347
347
  user_data = initscript(wait_handle, name, call('Fn::Join', "\n", script_arr))
348
348
 
@@ -355,7 +355,7 @@ module Sumomo
355
355
  }]
356
356
  }
357
357
 
358
- asg_role = make 'AWS::IAM::Role' do
358
+ asg_role = make 'AWS::IAM::Role', name: "#{name}Role" do
359
359
  AssumeRolePolicyDocument role_policy_doc
360
360
  Path '/'
361
361
  Policies [{
@@ -404,12 +404,12 @@ module Sumomo
404
404
  }]
405
405
  end
406
406
 
407
- asg_profile = make 'AWS::IAM::InstanceProfile' do
407
+ asg_profile = make 'AWS::IAM::InstanceProfile', name: "#{name}InstanceProfile" do
408
408
  Path '/'
409
409
  Roles [asg_role]
410
410
  end
411
411
 
412
- launch_config = make 'AWS::AutoScaling::LaunchConfiguration' do
412
+ launch_config = make 'AWS::AutoScaling::LaunchConfiguration', name: "#{name}LaunchConfiguration" do
413
413
  AssociatePublicIpAddress has_public_ips
414
414
  KeyName keypair
415
415
  SecurityGroups [web_sec_group] + security_groups
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sumomo
4
- VERSION = '0.8.22'
4
+ VERSION = '0.10.0'
5
5
  end
data/lib/sumomo.rb CHANGED
@@ -26,17 +26,7 @@ module Sumomo
26
26
  "cloudformation/#{make_master_key_name(name: name)}.pem"
27
27
  end
28
28
 
29
- def self.create_stack(name:, region:, sns_arn: nil, &block)
30
- cf = Aws::CloudFormation::Client.new(region: region)
31
- begin
32
- cf.describe_stacks(stack_name: name)
33
- raise "There is already a stack named '#{name}'"
34
- rescue Aws::CloudFormation::Errors::ValidationError
35
- update_stack(name: name, region: region, sns_arn: sns_arn, &block)
36
- end
37
- end
38
-
39
- def self.update_stack(name:, region:, sns_arn: nil, changeset: false, &block)
29
+ def self.manage_stack(name:, region:, cmd:'update', sns_arn: nil, changeset: false, &block)
40
30
  cf = Aws::CloudFormation::Client.new(region: region)
41
31
  s3 = Aws::S3::Client.new(region: region)
42
32
  ec2 = Aws::EC2::Client.new(region: region)
@@ -107,9 +97,66 @@ module Sumomo
107
97
  hidden_values = @hidden_values
108
98
  end.templatize
109
99
 
110
- # TODO: if the template is too big, split it into nested templates
100
+ templatedata = JSON.parse(template)
101
+
102
+ if cmd == 'show'
103
+ puts templatedata.to_yaml
104
+ return
105
+ end
106
+
107
+ if cmd == 'diff'
108
+ store.set_raw('cloudformation/temporary_template', template)
109
+ update_options = {
110
+ stack_name: name,
111
+ template_url: store.url('cloudformation/temporary_template'),
112
+ parameters: hidden_values,
113
+ capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM']
114
+ }
115
+
116
+ change_set_name = "CanaryChange#{curtimestr}"
117
+
118
+ puts "Create change set"
119
+
120
+ cf.create_change_set(
121
+ **update_options,
122
+ change_set_name: change_set_name
123
+ )
124
+
125
+ resp = nil
126
+
127
+ puts "Wait for change set"
128
+
129
+ loop do
130
+ sleep 5
131
+ resp = cf.describe_change_set({
132
+ change_set_name: change_set_name,
133
+ stack_name: name
134
+ })
135
+ puts "#{change_set_name} #{resp.status}"
136
+
137
+ break unless resp.status.end_with? 'IN_PROGRESS'
138
+ end
139
+
140
+ puts "Cleaning up..."
141
+
142
+ cf.delete_change_set({
143
+ change_set_name: change_set_name,
144
+ stack_name: name,
145
+ })
146
+
147
+ puts 'Change list'
148
+
149
+ clist = process_changeset_response(resp)
150
+ drifts = get_drifts(templatedata, cf, name)
111
151
 
112
- # puts JSON.parse(template).to_yaml
152
+ # puts clist.to_yaml
153
+ # puts drifts.to_yaml
154
+ puts humanize_changeset_response(clist, drifts, templatedata).to_yaml
155
+
156
+ return
157
+ end
158
+
159
+ # TODO: if the template is too big, split it into nested templates
113
160
 
114
161
  store.set_raw('cloudformation/template', template)
115
162
 
@@ -144,6 +191,137 @@ module Sumomo
144
191
  end
145
192
  end
146
193
 
194
+ def self.get_drifts(templatedata, cf, stack_name)
195
+ drifts = {}
196
+
197
+ templatedata['Resources'].each do |k,info|
198
+ puts "detect drift for #{k}"
199
+
200
+ rd = cf.detect_stack_resource_drift({
201
+ stack_name: stack_name, # required
202
+ logical_resource_id: k, # required
203
+ })
204
+
205
+ diffinfo = {}
206
+ differences = []
207
+
208
+ rd.stack_resource_drift.property_differences.each do |pd|
209
+ res = {}
210
+
211
+ %w[property_path expected_value actual_value difference_type].each do |att|
212
+ res[att] = pd.send(att.to_sym)
213
+ end
214
+
215
+ differences << res
216
+ end
217
+
218
+ %w[resource_type expected_properties actual_properties stack_resource_drift_status].each do |key|
219
+ diffinfo[key] = rd.stack_resource_drift.send(key.to_sym)
220
+ end
221
+
222
+ diffinfo['differences'] = differences
223
+
224
+ drifts[k] = diffinfo
225
+
226
+ rescue Aws::CloudFormation::Errors::ValidationError => e
227
+ drifts[k] = {
228
+ "exception" => e.message
229
+ }
230
+
231
+ end
232
+
233
+ drifts
234
+ end
235
+
236
+ def self.process_changeset_response(resp)
237
+ result = {}
238
+
239
+ resp.changes.each do |change|
240
+ info = {}
241
+
242
+ %w[action physical_resource_id resource_type replacement].each do |k|
243
+ info[k] = change.resource_change.send(k.to_sym)
244
+ end
245
+
246
+ details = []
247
+
248
+ change.resource_change.details.each do |detail|
249
+
250
+ detailinfo = {}
251
+
252
+ %w[attribute name requires_recreation].each do |k|
253
+ detailinfo[k] = detail.target.send(k.to_sym)
254
+ end
255
+
256
+ %w[evaluation change_source causing_entity].each do |k|
257
+ detailinfo[k] = detail.send(k.to_sym)
258
+ end
259
+
260
+ details << detailinfo
261
+ end
262
+
263
+ info['details'] = details
264
+
265
+ result[change.resource_change.logical_resource_id] = info
266
+ end
267
+
268
+ result
269
+ end
270
+
271
+ def self.humanize_changeset_response(clist, drifts, templatedata)
272
+ # refs
273
+ # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/CloudFormation/Types/ResourceChange.html
274
+ # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/CloudFormation/Types/ResourceTargetDefinition.html
275
+
276
+ result = {}
277
+
278
+ clist.each do |k,info|
279
+
280
+ if info['action'] == 'Modify'
281
+
282
+ z = if info['replacement'] != 'False'
283
+ result['Replace'] ||= {}
284
+ else
285
+ result['Modify'] ||= {}
286
+ end
287
+
288
+ z[k] = {}
289
+
290
+ info['details'].each do |detail|
291
+
292
+ propname = detail['attribute']
293
+ if detail['attribute'] == 'Properties'
294
+ propname = detail['name']
295
+ end
296
+
297
+ fr = templatedata['Resources'][k]
298
+ tr = drifts[k]
299
+
300
+ fromprops = fr['Properties']
301
+ toprops = {}
302
+
303
+ if tr['actual_properties']
304
+ toprops = JSON.parse(tr['actual_properties'])
305
+ end
306
+
307
+
308
+ z[k][propname] = {
309
+ 'From' => toprops[propname],
310
+ 'To' => fromprops[propname]
311
+ }
312
+ end
313
+ else
314
+ z = result[info['action']] ||= {}
315
+
316
+ z[k] = {}
317
+ end
318
+
319
+ end
320
+
321
+ result
322
+
323
+ end
324
+
147
325
  def self.curtimestr
148
326
  Time.now.strftime('%Y%m%d%H%M%S')
149
327
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sumomo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.22
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Siaw
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-12-20 00:00:00.000000000 Z
11
+ date: 2023-06-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler