stax 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/stax +1 -4
- data/lib/stax.rb +33 -3
- data/lib/stax/asg.rb +140 -0
- data/lib/stax/aws/alb.rb +28 -0
- data/lib/stax/aws/asg.rb +34 -0
- data/lib/stax/aws/cfn.rb +102 -0
- data/lib/stax/aws/dynamodb.rb +27 -0
- data/lib/stax/aws/ec2.rb +22 -0
- data/lib/stax/aws/ecr.rb +25 -0
- data/lib/stax/aws/ecs.rb +54 -0
- data/lib/stax/aws/elb.rb +30 -0
- data/lib/stax/aws/emr.rb +28 -0
- data/lib/stax/aws/iam.rb +19 -0
- data/lib/stax/aws/keypair.rb +26 -0
- data/lib/stax/aws/kms.rb +19 -0
- data/lib/stax/aws/lambda.rb +33 -0
- data/lib/stax/aws/logs.rb +25 -0
- data/lib/stax/aws/s3.rb +41 -0
- data/lib/stax/aws/sdk.rb +21 -0
- data/lib/stax/aws/sg.rb +42 -0
- data/lib/stax/aws/sqs.rb +30 -0
- data/lib/stax/aws/ssm.rb +49 -0
- data/lib/stax/aws/sts.rb +31 -0
- data/lib/stax/base.rb +92 -4
- data/lib/stax/cfer.rb +59 -0
- data/lib/stax/cli.rb +13 -4
- data/lib/stax/docker.rb +106 -0
- data/lib/stax/dsl.rb +15 -0
- data/lib/stax/git.rb +61 -0
- data/lib/stax/github.rb +25 -0
- data/lib/stax/iam.rb +18 -0
- data/lib/stax/keypair.rb +75 -0
- data/lib/stax/mixin/alb.rb +45 -0
- data/lib/stax/mixin/asg.rb +115 -0
- data/lib/stax/mixin/dynamodb.rb +62 -0
- data/lib/stax/mixin/ec2.rb +37 -0
- data/lib/stax/mixin/ecs.rb +114 -0
- data/lib/stax/mixin/elb.rb +42 -0
- data/lib/stax/mixin/emr.rb +69 -0
- data/lib/stax/mixin/keypair.rb +45 -0
- data/lib/stax/mixin/kms.rb +30 -0
- data/lib/stax/mixin/lambda.rb +76 -0
- data/lib/stax/mixin/logs.rb +94 -0
- data/lib/stax/mixin/s3.rb +76 -0
- data/lib/stax/mixin/sg.rb +95 -0
- data/lib/stax/mixin/sqs.rb +49 -0
- data/lib/stax/mixin/ssh.rb +52 -0
- data/lib/stax/mixin/ssm.rb +137 -0
- data/lib/stax/stack.rb +19 -35
- data/lib/stax/stack/cfn.rb +24 -0
- data/lib/stax/stack/crud.rb +92 -0
- data/lib/stax/stack/outputs.rb +24 -0
- data/lib/stax/stack/parameters.rb +24 -0
- data/lib/stax/stack/resources.rb +35 -0
- data/lib/stax/staxfile.rb +41 -0
- data/lib/stax/subcommand.rb +12 -0
- data/lib/stax/version.rb +1 -1
- data/stax.gemspec +6 -13
- metadata +105 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d9678c52393c842e801f8e3c306eb6976049e7d9
|
4
|
+
data.tar.gz: 6732bf5a03a47223ac78300ff338d71df796047c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 97d814ccc5329e44a534213a6100a8f485e4c2c59eac762cc6aa1b5ccabd79c2cb0707f4d82383fcd5d29fb914836cdbfd0f2af9ad5ddbd900a68824af6022e5
|
7
|
+
data.tar.gz: de492742715aaf17fbcd5a57cd4a626469264a6a1af95d7bc5808b58007d28aa02ee9861643affefd33b7d5fe89547c9fc55e08b3b14cf9be6767599230b99b1
|
data/bin/stax
CHANGED
data/lib/stax.rb
CHANGED
@@ -1,5 +1,35 @@
|
|
1
|
-
require '
|
2
|
-
|
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/
|
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'
|
data/lib/stax/asg.rb
ADDED
@@ -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
|
data/lib/stax/aws/alb.rb
ADDED
@@ -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
|
data/lib/stax/aws/asg.rb
ADDED
@@ -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
|
data/lib/stax/aws/cfn.rb
ADDED
@@ -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
|
data/lib/stax/aws/ec2.rb
ADDED
@@ -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
|
data/lib/stax/aws/ecr.rb
ADDED
@@ -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
|