stax 0.0.1 → 0.0.2

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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/bin/stax +1 -4
  3. data/lib/stax.rb +33 -3
  4. data/lib/stax/asg.rb +140 -0
  5. data/lib/stax/aws/alb.rb +28 -0
  6. data/lib/stax/aws/asg.rb +34 -0
  7. data/lib/stax/aws/cfn.rb +102 -0
  8. data/lib/stax/aws/dynamodb.rb +27 -0
  9. data/lib/stax/aws/ec2.rb +22 -0
  10. data/lib/stax/aws/ecr.rb +25 -0
  11. data/lib/stax/aws/ecs.rb +54 -0
  12. data/lib/stax/aws/elb.rb +30 -0
  13. data/lib/stax/aws/emr.rb +28 -0
  14. data/lib/stax/aws/iam.rb +19 -0
  15. data/lib/stax/aws/keypair.rb +26 -0
  16. data/lib/stax/aws/kms.rb +19 -0
  17. data/lib/stax/aws/lambda.rb +33 -0
  18. data/lib/stax/aws/logs.rb +25 -0
  19. data/lib/stax/aws/s3.rb +41 -0
  20. data/lib/stax/aws/sdk.rb +21 -0
  21. data/lib/stax/aws/sg.rb +42 -0
  22. data/lib/stax/aws/sqs.rb +30 -0
  23. data/lib/stax/aws/ssm.rb +49 -0
  24. data/lib/stax/aws/sts.rb +31 -0
  25. data/lib/stax/base.rb +92 -4
  26. data/lib/stax/cfer.rb +59 -0
  27. data/lib/stax/cli.rb +13 -4
  28. data/lib/stax/docker.rb +106 -0
  29. data/lib/stax/dsl.rb +15 -0
  30. data/lib/stax/git.rb +61 -0
  31. data/lib/stax/github.rb +25 -0
  32. data/lib/stax/iam.rb +18 -0
  33. data/lib/stax/keypair.rb +75 -0
  34. data/lib/stax/mixin/alb.rb +45 -0
  35. data/lib/stax/mixin/asg.rb +115 -0
  36. data/lib/stax/mixin/dynamodb.rb +62 -0
  37. data/lib/stax/mixin/ec2.rb +37 -0
  38. data/lib/stax/mixin/ecs.rb +114 -0
  39. data/lib/stax/mixin/elb.rb +42 -0
  40. data/lib/stax/mixin/emr.rb +69 -0
  41. data/lib/stax/mixin/keypair.rb +45 -0
  42. data/lib/stax/mixin/kms.rb +30 -0
  43. data/lib/stax/mixin/lambda.rb +76 -0
  44. data/lib/stax/mixin/logs.rb +94 -0
  45. data/lib/stax/mixin/s3.rb +76 -0
  46. data/lib/stax/mixin/sg.rb +95 -0
  47. data/lib/stax/mixin/sqs.rb +49 -0
  48. data/lib/stax/mixin/ssh.rb +52 -0
  49. data/lib/stax/mixin/ssm.rb +137 -0
  50. data/lib/stax/stack.rb +19 -35
  51. data/lib/stax/stack/cfn.rb +24 -0
  52. data/lib/stax/stack/crud.rb +92 -0
  53. data/lib/stax/stack/outputs.rb +24 -0
  54. data/lib/stax/stack/parameters.rb +24 -0
  55. data/lib/stax/stack/resources.rb +35 -0
  56. data/lib/stax/staxfile.rb +41 -0
  57. data/lib/stax/subcommand.rb +12 -0
  58. data/lib/stax/version.rb +1 -1
  59. data/stax.gemspec +6 -13
  60. metadata +105 -9
@@ -0,0 +1,45 @@
1
+ require 'stax/aws/keypair'
2
+
3
+ module Stax
4
+ module Keypair
5
+ def self.included(thor)
6
+ thor.desc(:keypair, 'Keypair subcommands')
7
+ thor.subcommand(:keypair, Cmd::Keypair)
8
+ end
9
+
10
+ def keypair_create
11
+ Aws::Keypair.create(stack_name).key_material
12
+ rescue ::Aws::EC2::Errors::InvalidKeyPairDuplicate => e
13
+ fail_task(e.message)
14
+ end
15
+
16
+ def keypair_delete
17
+ Aws::Keypair.delete(stack_name)
18
+ end
19
+ end
20
+
21
+ module Cmd
22
+ class Keypair < SubCommand
23
+
24
+ desc 'ls', 'list keypairs'
25
+ method_option :all_keys, aliases: '-a', type: :boolean, default: false, desc: 'list all keys'
26
+ def ls
27
+ names = options[:all_keys] ? nil : [my.stack_name]
28
+ print_table Aws::Keypair.describe(names).map { |k|
29
+ [k.key_name, k.key_fingerprint]
30
+ }
31
+ end
32
+
33
+ desc 'create [NAME]', 'create keypair'
34
+ def create
35
+ puts my.keypair_create
36
+ end
37
+
38
+ desc 'delete [NAME]', 'delete keypair'
39
+ def delete(name = my.stack_name)
40
+ my.keypair_delete if yes?("Really delete keypair #{name}?", :yellow)
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,30 @@
1
+ require 'stax/aws/kms'
2
+
3
+ module Stax
4
+ module Kms
5
+ def self.included(thor)
6
+ thor.desc(:kms, 'KMS subcommands')
7
+ thor.subcommand(:kms, Cmd::Kms)
8
+ end
9
+ end
10
+
11
+ module Cmd
12
+ class Kms < SubCommand
13
+
14
+ no_commands do
15
+ def stack_kms_keys
16
+ Aws::Cfn.resources_by_type(my.stack_name, 'AWS::KMS::Key')
17
+ end
18
+ end
19
+
20
+ desc 'ls', 'list kms keys for stack'
21
+ def ls
22
+ print_table stack_kms_keys.map { |r|
23
+ k = Aws::Kms.describe(r.physical_resource_id)
24
+ [k.key_id, k.key_state, k.creation_date, k.description]
25
+ }
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,76 @@
1
+ require 'open-uri'
2
+ require 'yaml'
3
+ require 'base64'
4
+ require 'stax/aws/lambda'
5
+
6
+ module Stax
7
+ module Lambda
8
+ def self.included(thor)
9
+ thor.desc(:lambda, 'Lambda subcommands')
10
+ thor.subcommand(:lambda, Cmd::Lambda)
11
+ end
12
+ end
13
+
14
+ module Cmd
15
+ class Lambda < SubCommand
16
+
17
+ no_commands do
18
+ def stack_lambdas
19
+ Aws::Cfn.resources_by_type(my.stack_name, 'AWS::Lambda::Function')
20
+ end
21
+ end
22
+
23
+ desc 'ls', 'list lambdas for stack'
24
+ def ls
25
+ names = stack_lambdas.map(&:physical_resource_id)
26
+ print_table Aws::Lambda.list.select { |l|
27
+ names.include?(l.function_name)
28
+ }.map { |l|
29
+ size = (l.code_size/1.0.megabyte).round.to_s + 'MB'
30
+ [l.function_name, l.description, l.runtime, size, l.last_modified]
31
+ }
32
+ end
33
+
34
+ desc 'config ID', 'get function configuration'
35
+ def config(id)
36
+ cfg = Aws::Lambda.configuration(my.resource(id))
37
+ puts YAML.dump(stringify_keys(cfg.to_hash))
38
+ end
39
+
40
+ desc 'code ID', 'get code for lambda function with ID'
41
+ method_option :url, type: :boolean, default: false, desc: 'return just URL for code'
42
+ def code(id)
43
+ url = Aws::Lambda.code(my.resource(id))
44
+ if options[:url]
45
+ puts url
46
+ else
47
+ Tempfile.new([my.stack_name, '.zip']).tap do |file|
48
+ file.write(open(url).read)
49
+ file.close
50
+ puts %x[unzip -p #{file.path}] # unzip all contents to stdout
51
+ end
52
+ end
53
+ end
54
+
55
+ desc 'test ID', 'run lambda with ID'
56
+ method_option :type, type: :string, default: nil, desc: 'invocation type: RequestResponse, Event'
57
+ method_option :tail, type: :boolean, default: false, desc: 'tail log for RequestResponse'
58
+ method_option :payload, type: :string, default: nil, desc: 'json input to function'
59
+ method_option :file, type: :string, default: nil, desc: 'get json payload from file'
60
+ def test(id)
61
+ Aws::Lambda.invoke(
62
+ function_name: my.resource(id),
63
+ invocation_type: options[:type],
64
+ log_type: options[:tail] ? 'Tail' : nil,
65
+ payload: options[:file] ? File.open(options[:file]) : options[:payload],
66
+ ).tap do |resp|
67
+ puts resp.status_code
68
+ warn(resp.function_error) if resp.function_error
69
+ puts Base64.decode64(resp.log_result) if options[:tail]
70
+ end
71
+ end
72
+
73
+ end
74
+ end
75
+
76
+ end
@@ -0,0 +1,94 @@
1
+ require 'stax/aws/logs'
2
+
3
+ module Stax
4
+ module Logs
5
+
6
+ def self.included(thor)
7
+ thor.desc(:logs, 'Logs subcommands')
8
+ thor.subcommand(:logs, Cmd::Logs)
9
+ end
10
+
11
+ def stack_log_groups
12
+ @_stack_log_groups ||= stack_resources_by_type('AWS::Logs::LogGroup')
13
+ end
14
+
15
+ end
16
+
17
+ module Cmd
18
+ class Logs < SubCommand
19
+
20
+ no_commands do
21
+ ## n-th most-recently updated stream
22
+ def latest_stream(group, n = 0)
23
+ n = n.to_i.abs # convert string and -ves
24
+ Aws::Logs.streams(log_group_name: group, order_by: :LastEventTime, descending: true, limit: n+1)[n]
25
+ end
26
+
27
+ ## hash of resource id to log group objects, including lambda auto-created groups
28
+ def log_groups
29
+ {}.tap do |h|
30
+ my.stack_resources_by_type('AWS::Logs::LogGroup').each do |r|
31
+ h[r.logical_resource_id] = Aws::Logs.groups(r.physical_resource_id)&.first
32
+ end
33
+ my.stack_resources_by_type('AWS::Lambda::Function').each do |r|
34
+ h[r.logical_resource_id] = Aws::Logs.groups("/aws/lambda/#{r.physical_resource_id}")&.first
35
+ end
36
+ end.compact # lambda groups may be nil if not invoked yet
37
+ end
38
+ end
39
+
40
+ desc 'groups', 'list log groups for stack'
41
+ def groups
42
+ print_table log_groups.map { |id, g|
43
+ [id, g.log_group_name, g.retention_in_days, human_time(g.creation_time), human_bytes(g.stored_bytes)]
44
+ }
45
+ end
46
+
47
+ desc 'streams', 'list log streams'
48
+ method_option :alpha, aliases: '-a', type: :boolean, default: false, desc: 'order by name'
49
+ method_option :limit, aliases: '-n', type: :numeric, default: nil, desc: 'number of streams to list'
50
+ def streams
51
+ log_groups.each do |id, group|
52
+ debug(group.log_group_name)
53
+ streams = Aws::Logs.streams(
54
+ log_group_name: group.log_group_name,
55
+ order_by: options[:alpha] ? :LogStreamName : :LastEventTime,
56
+ descending: !options[:alpha], # most recent first
57
+ limit: options[:limit],
58
+ )
59
+ print_table streams.map { |s|
60
+ [s.log_stream_name, human_time(s.last_event_timestamp), human_bytes(s.stored_bytes)]
61
+ }
62
+ end
63
+ end
64
+
65
+ desc 'tail [STREAM]', 'tail latest/given log stream'
66
+ method_option :group, aliases: '-g', type: :string, default: nil, desc: 'log group to tail'
67
+ method_option :numlines, aliases: '-n', type: :numeric, default: 10, desc: 'number of lines to show'
68
+ method_option :follow, aliases: '-f', type: :boolean, default: false, desc: 'follow log output'
69
+ method_option :sleep, aliases: '-s', type: :numeric, default: 1, desc: 'seconds to sleep between poll for new data'
70
+ def tail(stream = nil)
71
+ trap('SIGINT', 'EXIT') # clean exit with ctrl-c
72
+ group = ((g = options[:group]) ? log_groups[g] : log_groups.values.first).log_group_name
73
+ stream ||= latest_stream(group).log_stream_name
74
+
75
+ debug("Log stream #{group}/#{stream}")
76
+ token = nil
77
+ loop do
78
+ resp = Aws::Logs.client.get_log_events(
79
+ log_group_name: group,
80
+ log_stream_name: stream,
81
+ limit: options[:numlines],
82
+ next_token: token,
83
+ )
84
+ resp.events.each do |e|
85
+ puts("#{set_color(human_time(e.timestamp).utc, :green)} #{e.message}")
86
+ end
87
+ token = resp.next_forward_token
88
+ options[:follow] ? sleep(options[:sleep]) : break
89
+ end
90
+ end
91
+
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,76 @@
1
+ require 'stax/aws/s3'
2
+
3
+ module Stax
4
+ module S3
5
+ def self.included(thor)
6
+ thor.desc(:s3, 'S3 subcommands')
7
+ thor.subcommand(:s3, Cmd::S3)
8
+ end
9
+ end
10
+
11
+ module Cmd
12
+ class S3 < SubCommand
13
+ no_commands do
14
+ def stack_s3_buckets
15
+ Aws::Cfn.resources_by_type(my.stack_name, 'AWS::S3::Bucket')
16
+ end
17
+
18
+ def stack_tagged_buckets
19
+ Aws::S3.list_buckets.select do |bucket|
20
+ region = Aws::S3.bucket_region(bucket.name)
21
+ next unless region.empty? || region == ENV['AWS_REGION']
22
+ tags = Aws::S3.bucket_tags(bucket.name)
23
+ tags.any? { |t| t.key == 'aws:cloudformation:stack-name' && t.value == my.stack_name }
24
+ end
25
+ end
26
+ end
27
+
28
+ desc 'buckets', 'S3 buckets for this stack'
29
+ def buckets
30
+ puts stack_s3_buckets.map(&:physical_resource_id)
31
+ end
32
+
33
+ desc 'tagged', 'S3 buckets that were tagged by this stack'
34
+ def tagged
35
+ print_table stack_tagged_buckets.map { |b|
36
+ [b.name, b.creation_date]
37
+ }
38
+ end
39
+
40
+ desc 'lifecycle', 'show/set lifecycle for tagged buckets'
41
+ def lifecycle
42
+ debug("Lifecycle for buckets tagged by #{my.stack_name}")
43
+ stack_tagged_buckets.each do |bucket|
44
+ Aws::S3.get_lifecycle(bucket.name).each do |l|
45
+ puts YAML.dump(stringify_keys(l.to_hash))
46
+ end
47
+ end
48
+ end
49
+
50
+ desc 'expire', 'expire objects in tagged buckets'
51
+ def expire(days = 1)
52
+ debug("Expiring objects in buckets tagged by #{my.stack_name}")
53
+ stack_tagged_buckets.each do |bucket|
54
+ if yes?("Expire all objects for #{bucket.name} in #{days}d?", :yellow)
55
+ Aws::S3.put_lifecycle(
56
+ bucket.name,
57
+ rules: [
58
+ {
59
+ prefix: '', # required, all objects
60
+ status: :Enabled,
61
+ expiration: {
62
+ days: days,
63
+ },
64
+ noncurrent_version_expiration: {
65
+ noncurrent_days: days,
66
+ },
67
+ }
68
+ ]
69
+ )
70
+ end
71
+ end
72
+ end
73
+
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,95 @@
1
+ require 'stax/aws/sg'
2
+ require 'open-uri'
3
+
4
+ module Stax
5
+ module Sg
6
+ def self.included(thor)
7
+ thor.desc(:sg, 'Security group subcommands')
8
+ thor.subcommand(:sg, Cmd::Sg)
9
+ end
10
+
11
+ ## look up my local public IP
12
+ def get_my_ip
13
+ URI.parse('http://v4.ident.me/').read + '/32'
14
+ end
15
+
16
+ def sg_authorize(id, cidr = get_my_ip, port = 22)
17
+ Aws::Sg.authorize(id, cidr, port)
18
+ end
19
+
20
+ def sg_revoke(id, cidr = get_my_ip, port = 22)
21
+ Aws::Sg.revoke(id, cidr, port)
22
+ end
23
+ end
24
+
25
+ module Cmd
26
+ class Sg < SubCommand
27
+ no_commands do
28
+ def stack_security_groups
29
+ Aws::Cfn.resources_by_type(my.stack_name, 'AWS::EC2::SecurityGroup')
30
+ end
31
+
32
+ def get_id(id)
33
+ id.match(/^sg-\h{8}$/) ? id : Aws::Cfn.id(my.stack_name, id)
34
+ end
35
+
36
+ def stack_security_group(id)
37
+ Aws::Sg.describe(get_id(id))
38
+ end
39
+
40
+ ## format permissions output
41
+ def sg_permissions(perms)
42
+ perms.map do |p|
43
+ proto = (p.ip_protocol == '-1') ? 'all' : p.ip_protocol
44
+ port = ((p.from_port == p.to_port) ? p.from_port : [p.from_port, p.to_port].join('-')) || 'all'
45
+ [proto, port, p.ip_ranges.map(&:cidr_ip).join(','), p.user_id_group_pairs.map(&:group_id).join(',')]
46
+ end
47
+ end
48
+
49
+ ## lookup my IP as a CIDR
50
+ def get_my_ip
51
+ open('http://v4.ident.me/').read + '/32'
52
+ end
53
+
54
+ end
55
+
56
+ desc 'ls', 'SGs for stack'
57
+ def ls
58
+ print_table Aws::Sg.describe(stack_security_groups.map(&:physical_resource_id)).map { |s|
59
+ [s.group_name, s.group_id, s.vpc_id, s.description]
60
+ }
61
+ end
62
+
63
+ desc 'inbound ID', 'SG inbound permissions'
64
+ def inbound
65
+ stack_security_groups.each do |s|
66
+ debug("Inbound permissions for #{s.logical_resource_id} #{s.physical_resource_id}")
67
+ print_table sg_permissions(stack_security_group(s.physical_resource_id).first.ip_permissions)
68
+ end
69
+ end
70
+
71
+ desc 'outbound ID', 'SG outbound permissions'
72
+ def outbound
73
+ stack_security_groups.each do |s|
74
+ debug("Outbound permissions for #{s.logical_resource_id} #{s.physical_resource_id}")
75
+ print_table sg_permissions(stack_security_group(s.physical_resource_id).first.ip_permissions_egress)
76
+ end
77
+ end
78
+
79
+ desc 'authorize ID', 'open port on security group'
80
+ method_option :cidr, type: :string, default: nil, desc: 'cidr block to open'
81
+ method_option :port, type: :numeric, default: 22, desc: 'port to open'
82
+ def authorize(id)
83
+ Aws::Sg.authorize(get_id(id), options.fetch(:cidr, get_my_ip), options[:port])
84
+ end
85
+
86
+ desc 'revoke ID', 'close port on security group'
87
+ method_option :cidr, type: :string, default: nil, desc: 'cidr block to close'
88
+ method_option :port, type: :numeric, default: 22, desc: 'port to close'
89
+ def revoke(id)
90
+ Aws::Sg.revoke(get_id(id), options.fetch(:cidr, get_my_ip), options[:port])
91
+ end
92
+
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,49 @@
1
+ require 'stax/aws/sqs'
2
+
3
+ module Stax
4
+ module Sqs
5
+ def self.included(thor)
6
+ thor.desc(:sqs, 'SQS subcommands')
7
+ thor.subcommand(:sqs, Cmd::Sqs)
8
+ end
9
+ end
10
+
11
+ module Cmd
12
+ class Sqs < SubCommand
13
+
14
+ no_commands do
15
+ def stack_sqs_queues
16
+ Aws::Cfn.resources_by_type(my.stack_name, 'AWS::SQS::Queue')
17
+ end
18
+ end
19
+
20
+ desc 'ls', 'SQS queues'
21
+ def ls
22
+ print_table stack_sqs_queues.map { |r|
23
+ q = Aws::Sqs.get(r.physical_resource_id)
24
+ [
25
+ q['QueueArn'].split(':').last,
26
+ q['ApproximateNumberOfMessages'],
27
+ q['ApproximateNumberOfMessagesNotVisible'],
28
+ Time.at(q['LastModifiedTimestamp'].to_i),
29
+ ]
30
+ }
31
+ end
32
+
33
+ desc 'purge', 'purge SQS queues'
34
+ def purge
35
+ stack_sqs_queues.each do |q|
36
+ name = q.physical_resource_id.split('/').last
37
+ if yes?("Purge queue #{name}?", :yellow)
38
+ begin
39
+ Aws::Sqs.purge(q.physical_resource_id)
40
+ rescue ::Aws::SQS::Errors::PurgeQueueInProgress => e
41
+ warn(e.message)
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+ end