stax 0.1.4 → 0.1.8

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: ff348fae4586ba8565b0716cafbd57a2804fa9ead68069195ec351815833201b
4
- data.tar.gz: dcae498f9406661f84112b0034c15a844a1945ef746e19188c05fce565c8e9ed
3
+ metadata.gz: a329f6a9e49e506570e4b869de676c6811be3cb0fc3725107ecd61dbd87574b0
4
+ data.tar.gz: a96ea2c978ceab09a1e9f4686be93d5b96929ef84ac7e2972d437c0283c811fb
5
5
  SHA512:
6
- metadata.gz: e6469ff904a334f9fd228f14c754c59f6b273d9bd0916ca367cabdad103f72d29993f45f6101a12215249197d32fc7dd6cfa1bd78cdbe45c17508b0baa24f223
7
- data.tar.gz: 75cae424254fc98fb6696051b2d08e3f1c6a045f54c64e257fb78a6cf65ad6f86f220ab5482568803e396568ae7a5d64b677577b69674ff99f9c455095ec0b38
6
+ metadata.gz: 71d82f945a2bcd9c9964550ad456714a1f655e6a1df5cbfe19e355b807125306f4e70689ac231344ce55b09dc04d3f248d1c0330939b7f4649817f0a14e879ad
7
+ data.tar.gz: 46a21736e16236d4f415830e4c07f931ff651a0dfc08e1c289e8d6419c5b0af465eef6df9b9d7a79052e4aae40acb447ef3230a2bab17c41bf655d8071ab3a08
data/lib/stax/aws/cfn.rb CHANGED
@@ -17,6 +17,7 @@ module Stax
17
17
  CREATE_COMPLETE: :green,
18
18
  DELETE_COMPLETE: :green,
19
19
  UPDATE_COMPLETE: :green,
20
+ IMPORT_COMPLETE: :green,
20
21
  CREATE_FAILED: :red,
21
22
  DELETE_FAILED: :red,
22
23
  UPDATE_FAILED: :red,
@@ -24,6 +25,7 @@ module Stax
24
25
  ROLLBACK_COMPLETE: :red,
25
26
  ## resource action
26
27
  Add: :green,
28
+ Import: :green,
27
29
  Modify: :yellow,
28
30
  Remove: :red,
29
31
  }
@@ -170,4 +172,4 @@ module Stax
170
172
 
171
173
  end
172
174
  end
173
- end
175
+ end
data/lib/stax/base.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  require 'thor'
2
2
  require 'stax/aws/sts'
3
3
 
4
+ ## clean exit on ctrl-c for all methods
5
+ trap('SIGINT', 'EXIT')
6
+
4
7
  module Stax
5
8
  class Base < Thor
6
9
 
@@ -68,7 +71,9 @@ module Stax
68
71
  end
69
72
  end
70
73
 
71
- def color(string, hash)
74
+ ## color a string according to current class COLORS hash
75
+ def color(string, hash = nil)
76
+ hash ||= self.class::COLORS
72
77
  set_color(string, hash.fetch(string.to_sym, :yellow))
73
78
  end
74
79
 
@@ -118,6 +123,7 @@ module Stax
118
123
 
119
124
  ## convert a diff in seconds to d h m s
120
125
  def human_time_diff(t, n = 5)
126
+ t = t.round # handle fractional seconds
121
127
  mm, ss = t.divmod(60)
122
128
  hh, mm = mm.divmod(60)
123
129
  dd, hh = hh.divmod(24)
@@ -132,6 +138,12 @@ module Stax
132
138
  end
133
139
  end
134
140
 
141
+ ## run desktop open command
142
+ def os_open(*args)
143
+ cmd = RUBY_PLATFORM.end_with?('linux') ? 'xdg-open' : 'open'
144
+ system(cmd, *args)
145
+ end
146
+
135
147
  end
136
148
  end
137
- end
149
+ end
data/lib/stax/cli/crud.rb CHANGED
@@ -56,10 +56,11 @@ module Stax
56
56
  end
57
57
 
58
58
  desc 'delete', 'meta delete task'
59
+ method_option :notail, aliases: '-n', type: :boolean, default: false, description: 'do not tail stack events'
59
60
  def delete
60
61
  stack_objects.reverse.each do |s|
61
62
  if s.exists?
62
- s.delete
63
+ s.invoke(:delete, [], options)
63
64
  else
64
65
  say("#{s.stack_name} does not exist", :green)
65
66
  end
@@ -67,4 +68,4 @@ module Stax
67
68
  end
68
69
 
69
70
  end
70
- end
71
+ end
@@ -14,6 +14,7 @@ module Stax
14
14
  def stack_project_names
15
15
  @_stack_project_names ||= stack_projects.map(&:physical_resource_id)
16
16
  end
17
+
17
18
  end
18
19
 
19
20
  module Cmd
@@ -34,6 +35,17 @@ module Stax
34
35
  status = p.phase_status || (p.phase_type == 'COMPLETED' ? '' : 'in progress')
35
36
  puts "%-16s %-12s %4s %s" % [p.phase_type, color(status, COLORS), duration, p.end_time]
36
37
  end
38
+
39
+ ## latest run id for a build project
40
+ def latest_run(name)
41
+ Aws::Codebuild.builds_for_project(name, 1).first
42
+ end
43
+
44
+ ## aws console link to latest project run
45
+ def latest_run_link(name)
46
+ id = latest_run(name)
47
+ "https://console.aws.amazon.com/codesuite/codebuild/#{aws_account_id}/projects/#{name}/build/#{id}/?region=#{aws_region}"
48
+ end
37
49
  end
38
50
 
39
51
  desc 'projects', 'list projects'
@@ -118,6 +130,20 @@ module Stax
118
130
  tail build.id
119
131
  end
120
132
 
133
+ desc 'link', 'link to latest run for builds'
134
+ def link
135
+ my.stack_project_names.map do |name|
136
+ puts latest_run_link(name)
137
+ end
138
+ end
139
+
140
+ desc 'open', 'open latest run in aws console'
141
+ def open
142
+ my.stack_project_names.map do |name|
143
+ os_open(latest_run_link(name))
144
+ end
145
+ end
146
+
121
147
  end
122
148
  end
123
- end
149
+ end
@@ -14,6 +14,12 @@ module Stax
14
14
  def stack_pipeline_names
15
15
  @_stack_pipeline_names ||= stack_pipelines.map(&:physical_resource_id)
16
16
  end
17
+
18
+ ## get status string for n-th pipeline
19
+ def stack_pipeline_status(n = 0)
20
+ name = stack_pipeline_names[n]
21
+ Aws::Codepipeline.executions(name, 1)&.first&.status
22
+ end
17
23
  end
18
24
 
19
25
  module Cmd
@@ -29,6 +35,12 @@ module Stax
29
35
  disabled: :red,
30
36
  }
31
37
 
38
+ no_commands do
39
+ def pipeline_link(name)
40
+ "https://console.aws.amazon.com/codesuite/codepipeline/pipelines/#{name}/view?region=#{aws_region}"
41
+ end
42
+ end
43
+
32
44
  desc 'stages', 'list pipeline stages'
33
45
  def stages
34
46
  my.stack_pipeline_names.each do |name|
@@ -54,6 +66,14 @@ module Stax
54
66
  end
55
67
  end
56
68
 
69
+ ## print status as one of InProgress, Stopped, Stopping, Succeeded, Superseded, Failed
70
+ desc 'status', 'pipeline execution status'
71
+ def status
72
+ s = my.stack_pipeline_status
73
+ puts(s)
74
+ exit(1) if s == 'Failed'
75
+ end
76
+
57
77
  desc 'state', 'pipeline state'
58
78
  def state
59
79
  my.stack_pipeline_names.each do |name|
@@ -187,6 +207,20 @@ module Stax
187
207
  end
188
208
  end
189
209
 
210
+ desc 'link', 'link to pipelines in aws console'
211
+ def link
212
+ my.stack_pipeline_names.each do |name|
213
+ puts pipeline_link(name)
214
+ end
215
+ end
216
+
217
+ desc 'open', 'open pipelines in aws console'
218
+ def open
219
+ my.stack_pipeline_names.each do |name|
220
+ os_open(pipeline_link(name))
221
+ end
222
+ end
223
+
190
224
  end
191
225
  end
192
226
  end
@@ -14,6 +14,7 @@ module Stax
14
14
 
15
15
  COLORS = {
16
16
  available: :green,
17
+ 'in-sync': :green,
17
18
  Complete: :green,
18
19
  Active: :green,
19
20
  }
@@ -40,13 +41,36 @@ module Stax
40
41
  def stack_db_subnet_groups
41
42
  Aws::Cfn.resources_by_type(my.stack_name, 'AWS::RDS::DBSubnetGroup')
42
43
  end
44
+
45
+ def print_rds_events(opt)
46
+ Aws::Rds.client.describe_events(opt).map(&:events).flatten.map do |e|
47
+ [ e.date, e.message ]
48
+ end.tap(&method(:print_table))
49
+ end
50
+
51
+ end
52
+
53
+ desc 'ls', 'list clusters with members'
54
+ def ls
55
+ debug("RDS databases for #{my.stack_name}")
56
+ stack_rds_clusters.map do |c|
57
+ cluster = [ c.db_cluster_identifier, 'cluster', color(c.status), c.engine ]
58
+ instances = c.db_cluster_members.map do |m|
59
+ role = m.is_cluster_writer ? 'writer' : 'reader'
60
+ i = Aws::Rds.instances(filters: [ { name: 'db-instance-id', values: [ m.db_instance_identifier ] } ]).first
61
+ [ '- ' + i.db_instance_identifier, role, color(i.db_instance_status), i.engine, i.availability_zone, i.db_instance_class ]
62
+ end
63
+ [ cluster ] + instances
64
+ end.flatten(1).tap do |list|
65
+ print_table list
66
+ end
43
67
  end
44
68
 
45
69
  desc 'clusters', 'list db clusters for stack'
46
70
  def clusters
47
71
  debug("RDS DB clusters for #{my.stack_name}")
48
72
  print_table stack_rds_clusters.map { |c|
49
- [c.db_cluster_identifier, c.engine, c.engine_version, color(c.status, COLORS), c.cluster_create_time]
73
+ [c.db_cluster_identifier, c.engine, c.engine_version, color(c.status), c.cluster_create_time]
50
74
  }
51
75
  end
52
76
 
@@ -65,7 +89,7 @@ module Stax
65
89
  def instances
66
90
  debug("RDS DB instances for #{my.stack_name}")
67
91
  print_table stack_rds_instances.map { |i|
68
- [i.db_instance_identifier, i.engine, i.engine_version, color(i.db_instance_status, COLORS), i.db_instance_class, i.db_subnet_group&.vpc_id, i.availability_zone]
92
+ [i.db_instance_identifier, i.engine, i.engine_version, color(i.db_instance_status), i.db_instance_class, i.db_subnet_group&.vpc_id, i.availability_zone]
69
93
  }
70
94
  end
71
95
 
@@ -92,11 +116,48 @@ module Stax
92
116
  end.flatten.each do |g|
93
117
  debug("Subnets for group #{g.db_subnet_group_name}")
94
118
  print_table g.subnets.map { |s|
95
- [s&.subnet_availability_zone&.name, s&.subnet_identifier, color(s&.subnet_status, COLORS)]
119
+ [s&.subnet_availability_zone&.name, s&.subnet_identifier, color(s&.subnet_status)]
96
120
  }
97
121
  end
98
122
  end
99
123
 
124
+ desc 'failover', 'failover clusters'
125
+ method_option :target, type: :string, default: nil, desc: 'id of instance to promote'
126
+ def failover
127
+ stack_rds_clusters.each do |c|
128
+ if yes?("Failover #{c.db_cluster_identifier}?", :yellow)
129
+ resp = Aws::Rds.client.failover_db_cluster(db_cluster_identifier: c.db_cluster_identifier, target_db_instance_identifier: options[:target])
130
+ puts "failing over #{resp.db_cluster.db_cluster_identifier}"
131
+ end
132
+ end
133
+ end
134
+
135
+ desc 'snapshots', 'list snapshots for stack clusters'
136
+ def snapshots
137
+ stack_db_clusters.map(&:physical_resource_id).each do |id|
138
+ debug("Snapshots for cluster #{id}")
139
+ Aws::Rds.client.describe_db_cluster_snapshots(db_cluster_identifier: id).map(&:db_cluster_snapshots).flatten.map do |s|
140
+ [ s.db_cluster_snapshot_identifier, s.snapshot_create_time, "#{s.allocated_storage}G", color(s.status), s.snapshot_type ]
141
+ end.tap do |list|
142
+ print_table list
143
+ end
144
+ end
145
+ end
146
+
147
+ desc 'events', 'list rds events for this stack'
148
+ option :duration, aliases: '-d', type: :numeric, default: 60*24, desc: 'duration in mins to show'
149
+ def events
150
+ stack_db_clusters.map(&:physical_resource_id).each do |id|
151
+ debug("Events for cluster #{id}")
152
+ print_rds_events(duration: options[:duration], source_type: 'db-cluster', source_identifier: id)
153
+ end
154
+
155
+ stack_db_instances.map(&:physical_resource_id).each do |id|
156
+ debug("Events for instance #{id}")
157
+ print_rds_events(duration: options[:duration], source_type: 'db-instance', source_identifier: id)
158
+ end
159
+ end
160
+
100
161
  end
101
162
  end
102
- end
163
+ end
@@ -30,6 +30,23 @@ module Stax
30
30
  fail_task(e.message)
31
31
  end
32
32
 
33
+ ## create change set to import a resource
34
+ def change_set_import(resource)
35
+ Aws::Cfn.changeset(
36
+ change_set_type: :IMPORT,
37
+ resources_to_import: [ resource ],
38
+ stack_name: stack_name,
39
+ template_body: cfn_template_body,
40
+ template_url: cfn_template_url,
41
+ parameters: cfn_parameters_update,
42
+ capabilities: cfn_capabilities,
43
+ notification_arns: cfn_notification_arns,
44
+ change_set_name: change_set_name,
45
+ ).id
46
+ rescue ::Aws::CloudFormation::Errors::ValidationError => e
47
+ fail_task(e.message)
48
+ end
49
+
33
50
  ## wait and return true if changeset ready for execute
34
51
  def change_set_complete?(id)
35
52
  begin
@@ -71,11 +88,15 @@ module Stax
71
88
  end
72
89
 
73
90
  def change_set_unlock
74
- Aws::Cfn.set_policy(stack_name: stack_name, stack_policy_body: stack_policy_during_update)
91
+ unless stack_policy_during_update.nil?
92
+ Aws::Cfn.set_policy(stack_name: stack_name, stack_policy_body: stack_policy_during_update)
93
+ end
75
94
  end
76
95
 
77
96
  def change_set_lock
78
- Aws::Cfn.set_policy(stack_name: stack_name, stack_policy_body: stack_policy)
97
+ unless stack_policy.nil?
98
+ Aws::Cfn.set_policy(stack_name: stack_name, stack_policy_body: stack_policy)
99
+ end
79
100
  end
80
101
  end
81
102
 
@@ -90,5 +111,32 @@ module Stax
90
111
  change_set_lock
91
112
  end
92
113
 
114
+ desc 'import', 'create and execute changeset to import a resource'
115
+ option :type, aliases: '-t', type: :string, default: nil, desc: 'cfn resource (e.g. AWS::DynamoDB::Table)'
116
+ option :id, aliases: '-i', type: :string, default: nil, desc: 'logical id (e.g. OrdersTable)'
117
+ option :key, aliases: '-k', type: :string, default: nil, desc: 'resource key (e.g. TableName)'
118
+ option :value, aliases: '-v', type: :string, default: nil, desc: 'resource value (e.g. orders)'
119
+ def import
120
+ ## prompt for missing options
121
+ %i[type id key value].each do |opt|
122
+ options[opt] ||= ask("Resource #{opt}?", :yellow)
123
+ end
124
+
125
+ ## create import changeset
126
+ debug("Creating import change set for #{stack_name}")
127
+ id = change_set_import(
128
+ resource_type: options[:type],
129
+ logical_resource_id: options[:id],
130
+ resource_identifier: {
131
+ options[:key] => options[:value]
132
+ }
133
+ )
134
+
135
+ ## wait for changeset, prompt for changes, and execute
136
+ change_set_complete?(id) || fail_task(change_set_reason(id))
137
+ change_set_changes(id)
138
+ change_set_execute(id) && tail && update_warn_imports
139
+ end
140
+
93
141
  end
94
- end
142
+ end
@@ -24,28 +24,31 @@ module Stax
24
24
  }
25
25
  end
26
26
 
27
- ## policy to lock the stack to all updates
27
+ ## policy to lock the stack to all updates, for example:
28
+ ## {
29
+ ## Statement: [
30
+ ## Effect: 'Deny',
31
+ ## Action: 'Update:*',
32
+ ## Principal: '*',
33
+ ## Resource: '*'
34
+ ## ]
35
+ ## }.to_json
28
36
  def stack_policy
29
- {
30
- Statement: [
31
- Effect: 'Deny',
32
- Action: 'Update:*',
33
- Principal: '*',
34
- Resource: '*'
35
- ]
36
- }.to_json
37
+ nil
37
38
  end
38
39
 
39
- ## temporary policy during updates; modify this to restrict resources
40
+ ## tmp policy during updates, in case a deny was set in stack_policy()
41
+ ## for example:
42
+ ## {
43
+ ## Statement: [
44
+ ## Effect: 'Allow',
45
+ ## Action: 'Update:*',
46
+ ## Principal: '*',
47
+ ## Resource: '*'
48
+ ## ]
49
+ ## }.to_json
40
50
  def stack_policy_during_update
41
- {
42
- Statement: [
43
- Effect: 'Allow',
44
- Action: 'Update:*',
45
- Principal: '*',
46
- Resource: '*'
47
- ]
48
- }.to_json
51
+ nil
49
52
  end
50
53
 
51
54
  ## cleanup sometimes needs to wait
@@ -206,11 +209,12 @@ module Stax
206
209
  end
207
210
 
208
211
  desc 'delete', 'delete stack'
212
+ method_option :notail, aliases: '-n', type: :boolean, default: false, description: 'do not tail stack events'
209
213
  def delete
210
214
  delete_warn_imports
211
215
  if yes? "Really delete stack #{stack_name}?", :yellow
212
216
  Aws::Cfn.delete(stack_name)
213
- tail
217
+ tail unless options[:notail]
214
218
  end
215
219
  rescue ::Aws::CloudFormation::Errors::ValidationError => e
216
220
  fail_task(e.message)
@@ -21,8 +21,8 @@ module Stax
21
21
  end
22
22
  end
23
23
 
24
- desc 'imports', 'list imports from this stack'
25
- def imports
24
+ desc 'exports', 'list exports from this stack, and stacks that import them'
25
+ def exports
26
26
  debug("Stacks that import from #{stack_name}")
27
27
  print_table Aws::Cfn.exports(stack_name).map { |e|
28
28
  imports = (i = Aws::Cfn.imports(e.export_name)).empty? ? '-' : i.join(' ')
@@ -30,5 +30,11 @@ module Stax
30
30
  }.sort
31
31
  end
32
32
 
33
+ desc 'imports', 'deprecated: use exports'
34
+ def imports
35
+ warn("deprecated method: please use 'exports' instead")
36
+ exports
37
+ end
38
+
33
39
  end
34
- end
40
+ end
data/lib/stax/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Stax
2
- VERSION = '0.1.4'
2
+ VERSION = '0.1.8'
3
3
  end
data/lib/stax.rb CHANGED
@@ -16,7 +16,7 @@ require 'stax/stack/crud'
16
16
  require 'stax/stack/changeset'
17
17
  require 'stax/stack/parameters'
18
18
  require 'stax/stack/outputs'
19
- require 'stax/stack/imports'
19
+ require 'stax/stack/exports'
20
20
  require 'stax/stack/resources'
21
21
  require 'stax/stack/drift'
22
22
 
data/stax.gemspec CHANGED
@@ -23,7 +23,6 @@ Gem::Specification.new do |spec|
23
23
 
24
24
  spec.add_development_dependency "bundler"
25
25
  spec.add_development_dependency "rake", "~> 10.0"
26
- spec.add_development_dependency "docile", "1.2.0"
27
26
  spec.add_development_dependency "stax-examples"
28
27
 
29
28
  spec.add_dependency('aws-sdk-cloudformation')
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stax
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Lister
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-21 00:00:00.000000000 Z
11
+ date: 2021-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,20 +38,6 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10.0'
41
- - !ruby/object:Gem::Dependency
42
- name: docile
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - '='
46
- - !ruby/object:Gem::Version
47
- version: 1.2.0
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - '='
53
- - !ruby/object:Gem::Version
54
- version: 1.2.0
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: stax-examples
57
43
  requirement: !ruby/object:Gem::Requirement
@@ -596,7 +582,7 @@ files:
596
582
  - lib/stax/stack/changeset.rb
597
583
  - lib/stax/stack/crud.rb
598
584
  - lib/stax/stack/drift.rb
599
- - lib/stax/stack/imports.rb
585
+ - lib/stax/stack/exports.rb
600
586
  - lib/stax/stack/outputs.rb
601
587
  - lib/stax/stack/parameters.rb
602
588
  - lib/stax/stack/resources.rb
@@ -624,7 +610,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
624
610
  - !ruby/object:Gem::Version
625
611
  version: '0'
626
612
  requirements: []
627
- rubygems_version: 3.0.3
613
+ rubygems_version: 3.1.4
628
614
  signing_key:
629
615
  specification_version: 4
630
616
  summary: Control Cloudformation stack and other stuff.