stax 0.0.8 → 0.0.9

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: ee309ab8e6e330d1d4a048112e92ef4baa447a476d424aa603ef6ab8ba5d8373
4
- data.tar.gz: b961c4bd5be7fc88e23b07a36fd69ab9f3d308c490e32ddac51d4ff1aace36f0
3
+ metadata.gz: 1637c27b601383577e3b2f002e93de68a1d030d13a083fafaf0ae29d325d4cc3
4
+ data.tar.gz: 9d3d833071c1ef2c0ce92491e64aecbc39858dd44ffe225dc61d0e0adf487219
5
5
  SHA512:
6
- metadata.gz: e6190e8b3ebbac9249c4e4a506f940250dac24c9601453d7d956ef177ac71c62c885a7f784f971bdf611378719bd98c0b074f2470172c77963e9f7227941a2f0
7
- data.tar.gz: 1cf0747c5ed623d09612a3e19a55e6324afd7dc7bce69eb7f976464ec027cfa80d029baaa330dad80abe0d1ff8c2f4b900efed9b1b7034fe7b1ef6c9eb1206f0
6
+ metadata.gz: e560c98cafefb0228b31e0613372f722ed2b129e804a1010eaa6557e35de3ed070f93eba1e3e15b8fe8b76d78fbf5e2d9ca6b1492925caed3721198030af3c5a
7
+ data.tar.gz: c173d44f14e732458c6f677a24972b0eb88c8d8dc2f6189a397b598c8c46c3d09c0cd9bb68627bbab25c4f9888ef9c3587961a3c6cb1ee9bc9563c4d56a508d5
data/README.md CHANGED
@@ -176,7 +176,7 @@ Commands:
176
176
  ## Cloudformation templates
177
177
 
178
178
  Stax will load template files from the path relative to its `Staxfile`
179
- as `cf/$stack.rb`, e.g. `cf/vpc.rb`. Modify this using the method `Stax::Stack::cfer_template`.
179
+ as `cf/$stack.rb`, e.g. `cf/vpc.rb`. Modify this using the method `Stax::Stack::cfn_template_dir`.
180
180
  See `examples` for a typical setup.
181
181
 
182
182
  Simply control stacks using the relevant subcommands:
@@ -38,4 +38,5 @@ require 'stax/mixin/logs'
38
38
  require 'stax/mixin/apigw'
39
39
  require 'stax/mixin/firehose'
40
40
  require 'stax/mixin/codebuild'
41
- require 'stax/mixin/codepipeline'
41
+ require 'stax/mixin/codepipeline'
42
+ require 'stax/mixin/rds'
@@ -99,11 +99,13 @@ module Stax
99
99
 
100
100
  ## list of stacks that import from this one
101
101
  def imports(name)
102
- paginate(:imports) do |next_token|
103
- client.list_imports(export_name: name, next_token: next_token)
104
- end
102
+ client.list_imports(export_name: name).map(&:imports)
105
103
  rescue ::Aws::CloudFormation::Errors::ValidationError
106
104
  []
105
+ rescue ::Aws::CloudFormation::Errors::Throttling => e # this call rate-limits aggressively
106
+ warn(e.message)
107
+ sleep 1
108
+ retry
107
109
  end
108
110
 
109
111
  def validate(opt)
@@ -0,0 +1,39 @@
1
+ module Stax
2
+ module Aws
3
+ class Dms < Sdk
4
+
5
+ class << self
6
+
7
+ def client
8
+ @_client ||= ::Aws::DatabaseMigrationService::Client.new
9
+ end
10
+
11
+ def endpoints(opt)
12
+ client.describe_endpoints(opt).map(&:endpoints).flatten
13
+ end
14
+
15
+ def instances(opt)
16
+ client.describe_replication_instances(opt).map(&:replication_instances).flatten
17
+ end
18
+
19
+ def tasks(opt)
20
+ client.describe_replication_tasks(opt).map(&:replication_tasks).flatten
21
+ end
22
+
23
+ def test(opt)
24
+ client.test_connection(opt).connection
25
+ end
26
+
27
+ def connections(opt)
28
+ client.describe_connections(opt).map(&:connections).flatten
29
+ end
30
+
31
+ def start(opt)
32
+ client.start_replication_task(opt).replication_task
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,27 @@
1
+ module Stax
2
+ module Aws
3
+ class Rds < Sdk
4
+
5
+ class << self
6
+
7
+ def client
8
+ @_client ||= ::Aws::RDS::Client.new
9
+ end
10
+
11
+ def clusters(opt)
12
+ client.describe_db_clusters(opt)&.db_clusters
13
+ end
14
+
15
+ def instances(opt)
16
+ client.describe_db_instances(opt).map(&:db_instances).flatten
17
+ end
18
+
19
+ def subnet_groups(opt)
20
+ client.describe_db_subnet_groups(opt).map(&:db_subnet_groups).flatten
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -14,6 +14,10 @@ module Stax
14
14
 
15
15
  def bucket_tags(bucket)
16
16
  client.get_bucket_tagging(bucket: bucket).tag_set
17
+ rescue ::Aws::Errors::NoSuchEndpointError
18
+ warn("socket error for #{bucket}, retrying")
19
+ sleep 1
20
+ retry
17
21
  rescue ::Aws::S3::Errors::NoSuchTagSet
18
22
  []
19
23
  end
@@ -0,0 +1,22 @@
1
+ module Stax
2
+ module Aws
3
+ class SecretsManager < Sdk
4
+
5
+ class << self
6
+
7
+ def client
8
+ @_client ||= ::Aws::SecretsManager::Client.new
9
+ end
10
+
11
+ def list
12
+ client.list_secrets.map(&:secret_list).flatten
13
+ end
14
+
15
+ def get(id, version = :AWSCURRENT)
16
+ client.get_secret_value(secret_id: id, version_stage: version)
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+ end
@@ -24,6 +24,22 @@ module Stax
24
24
  warn(e.message)
25
25
  end
26
26
 
27
+ def authorize_sg(id, sg, port)
28
+ client.authorize_security_group_ingress(
29
+ group_id: id,
30
+ ip_permissions: [
31
+ {
32
+ ip_protocol: :tcp,
33
+ from_port: port,
34
+ to_port: port,
35
+ user_id_group_pairs: [ { group_id: sg } ],
36
+ }
37
+ ]
38
+ )
39
+ rescue ::Aws::EC2::Errors::InvalidPermissionDuplicate => e
40
+ warn(e.message)
41
+ end
42
+
27
43
  def revoke(id, cidr, port = 22)
28
44
  client.revoke_security_group_ingress(
29
45
  group_id: id,
@@ -36,6 +52,21 @@ module Stax
36
52
  warn(e.message)
37
53
  end
38
54
 
55
+ def revoke_sg(id, sg, port)
56
+ client.revoke_security_group_ingress(
57
+ group_id: id,
58
+ ip_permissions: [
59
+ {
60
+ ip_protocol: :tcp,
61
+ from_port: port,
62
+ to_port: port,
63
+ user_id_group_pairs: [ { group_id: sg } ],
64
+ }
65
+ ]
66
+ )
67
+ rescue ::Aws::EC2::Errors::InvalidPermissionNotFound => e
68
+ warn(e.message)
69
+ end
39
70
  end
40
71
  end
41
72
  end
@@ -33,7 +33,7 @@ end
33
33
 
34
34
  module Stax
35
35
  class Stack < Base
36
- class_option :use_previous_value, aliases: '-u', type: :array, default: [], desc: 'params to use previous value'
36
+ class_option :use_previous_value, aliases: '-u', type: :array, default: nil, desc: 'params to use previous value'
37
37
 
38
38
  no_commands do
39
39
 
@@ -0,0 +1,129 @@
1
+ require 'stax/aws/dms'
2
+
3
+ module Stax
4
+ module Dms
5
+ def self.included(thor)
6
+ thor.desc('dms COMMAND', 'DMS subcommands')
7
+ thor.subcommand(:dms, Cmd::Dms)
8
+ end
9
+
10
+ def stack_dms_endpoints
11
+ @_stack_dms_endpoints ||= Aws::Cfn.resources_by_type(stack_name, 'AWS::DMS::Endpoint')
12
+ end
13
+
14
+ def stack_dms_replication_instances
15
+ @_stack_dms_replication_instances ||= Aws::Cfn.resources_by_type(stack_name, 'AWS::DMS::ReplicationInstance')
16
+ end
17
+
18
+ def stack_dms_replication_tasks
19
+ @_stack_dms_replication_tasks ||= Aws::Cfn.resources_by_type(stack_name, 'AWS::DMS::ReplicationTask')
20
+ end
21
+ end
22
+
23
+ module Cmd
24
+ class Dms < SubCommand
25
+ stax_info :endpoints, :instances, :tasks
26
+
27
+ COLORS = {
28
+ active: :green,
29
+ available: :green,
30
+ successful: :green,
31
+ failed: :red,
32
+ stopped: :red,
33
+ ready: :green,
34
+ }
35
+
36
+ no_commands do
37
+ def dms_endpoint_arns
38
+ my.stack_dms_endpoints.map(&:physical_resource_id)
39
+ end
40
+
41
+ def dms_instance_arns
42
+ my.stack_dms_replication_instances.map(&:physical_resource_id)
43
+ end
44
+
45
+ def dms_task_arns
46
+ my.stack_dms_replication_tasks.map(&:physical_resource_id)
47
+ end
48
+ end
49
+
50
+ desc 'endpoints', 'list endpoints'
51
+ def endpoints
52
+ debug("DMS endpoints for #{my.stack_name}")
53
+ print_table Aws::Dms.endpoints(filters: [{name: 'endpoint-arn', values: dms_endpoint_arns}]).map { |e|
54
+ [e.endpoint_identifier, e.endpoint_type, color(e.status, COLORS), e.engine_name, e.server_name]
55
+ }
56
+ end
57
+
58
+ desc 'instances', 'list replication instances'
59
+ def instances
60
+ debug("DMS replication instances for #{my.stack_name}")
61
+ print_table Aws::Dms.instances(filters: [{name: 'replication-instance-arn', values: dms_instance_arns}]).map { |i|
62
+ [
63
+ i.replication_instance_identifier, color(i.replication_instance_status, COLORS),
64
+ i.replication_subnet_group&.vpc_id, i.replication_instance_class, i.engine_version,
65
+ i.availability_zone, i.replication_instance_private_ip_address,
66
+ ]
67
+ }
68
+ end
69
+
70
+ desc 'tasks', 'list replication tasks'
71
+ def tasks
72
+ debug("DMS replication tasks for #{my.stack_name}")
73
+ print_table Aws::Dms.tasks(filters: [{name: 'replication-task-arn', values: dms_task_arns}]).map { |t|
74
+ [
75
+ t.replication_task_identifier, color(t.status, COLORS), t.migration_type,
76
+ "#{t.replication_task_stats&.full_load_progress_percent}%", "#{(t.replication_task_stats&.elapsed_time_millis/1000).to_i}s",
77
+ "#{t.replication_task_stats&.tables_loaded} loaded", "#{t.replication_task_stats&.tables_errored} errors",
78
+ ]
79
+ }
80
+ end
81
+
82
+ desc 'test', 'test endpoint connections'
83
+ def test
84
+ instance = dms_instance_arns.first # FIXME: handle multiple instances
85
+ dms_endpoint_arns.each do |endpoint|
86
+ debug("Testing connection for #{endpoint}")
87
+ conn = Aws::Dms.test(replication_instance_arn: instance, endpoint_arn: endpoint)
88
+ loop do
89
+ sleep 3
90
+ c = Aws::Dms.connections(
91
+ filters: [
92
+ { name: 'endpoint-arn', values: [conn.endpoint_arn] },
93
+ { name: 'replication-instance-arn', values: [conn.replication_instance_arn] },
94
+ ]
95
+ ).first
96
+ puts [c.endpoint_identifier, c.replication_instance_identifier, color(c.status, COLORS), c.last_failure_message].join(' ')
97
+ break unless c.status == 'testing'
98
+ end
99
+ end
100
+ end
101
+
102
+ desc 'connections', 'list endpoint test connections'
103
+ def connections
104
+ debug("Test connection results for #{my.stack_name}")
105
+ print_table Aws::Dms.connections(filters: [{name: 'endpoint-arn', values: dms_endpoint_arns}]).map { |c|
106
+ [c.endpoint_identifier, c.replication_instance_identifier, color(c.status, COLORS), c.last_failure_message]
107
+ }
108
+ end
109
+
110
+ desc 'start', 'start replication task'
111
+ method_option :tasks, type: :array, default: nil, description: 'task arns to start'
112
+ method_option :resume, type: :boolean, default: false, description: 'resume processing'
113
+ method_option :reload, type: :boolean, default: false, description: 'reload target'
114
+ def start(*tasks)
115
+ type = (options[:resume] && 'resume-processing') || (options[:reload] && 'reload-target') || 'start-replication'
116
+ options.fetch(:tasks, dms_task_arns).each do |task|
117
+ Aws::Dms.start(replication_task_arn: task, start_replication_task_type: type).tap do |r|
118
+ puts [r.replication_task_identifier, r.status, r.replication_task_start_date].join(' ')
119
+ end
120
+ end
121
+ rescue ::Aws::DatabaseMigrationService::Errors::InvalidParameterCombinationException => e
122
+ fail_task(e.message)
123
+ rescue ::Aws::DatabaseMigrationService::Errors::InvalidResourceStateFault => e
124
+ fail_task(e.message)
125
+ end
126
+
127
+ end
128
+ end
129
+ end
@@ -1,14 +1,25 @@
1
1
  require 'json'
2
2
 
3
3
  module Stax
4
+ module DynamoDB
5
+
6
+ ## monkey-patch this method to apply any app-specific changes to payload
7
+ ## args: logical_id, payload hash
8
+ ## returns: new payload
9
+ def dynamo_local_payload_hacks(id, payload)
10
+ payload
11
+ end
12
+
13
+ end
14
+
4
15
  module Cmd
5
16
  class DynamoDB < SubCommand
6
17
 
7
18
  no_commands do
8
19
 
9
- ## client for dynamodb-local endpoint
10
- def client
11
- @_client ||= ::Aws::DynamoDB::Client.new(endpoint: 'http://localhost:8000')
20
+ ## client for dynamodb-local endpoint on given port
21
+ def client(port)
22
+ @_client ||= ::Aws::DynamoDB::Client.new(endpoint: "http://localhost:#{port}")
12
23
  end
13
24
 
14
25
  ## get CFN template and return hash of table configs
@@ -37,8 +48,8 @@ module Stax
37
48
  end
38
49
 
39
50
  ## create table
40
- def dynamo_local_create(payload)
41
- client.create_table(dynamo_ruby_payload(payload))
51
+ def dynamo_local_create(payload, port)
52
+ client(port).create_table(dynamo_ruby_payload(payload))
42
53
  rescue ::Aws::DynamoDB::Errors::ResourceInUseException => e
43
54
  warn(e.message) # table exists
44
55
  rescue Seahorse::Client::NetworkingError => e
@@ -46,24 +57,38 @@ module Stax
46
57
  end
47
58
  end
48
59
 
49
- desc 'local', 'create local tables from template'
60
+ desc 'local-create', 'create local tables from template'
50
61
  method_option :tables, aliases: '-t', type: :array, default: nil, desc: 'filter table ids'
51
62
  method_option :payload, aliases: '-p', type: :boolean, default: false, desc: 'just output payload'
52
- def local
63
+ method_option :port, aliases: '-P', type: :numeric, default: 8000, desc: 'local dynamo port'
64
+ def local_create
53
65
  tables = dynamo_local_tables
54
66
  tables.slice!(*options[:tables]) if options[:tables]
55
67
 
56
68
  tables.each do |id, value|
57
69
  payload = dynamo_payload_from_template(id, value)
70
+ payload = my.dynamo_local_payload_hacks(id, payload) # apply user-supplied hacks
58
71
  if options[:payload]
59
72
  puts JSON.pretty_generate(payload)
60
73
  else
61
74
  puts "create table #{id}"
62
- dynamo_local_create(payload)
75
+ dynamo_local_create(payload, options[:port])
63
76
  end
64
77
  end
65
78
  end
66
79
 
80
+ desc 'local-delete', 'delete local tables from template'
81
+ method_option :tables, aliases: '-t', type: :array, default: nil, desc: 'filter table ids'
82
+ method_option :port, aliases: '-P', type: :numeric, default: 8000, desc: 'local dynamo port'
83
+ def local_delete
84
+ tables = dynamo_local_tables
85
+ tables.slice!(*options[:tables]) if options[:tables]
86
+
87
+ tables.each do |id,_value|
88
+ puts "deleting table #{id}"
89
+ client(options[:port]).delete_table(table_name: id)
90
+ end
91
+ end
67
92
  end
68
93
  end
69
- end
94
+ end
@@ -10,6 +10,8 @@ module Stax
10
10
 
11
11
  module Cmd
12
12
  class Ec2 < SubCommand
13
+ stax_info :ls
14
+
13
15
  COLORS = {
14
16
  ## instances
15
17
  running: :green,
@@ -23,6 +25,7 @@ module Stax
23
25
 
24
26
  desc 'ls', 'list instances for stack'
25
27
  def ls
28
+ debug("EC2 instances for #{my.stack_name}")
26
29
  print_table Aws::Ec2.instances(my.stack_name).map { |i|
27
30
  name = i.tags.find { |t| t.key == 'Name' }&.value
28
31
  [
@@ -0,0 +1,102 @@
1
+ require 'stax/aws/rds'
2
+
3
+ module Stax
4
+ module Rds
5
+ def self.included(thor)
6
+ thor.desc('rds COMMAND', 'RDS subcommands')
7
+ thor.subcommand(:rds, Cmd::Rds)
8
+ end
9
+ end
10
+
11
+ module Cmd
12
+ class Rds < SubCommand
13
+ stax_info :clusters, :instances, :endpoints
14
+
15
+ COLORS = {
16
+ available: :green,
17
+ Complete: :green,
18
+ Active: :green,
19
+ }
20
+
21
+ no_commands do
22
+ def stack_db_clusters
23
+ Aws::Cfn.resources_by_type(my.stack_name, 'AWS::RDS::DBCluster')
24
+ end
25
+
26
+ def stack_db_instances
27
+ Aws::Cfn.resources_by_type(my.stack_name, 'AWS::RDS::DBInstance')
28
+ end
29
+
30
+ def stack_rds_clusters
31
+ filter = { name: 'db-cluster-id', values: stack_db_clusters.map(&:physical_resource_id) }
32
+ Aws::Rds.clusters(filters: [filter])
33
+ end
34
+
35
+ def stack_rds_instances
36
+ filter = { name: 'db-instance-id', values: stack_db_instances.map(&:physical_resource_id) }
37
+ Aws::Rds.instances(filters: [filter])
38
+ end
39
+
40
+ def stack_db_subnet_groups
41
+ Aws::Cfn.resources_by_type(my.stack_name, 'AWS::RDS::DBSubnetGroup')
42
+ end
43
+ end
44
+
45
+ desc 'clusters', 'list db clusters for stack'
46
+ def clusters
47
+ debug("RDS DB clusters for #{my.stack_name}")
48
+ 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]
50
+ }
51
+ end
52
+
53
+ desc 'members', 'list db cluster members for stack'
54
+ def members
55
+ stack_rds_clusters.each do |c|
56
+ debug("RDS DB members for cluster #{c.db_cluster_identifier}")
57
+ print_table c.db_cluster_members.map { |m|
58
+ role = m.is_cluster_writer ? 'writer' : 'reader'
59
+ [m.db_instance_identifier, role, m.db_cluster_parameter_group_status]
60
+ }
61
+ end
62
+ end
63
+
64
+ desc 'instances', 'list db instances for stack'
65
+ def instances
66
+ debug("RDS DB instances for #{my.stack_name}")
67
+ 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]
69
+ }
70
+ end
71
+
72
+ desc 'endpoints', 'list db instance endpoints'
73
+ def endpoints
74
+ stack_rds_clusters.each do |c|
75
+ debug("RDS DB endpoints for cluster #{c.db_cluster_identifier}")
76
+ print_table [
77
+ ['writer', c.endpoint, c.port, c.hosted_zone_id],
78
+ ['reader', c.reader_endpoint, c.port, c.hosted_zone_id],
79
+ ]
80
+ end
81
+
82
+ debug("RDS DB instance endpoints for #{my.stack_name}")
83
+ print_table stack_rds_instances.map { |i|
84
+ [i.db_instance_identifier, i.endpoint&.address, i.endpoint&.port, i.endpoint&.hosted_zone_id]
85
+ }
86
+ end
87
+
88
+ desc 'subnets', 'list db subnet groups'
89
+ def subnets
90
+ stack_db_subnet_groups.map do |r|
91
+ Aws::Rds.subnet_groups(db_subnet_group_name: r.physical_resource_id)
92
+ end.flatten.each do |g|
93
+ debug("Subnets for group #{g.db_subnet_group_name}")
94
+ print_table g.subnets.map { |s|
95
+ [s&.subnet_availability_zone&.name, s&.subnet_identifier, color(s&.subnet_status, COLORS)]
96
+ }
97
+ end
98
+ end
99
+
100
+ end
101
+ end
102
+ end
@@ -15,6 +15,10 @@ module Stax
15
15
  Aws::Cfn.resources_by_type(my.stack_name, 'AWS::S3::Bucket')
16
16
  end
17
17
 
18
+ def stack_s3_bucket_names
19
+ stack_s3_buckets.map(&:physical_resource_id).compact
20
+ end
21
+
18
22
  def stack_tagged_buckets
19
23
  Aws::S3.list_buckets.select do |bucket|
20
24
  region = Aws::S3.bucket_region(bucket.name)
@@ -27,16 +31,27 @@ module Stax
27
31
 
28
32
  desc 'buckets', 'S3 buckets for this stack'
29
33
  def buckets
30
- puts stack_s3_buckets.map(&:physical_resource_id)
34
+ puts stack_s3_bucket_names
31
35
  end
32
36
 
33
37
  desc 'tagged', 'S3 buckets that were tagged by this stack'
34
38
  def tagged
39
+ debug("Buckets tagged by stack #{my.stack_name}")
35
40
  print_table stack_tagged_buckets.map { |b|
36
41
  [b.name, b.creation_date]
37
42
  }
38
43
  end
39
44
 
45
+ desc 'reap', 'delete buckets tagged by this stack'
46
+ def reap
47
+ debug("Deleting buckets tagged by #{my.stack_name}")
48
+ stack_tagged_buckets.each do |b|
49
+ if yes?("Delete bucket and contents #{b.name}?", :yellow)
50
+ ::Aws::S3::Bucket.new(b.name).delete!
51
+ end
52
+ end
53
+ end
54
+
40
55
  desc 'lifecycle', 'show/set lifecycle for tagged buckets'
41
56
  def lifecycle
42
57
  debug("Lifecycle for buckets tagged by #{my.stack_name}")
@@ -71,6 +86,28 @@ module Stax
71
86
  end
72
87
  end
73
88
 
89
+ desc 'clear', 'clear objects from buckets'
90
+ method_option :names, aliases: '-n', type: :array, default: nil, desc: 'names of buckets to clear'
91
+ def clear
92
+ debug("Clearing buckets for #{my.stack_name}")
93
+ (options[:names] || stack_s3_bucket_names).each do |b|
94
+ if yes?("Clear contents of bucket #{b}?", :yellow)
95
+ ::Aws::S3::Bucket.new(b).clear!
96
+ end
97
+ end
98
+ end
99
+
100
+ desc 'delete', 'delete buckets and objects'
101
+ method_option :names, aliases: '-n', type: :array, default: nil, desc: 'names of buckets to delete'
102
+ def delete
103
+ debug("Deleting buckets for #{my.stack_name}")
104
+ (options[:names] || stack_s3_bucket_names).each do |b|
105
+ if yes?("Delete bucket and contents #{b}?", :yellow)
106
+ ::Aws::S3::Bucket.new(b).delete!
107
+ end
108
+ end
109
+ end
110
+
74
111
  end
75
112
  end
76
113
  end
@@ -0,0 +1,37 @@
1
+ require 'stax/aws/secrets_manager'
2
+
3
+ module Stax
4
+ module SecretsManager
5
+ def self.included(thor)
6
+ thor.desc('sm COMMAND', 'SecretsManager subcommands')
7
+ thor.subcommand(:sm, Cmd::SecretsManager)
8
+ end
9
+
10
+ ## monkey-patch in your application as needed
11
+ def secrets_manager_prefix
12
+ @_secrets_manager_prefix ||= "#{app_name}/#{branch_name}/"
13
+ end
14
+ end
15
+
16
+ module Cmd
17
+ class SecretsManager < SubCommand
18
+
19
+ desc 'ls', 'list secrets'
20
+ def ls
21
+ debug("Secrets for #{my.stack_name}")
22
+ print_table Aws::SecretsManager.list.select { |s|
23
+ s.name.start_with?(my.secrets_manager_prefix)
24
+ }.map { |s|
25
+ [s.name, s.description, s.last_accessed_date]
26
+ }.sort
27
+ end
28
+
29
+ desc 'get ID', 'get secret'
30
+ def get(id)
31
+ id = my.secrets_manager_prefix + id unless id.include?('/') # allow absolute or relative path
32
+ puts Aws::SecretsManager.get(id).secret_string
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -16,7 +16,7 @@ module Stax
16
16
 
17
17
  ## IP address to ssh
18
18
  def ssh_instances
19
- Aws::Ec2.instances(stack_name).map(&:public_ip_address)
19
+ Aws::Ec2.instances(stack_name).map(&:public_ip_address).reject(&:nil?)
20
20
  end
21
21
 
22
22
  def ssh_options_format(opt)
@@ -57,6 +57,11 @@ module Stax
57
57
  }
58
58
  end
59
59
 
60
+ ## get status reason, used for a failure message
61
+ def change_set_reason(id)
62
+ Aws::Cfn.client.describe_change_set(stack_name: stack_name, change_set_name: id).status_reason
63
+ end
64
+
60
65
  ## confirm and execute the change set
61
66
  def change_set_execute(id)
62
67
  if yes?("Apply these changes to stack #{stack_name}?", :yellow)
@@ -76,7 +81,7 @@ module Stax
76
81
  desc 'change', 'create and execute a changeset'
77
82
  def change
78
83
  id = change_set_update
79
- change_set_complete?(id) || fail_task('No changes')
84
+ change_set_complete?(id) || fail_task(change_set_reason(id))
80
85
  change_set_changes(id)
81
86
  change_set_unlock
82
87
  change_set_execute(id) && tail && update_warn_imports
@@ -45,20 +45,32 @@ module Stax
45
45
  end
46
46
  end
47
47
 
48
+ ## stack should monkey-patch with list of params to keep on update
49
+ def use_previous_value
50
+ []
51
+ end
52
+
53
+ ## return option or method
54
+ def _use_previous_value
55
+ @_use_previous_value ||= (options[:use_previous_value] || use_previous_value.map(&:to_s))
56
+ end
57
+
58
+ ## get array of params for stack create
48
59
  def cfn_parameters_create
49
- cfn_parameters.map do |k,v|
60
+ @_cfn_parameters_create ||= cfn_parameters.map { |k,v|
50
61
  { parameter_key: k, parameter_value: v }
51
- end
62
+ }
52
63
  end
53
64
 
65
+ ## get array of params for stack update, use previous where requested
54
66
  def cfn_parameters_update
55
- cfn_parameters.map do |k,v|
56
- if options[:use_previous_value].include?(k.to_s)
67
+ @_cfn_parameters_update ||= cfn_parameters.map { |k,v|
68
+ if _use_previous_value.include?(k.to_s)
57
69
  { parameter_key: k, use_previous_value: true }
58
70
  else
59
71
  { parameter_key: k, parameter_value: v }
60
72
  end
61
- end
73
+ }
62
74
  end
63
75
 
64
76
  ## location of templates relative to Staxfile
@@ -25,9 +25,9 @@ module Stax
25
25
  def imports
26
26
  debug("Stacks that import from #{stack_name}")
27
27
  print_table Aws::Cfn.exports(stack_name).map { |e|
28
- imports = (i = Aws::Cfn.imports(e.export_name)).empty? ? '-' : i.join(',')
28
+ imports = (i = Aws::Cfn.imports(e.export_name)).empty? ? '-' : i.join(' ')
29
29
  [e.output_key, imports]
30
- }
30
+ }.sort
31
31
  end
32
32
 
33
33
  end
@@ -1,3 +1,3 @@
1
1
  module Stax
2
- VERSION = '0.0.8'
2
+ VERSION = '0.0.9'
3
3
  end
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.0.8
4
+ version: 0.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Lister
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-06-09 00:00:00.000000000 Z
11
+ date: 2018-11-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -163,6 +163,7 @@ files:
163
163
  - lib/stax/aws/cfn.rb
164
164
  - lib/stax/aws/codebuild.rb
165
165
  - lib/stax/aws/codepipeline.rb
166
+ - lib/stax/aws/dms.rb
166
167
  - lib/stax/aws/dynamodb.rb
167
168
  - lib/stax/aws/ec2.rb
168
169
  - lib/stax/aws/ecr.rb
@@ -175,9 +176,11 @@ files:
175
176
  - lib/stax/aws/kms.rb
176
177
  - lib/stax/aws/lambda.rb
177
178
  - lib/stax/aws/logs.rb
179
+ - lib/stax/aws/rds.rb
178
180
  - lib/stax/aws/route53.rb
179
181
  - lib/stax/aws/s3.rb
180
182
  - lib/stax/aws/sdk.rb
183
+ - lib/stax/aws/secrets_manager.rb
181
184
  - lib/stax/aws/sg.rb
182
185
  - lib/stax/aws/sqs.rb
183
186
  - lib/stax/aws/ssm.rb
@@ -211,6 +214,7 @@ files:
211
214
  - lib/stax/mixin/asg.rb
212
215
  - lib/stax/mixin/codebuild.rb
213
216
  - lib/stax/mixin/codepipeline.rb
217
+ - lib/stax/mixin/dms.rb
214
218
  - lib/stax/mixin/dynamodb.rb
215
219
  - lib/stax/mixin/dynamodb/backup.rb
216
220
  - lib/stax/mixin/dynamodb/local.rb
@@ -226,7 +230,9 @@ files:
226
230
  - lib/stax/mixin/kms.rb
227
231
  - lib/stax/mixin/lambda.rb
228
232
  - lib/stax/mixin/logs.rb
233
+ - lib/stax/mixin/rds.rb
229
234
  - lib/stax/mixin/s3.rb
235
+ - lib/stax/mixin/secrets_manager.rb
230
236
  - lib/stax/mixin/sg.rb
231
237
  - lib/stax/mixin/sqs.rb
232
238
  - lib/stax/mixin/ssh.rb
@@ -263,7 +269,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
263
269
  version: '0'
264
270
  requirements: []
265
271
  rubyforge_project:
266
- rubygems_version: 2.7.3
272
+ rubygems_version: 2.7.6
267
273
  signing_key:
268
274
  specification_version: 4
269
275
  summary: Control Cloudformation stack and other stuff.