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
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