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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 52b0ae114cdff3beab9a839cd4ce6aa0c00d8230
4
- data.tar.gz: 57967dd9cfcdf63b513dfb197b9961bdcb2551e1
3
+ metadata.gz: d9678c52393c842e801f8e3c306eb6976049e7d9
4
+ data.tar.gz: 6732bf5a03a47223ac78300ff338d71df796047c
5
5
  SHA512:
6
- metadata.gz: 50a8a1d2574ad73215cf9f6f0fc5f820c556422314df2a9990b5bbb1a38238d2c85e83b2ed987e5fc5b586098897c2d870005eb4ef9c232b538a3a74c7124a84
7
- data.tar.gz: 741fe79a952dbdfd496799378fc8b988766a35bc59102cd4f234619b4edca4b9fd66162a9f9f66cd889a19e400b6ecfde6378b43b9f44b7b72196c3e56f8d1e4
6
+ metadata.gz: 97d814ccc5329e44a534213a6100a8f485e4c2c59eac762cc6aa1b5ccabd79c2cb0707f4d82383fcd5d29fb914836cdbfd0f2af9ad5ddbd900a68824af6022e5
7
+ data.tar.gz: de492742715aaf17fbcd5a57cd4a626469264a6a1af95d7bc5808b58007d28aa02ee9861643affefd33b7d5fe89547c9fc55e08b3b14cf9be6767599230b99b1
data/bin/stax CHANGED
@@ -2,8 +2,5 @@
2
2
  require 'bundler/setup'
3
3
  require 'stax'
4
4
 
5
- module Stax
6
- add_stack(:botsy)
7
- end
8
-
5
+ Stax.load_staxfile
9
6
  Stax::Cli.start(ARGV)
@@ -1,5 +1,35 @@
1
- require 'awful'
2
- require 'awful/cloudformation'
1
+ require 'thor'
2
+
3
+ require 'stax/aws/sdk'
4
+ require 'stax/aws/cfn'
5
+
6
+ require 'stax/dsl'
7
+ require 'stax/staxfile'
3
8
  require 'stax/base'
9
+ require 'stax/git'
4
10
  require 'stax/cli'
5
- require 'stax/stack'
11
+ require 'stax/subcommand'
12
+ require 'stax/cfer'
13
+ require 'stax/stack'
14
+ require 'stax/stack/cfn'
15
+ require 'stax/stack/crud'
16
+ require 'stax/stack/parameters'
17
+ require 'stax/stack/outputs'
18
+ require 'stax/stack/resources'
19
+
20
+ require 'stax/mixin/ec2'
21
+ require 'stax/mixin/alb'
22
+ require 'stax/mixin/elb'
23
+ require 'stax/mixin/sg'
24
+ require 'stax/mixin/s3'
25
+ require 'stax/mixin/asg'
26
+ require 'stax/mixin/ecs'
27
+ require 'stax/mixin/sqs'
28
+ require 'stax/mixin/kms'
29
+ require 'stax/mixin/ssm'
30
+ require 'stax/mixin/keypair'
31
+ require 'stax/mixin/emr'
32
+ require 'stax/mixin/ssh'
33
+ require 'stax/mixin/lambda'
34
+ require 'stax/mixin/dynamodb'
35
+ require 'stax/mixin/logs'
@@ -0,0 +1,140 @@
1
+ require 'awful/auto_scaling'
2
+
3
+ module Stax
4
+ module Asg
5
+ def self.included(thor) # magic to make mixins work in Thor
6
+ thor.class_eval do # ... so magical
7
+
8
+ class_option :groups, aliases: '-g', type: :array, default: nil, desc: 'limit ASGs returned by id'
9
+
10
+ no_commands do
11
+ def auto_scaling_groups
12
+ cf(:resources, [stack_name], type: ['AWS::AutoScaling::AutoScalingGroup'], quiet: true).tap do |asgs|
13
+ if options[:groups]
14
+ ids = options[:groups].map { |group| prepend(:asg, group) }
15
+ asgs.select! { |g| ids.include?(g.logical_resource_id) }
16
+ end
17
+ end
18
+ end
19
+
20
+ def auto_scaling_instances
21
+ asg(:instances, auto_scaling_groups.map(&:physical_resource_id), describe: true, quiet: true)
22
+ end
23
+
24
+ ## get instance details from ec2
25
+ def auto_scaling_describe_instances
26
+ asgs = auto_scaling_groups.map(&:physical_resource_id)
27
+ fail_task("No matching autoscaling groups") if asgs.empty?
28
+ asg(:ips, auto_scaling_groups.map(&:physical_resource_id), quiet: true)
29
+ end
30
+
31
+ def asg_status
32
+ auto_scaling_groups.each do |asg|
33
+ debug("ASG status for #{asg.physical_resource_id}")
34
+ asg(:instances, [asg.physical_resource_id], long: true)
35
+ end
36
+ end
37
+
38
+ def asg_enter_standby(asg, *instances)
39
+ debug("Taking #{instances.join(',')} out of ELB for #{asg}")
40
+ instances.each do |instance| # one at a time so we can rescue each one
41
+ begin
42
+ asg(:enter_standby, [asg, instance])
43
+ rescue Aws::AutoScaling::Errors::ValidationError => e
44
+ warn(e.message)
45
+ end
46
+ end
47
+ end
48
+
49
+ def asg_exit_standby(asg, *instances)
50
+ debug("Putting #{instances.join(',')} back into ELB")
51
+ instances.each do |instance| # one at a time so we can rescue each one
52
+ begin
53
+ asg(:exit_standby, [asg, instance])
54
+ rescue Aws::AutoScaling::Errors::ValidationError => e
55
+ warn(e.message)
56
+ end
57
+ end
58
+ end
59
+
60
+ ## defaults, override in subclass
61
+ def ssh_options
62
+ {
63
+ User: 'core',
64
+ StrictHostKeyChecking: 'no',
65
+ UserKnownHostsFile: '/dev/null'
66
+ }
67
+ end
68
+
69
+ ## return num instances, filter by ids if non-nil
70
+ def ssh_instances(num, ids)
71
+ instances = auto_scaling_describe_instances
72
+ instances.select!{ |i| ids.include?(i.instance_id) } if ids
73
+ num ? instances.last(num) : instances
74
+ end
75
+ end
76
+
77
+ desc 'scale', 'scale number of instances in ASGs for stack'
78
+ method_option :desired_capacity, aliases: '-d', type: :numeric, default: nil, desc: 'desired instance count for each ASG'
79
+ method_option :min_size, aliases: '-m', type: :numeric, default: nil, desc: 'set minimum capacity'
80
+ method_option :max_size, aliases: '-M', type: :numeric, default: nil, desc: 'set maximum capacity'
81
+ def scale
82
+ opt = options.slice(:desired_capacity, :min_size, :max_size)
83
+ fail_task('No change requested') if opt.empty?
84
+
85
+ auto_scaling_groups.tap do |asgs|
86
+ warn('No matching auto-scaling groups') if asgs.empty?
87
+ end.each do |asg|
88
+ id = asg.physical_resource_id
89
+ debug("Scaling to #{opt} for #{id}")
90
+ asg(:update, [id], opt)
91
+ end
92
+ end
93
+
94
+ desc 'old', 'list or terminate old instances from ASGs'
95
+ method_option :terminate, aliases: '-t', type: :boolean, default: false, desc: 'terminate old instances'
96
+ def old
97
+ verb = options[:terminate] ? 'Terminating' : 'Listing'
98
+ debug("#{verb} out-of-date instances in autoscaling groups")
99
+ asgs = auto_scaling_groups.map(&:physical_resource_id)
100
+ asg(:old_instances, asgs, terminate: options[:terminate])
101
+ end
102
+
103
+ desc 'standby', 'enter (or exit) standby for ASGs'
104
+ method_option :exit, aliases: '-x', type: :boolean, default: false, desc: 'exit standby instead of enter'
105
+ def standby
106
+ auto_scaling_instances.each_with_object(Hash.new {|h,k| h[k]=[]}) do |i, h|
107
+ h[i.auto_scaling_group_name] << i.instance_id
108
+ end.each do |asg, ins|
109
+ options[:exit] ? asg_exit_standby(asg, *ins) : asg_enter_standby(asg, *ins)
110
+ end
111
+ end
112
+
113
+ desc 'ssh [CMD]', 'ssh to ASG instances'
114
+ method_option :number, aliases: '-n', type: :numeric, default: nil, desc: 'number of instances to ssh'
115
+ method_option :verbose, aliases: '-v', type: :boolean, default: false, desc: 'verbose ssh client logging'
116
+ method_option :instances, aliases: '-i', type: :array, default: nil, desc: 'match on these instance IDs'
117
+ def ssh(*cmd)
118
+ keyfile = try(:key_pair_get) # get private key from param store
119
+ try(:let_me_in_allow) # open security group
120
+
121
+ ## build ssh -o options
122
+ opts = ssh_options.merge(
123
+ IdentityFile: keyfile.try(:path),
124
+ LogLevel: (options[:verbose] ? 'DEBUG' : nil)
125
+ ).reject{ |_,v| v.nil? }.map{ |k,v| "-o #{k}=#{v}" }.join(' ')
126
+
127
+ ## loop instances
128
+ ssh_instances(options[:number], options[:instances]).each do |i|
129
+ debug("SSH to #{i.instance_id} #{i.public_ip_address}")
130
+ system "ssh #{opts} #{i.public_ip_address} #{cmd.join(' ')}"
131
+ end
132
+ ensure
133
+ keyfile.try(:unlink) # remove private key
134
+ try(:let_me_in_revoke) # close security group
135
+ end
136
+
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,28 @@
1
+ module Stax
2
+ module Aws
3
+ class Alb < Sdk
4
+
5
+ class << self
6
+
7
+ def client
8
+ @_client ||= ::Aws::ElasticLoadBalancingV2::Client.new
9
+ end
10
+
11
+ def describe(alb_arns)
12
+ client.describe_load_balancers(load_balancer_arns: alb_arns).load_balancers
13
+ end
14
+
15
+ def target_groups(alb_arn)
16
+ paginate(:target_groups) do |marker|
17
+ client.describe_target_groups(load_balancer_arn: alb_arn, marker: marker)
18
+ end
19
+ end
20
+
21
+ def target_health(tg_arn)
22
+ client.describe_target_health(target_group_arn: tg_arn).target_health_descriptions.flatten(1)
23
+ end
24
+
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,34 @@
1
+ module Stax
2
+ module Aws
3
+ class Asg < Sdk
4
+
5
+ class << self
6
+
7
+ def client
8
+ @_client ||= ::Aws::AutoScaling::Client.new
9
+ end
10
+
11
+ def describe(names)
12
+ paginate(:auto_scaling_groups) do |token|
13
+ client.describe_auto_scaling_groups(auto_scaling_group_names: Array(names), next_token: token)
14
+ end
15
+ end
16
+
17
+ def instances(names)
18
+ ids = describe(names).map(&:instances).flatten.map(&:instance_id)
19
+ paginate(:auto_scaling_instances) do |token|
20
+ client.describe_auto_scaling_instances(instance_ids: ids, next_token: token)
21
+ end
22
+ end
23
+
24
+ def update(name, opt = {})
25
+ client.update_auto_scaling_group(opt.merge(auto_scaling_group_name: name))
26
+ end
27
+
28
+ def terminate(id, decrement = false)
29
+ client.terminate_instance_in_auto_scaling_group(instance_id: id, should_decrement_desired_capacity: decrement)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,102 @@
1
+ module Stax
2
+ module Aws
3
+ class Cfn < Sdk
4
+
5
+ ## stack statuses that are not DELETE_COMPLETE
6
+ STATUSES = %i[
7
+ CREATE_IN_PROGRESS CREATE_FAILED CREATE_COMPLETE
8
+ ROLLBACK_IN_PROGRESS ROLLBACK_FAILED ROLLBACK_COMPLETE
9
+ DELETE_IN_PROGRESS DELETE_FAILED
10
+ UPDATE_IN_PROGRESS UPDATE_COMPLETE_CLEANUP_IN_PROGRESS UPDATE_COMPLETE
11
+ UPDATE_ROLLBACK_IN_PROGRESS UPDATE_ROLLBACK_FAILED UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS UPDATE_ROLLBACK_COMPLETE
12
+ REVIEW_IN_PROGRESS
13
+ ]
14
+
15
+ COLORS = {
16
+ CREATE_COMPLETE: :green,
17
+ DELETE_COMPLETE: :green,
18
+ UPDATE_COMPLETE: :green,
19
+ CREATE_FAILED: :red,
20
+ DELETE_FAILED: :red,
21
+ UPDATE_FAILED: :red,
22
+ ROLLBACK_IN_PROGRESS: :red,
23
+ ROLLBACK_COMPLETE: :red,
24
+ }
25
+
26
+ class << self
27
+
28
+ def client
29
+ @_client ||= ::Aws::CloudFormation::Client.new
30
+ end
31
+
32
+ def stacks
33
+ paginate(:stack_summaries) do |token|
34
+ client.list_stacks(stack_status_filter: STATUSES, next_token: token)
35
+ end
36
+ end
37
+
38
+ def template(name)
39
+ client.get_template(stack_name: name).template_body
40
+ end
41
+
42
+ def resources(name)
43
+ paginate(:stack_resource_summaries) do |token|
44
+ client.list_stack_resources(stack_name: name, next_token: token)
45
+ end
46
+ end
47
+
48
+ def resources_by_type(name, type)
49
+ resources(name).select do |r|
50
+ r.resource_type == type
51
+ end
52
+ end
53
+
54
+ def events(name)
55
+ paginate(:stack_events) do |token|
56
+ client.describe_stack_events(stack_name: name, next_token: token)
57
+ end
58
+ rescue ::Aws::CloudFormation::Errors::ValidationError => e
59
+ puts e.message
60
+ end
61
+
62
+ def id(name, id)
63
+ client.describe_stack_resource(stack_name: name, logical_resource_id: id).stack_resource_detail.physical_resource_id
64
+ end
65
+
66
+ def parameters(name)
67
+ client.describe_stacks(stack_name: name).stacks.first.parameters
68
+ end
69
+
70
+ def describe(name)
71
+ client.describe_stacks(stack_name: name).stacks.first
72
+ end
73
+
74
+ def exists?(name)
75
+ Cfn.describe(name) && true
76
+ rescue ::Aws::CloudFormation::Errors::ValidationError
77
+ false
78
+ end
79
+
80
+ def outputs(name)
81
+ describe(name).outputs.each_with_object(HashWithIndifferentAccess.new) do |o, h|
82
+ h[o.output_key] = o.output_value
83
+ end
84
+ end
85
+
86
+ def output(name, key)
87
+ outputs(name)[key]
88
+ end
89
+
90
+ def delete(name)
91
+ client.delete_stack(stack_name: name)
92
+ end
93
+
94
+ def protection(name, enable)
95
+ client.update_termination_protection(stack_name: name, enable_termination_protection: enable)
96
+ end
97
+
98
+ end
99
+
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,27 @@
1
+ module Stax
2
+ module Aws
3
+ class DynamoDB < Sdk
4
+
5
+ class << self
6
+
7
+ def client
8
+ @_client ||= ::Aws::DynamoDB::Client.new
9
+ end
10
+
11
+ def table(name)
12
+ client.describe_table(table_name: name).table
13
+ end
14
+
15
+ def gsi(name)
16
+ client.describe_table(table_name: name).table.global_secondary_indexes || []
17
+ end
18
+
19
+ def lsi(name)
20
+ client.describe_table(table_name: name).table.local_secondary_indexes || []
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ module Stax
2
+ module Aws
3
+ class Ec2 < Sdk
4
+
5
+ class << self
6
+
7
+ def client
8
+ @_client ||= ::Aws::EC2::Client.new
9
+ end
10
+
11
+ ## return instances tagged by stack with name
12
+ def instances(name)
13
+ filter = {name: 'tag:aws:cloudformation:stack-name', values: [name]}
14
+ paginate(:reservations) do |token|
15
+ client.describe_instances(filters: [filter], next_token: token)
16
+ end.map(&:instances).flatten
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ require 'base64'
2
+
3
+ module Stax
4
+ module Aws
5
+ class Ecr < Sdk
6
+
7
+ class << self
8
+
9
+ def client
10
+ @_client ||= ::Aws::ECR::Client.new
11
+ end
12
+
13
+ def auth
14
+ client.get_authorization_token.authorization_data
15
+ end
16
+
17
+ def exists?(repo, tag)
18
+ !client.batch_get_image(repository_name: repo, image_ids: [{image_tag: tag}]).images.empty?
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+ end
25
+ end