sumomo 0.8.22 → 0.10.0

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