stax 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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