stax 0.0.3 → 0.0.4
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.
- checksums.yaml +5 -5
- data/README.md +205 -13
- data/lib/stax.rb +6 -1
- data/lib/stax/aws/asg.rb +1 -0
- data/lib/stax/aws/cfn.rb +64 -3
- data/lib/stax/aws/codebuild.rb +41 -0
- data/lib/stax/aws/codepipeline.rb +44 -0
- data/lib/stax/aws/dynamodb.rb +10 -0
- data/lib/stax/aws/ec2.rb +11 -0
- data/lib/stax/aws/ecr.rb +17 -0
- data/lib/stax/aws/ecs.rb +5 -5
- data/lib/stax/aws/route53.rb +51 -0
- data/lib/stax/base.rb +18 -0
- data/lib/stax/cfer.rb +43 -33
- data/lib/stax/cli.rb +3 -5
- data/lib/stax/meta.rb +18 -0
- data/lib/stax/mixin/asg.rb +1 -1
- data/lib/stax/mixin/codebuild.rb +98 -0
- data/lib/stax/mixin/codepipeline.rb +125 -0
- data/lib/stax/mixin/dynamodb.rb +17 -1
- data/lib/stax/mixin/ec2.rb +6 -1
- data/lib/stax/mixin/ecr.rb +68 -0
- data/lib/stax/mixin/ecs.rb +98 -33
- data/lib/stax/mixin/ecs/deploy.rb +49 -0
- data/lib/stax/mixin/logs.rb +73 -2
- data/lib/stax/stack.rb +8 -6
- data/lib/stax/stack/cfn.rb +49 -8
- data/lib/stax/stack/changeset.rb +88 -0
- data/lib/stax/stack/crud.rb +143 -26
- data/lib/stax/stack/imports.rb +34 -0
- data/lib/stax/stack/outputs.rb +4 -2
- data/lib/stax/stack/parameters.rb +1 -1
- data/lib/stax/stack/resources.rb +3 -3
- data/lib/stax/staxfile.rb +14 -4
- data/lib/stax/version.rb +1 -1
- metadata +12 -4
- data/lib/stax/asg.rb +0 -140
data/lib/stax/mixin/dynamodb.rb
CHANGED
@@ -40,7 +40,9 @@ module Stax
|
|
40
40
|
def tables
|
41
41
|
print_table stack_tables.map { |r|
|
42
42
|
t = Aws::DynamoDB.table(r.physical_resource_id)
|
43
|
-
|
43
|
+
g = Aws::DynamoDB.global_table(r.physical_resource_id)
|
44
|
+
regions = g.nil? ? '-' : g.replication_group.map(&:region_name).sort.join(',')
|
45
|
+
[ t.table_name, color(t.table_status, COLORS), t.item_count, t.table_size_bytes, t.creation_date_time, regions ]
|
44
46
|
}
|
45
47
|
end
|
46
48
|
|
@@ -94,6 +96,20 @@ module Stax
|
|
94
96
|
)
|
95
97
|
end
|
96
98
|
|
99
|
+
desc 'put ID', 'put items from stdin to table'
|
100
|
+
method_option :verbose, aliases: '-v', type: :boolean, default: false, desc: 'show progress'
|
101
|
+
def put(id)
|
102
|
+
table = my.resource(id)
|
103
|
+
count = 0
|
104
|
+
$stdin.each do |line|
|
105
|
+
Aws::DynamoDB.put(table_name: table, item: JSON.parse(line))
|
106
|
+
print '.' if options[:verbose]
|
107
|
+
count += 1
|
108
|
+
end
|
109
|
+
print "\n" if options[:verbose]
|
110
|
+
puts "put #{count} items to #{table}"
|
111
|
+
end
|
112
|
+
|
97
113
|
end
|
98
114
|
end
|
99
115
|
end
|
data/lib/stax/mixin/ec2.rb
CHANGED
@@ -11,9 +11,14 @@ module Stax
|
|
11
11
|
module Cmd
|
12
12
|
class Ec2 < SubCommand
|
13
13
|
COLORS = {
|
14
|
+
## instances
|
14
15
|
running: :green,
|
15
16
|
stopped: :yellow,
|
16
17
|
terminated: :red,
|
18
|
+
## images
|
19
|
+
available: :green,
|
20
|
+
pending: :yellow,
|
21
|
+
failed: :red,
|
17
22
|
}
|
18
23
|
|
19
24
|
desc 'ls', 'list instances for stack'
|
@@ -31,7 +36,7 @@ module Stax
|
|
31
36
|
]
|
32
37
|
}
|
33
38
|
end
|
34
|
-
end
|
35
39
|
|
40
|
+
end
|
36
41
|
end
|
37
42
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'stax/aws/ecr'
|
3
|
+
|
4
|
+
module Stax
|
5
|
+
module Ecr
|
6
|
+
def self.included(thor)
|
7
|
+
thor.desc(:ecr, 'ECR subcommands')
|
8
|
+
thor.subcommand(:ecr, Cmd::Ecr)
|
9
|
+
end
|
10
|
+
|
11
|
+
def ecr_registry
|
12
|
+
@_ecr_registry ||= "#{aws_account_id}.dkr.ecr.#{aws_region}.amazonaws.com"
|
13
|
+
end
|
14
|
+
|
15
|
+
def ecr_repositories
|
16
|
+
@_ecr_repositories ||= Aws::Cfn.resources_by_type(stack_name, 'AWS::ECR::Repository')
|
17
|
+
end
|
18
|
+
|
19
|
+
def ecr_repository_names
|
20
|
+
@_ecr_repository_names ||= ecr_repositories.map(&:physical_resource_id)
|
21
|
+
end
|
22
|
+
|
23
|
+
## override to set an explicit repo name
|
24
|
+
def ecr_repository_name
|
25
|
+
@_ecr_repository_name ||= (ecr_repository_names&.first || app_name)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module Cmd
|
30
|
+
class Ecr < SubCommand
|
31
|
+
|
32
|
+
desc 'registry', 'show ECR registry'
|
33
|
+
def registry
|
34
|
+
puts my.ecr_registry
|
35
|
+
end
|
36
|
+
|
37
|
+
## TODO: reimplement using --password-stdin
|
38
|
+
desc 'login', 'login to ECR registry'
|
39
|
+
def login
|
40
|
+
Aws::Ecr.auth.each do |auth|
|
41
|
+
debug("Login to ECR registry #{auth.proxy_endpoint}")
|
42
|
+
user, pass = Base64.decode64(auth.authorization_token).split(':')
|
43
|
+
system "docker login -u #{user} -p #{pass} #{auth.proxy_endpoint}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
desc 'repositories', 'list ECR repositories'
|
48
|
+
def repositories
|
49
|
+
print_table Aws::Ecr.repositories(repository_names: my.ecr_repository_names).map { |r|
|
50
|
+
[r.repository_name, r.repository_uri, r.created_at]
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
desc 'images', 'list ECR images'
|
55
|
+
method_option :repositories, aliases: '-r', type: :array, default: nil, desc: 'list of repos'
|
56
|
+
def images
|
57
|
+
(options[:repositories] || my.ecr_repository_names).each do |repo|
|
58
|
+
debug("Images in repo #{repo}")
|
59
|
+
print_table Aws::Ecr.images(repository_name: repo).map { |i|
|
60
|
+
[i.image_tags.join(' '), i.image_digest, human_bytes(i.image_size_in_bytes), i.image_pushed_at]
|
61
|
+
}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
data/lib/stax/mixin/ecs.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'stax/aws/ecs'
|
2
|
+
require_relative 'ecs/deploy'
|
2
3
|
|
3
4
|
module Stax
|
4
5
|
module Ecs
|
@@ -7,8 +8,12 @@ module Stax
|
|
7
8
|
thor.subcommand(:ecs, Cmd::Ecs)
|
8
9
|
end
|
9
10
|
|
11
|
+
def ecs_clusters
|
12
|
+
@_ecs_clusters ||= Aws::Cfn.resources_by_type(stack_name, 'AWS::ECS::Cluster')
|
13
|
+
end
|
14
|
+
|
10
15
|
def ecs_cluster_name
|
11
|
-
'default'
|
16
|
+
@_ecs_cluster_name ||= (ecs_clusters&.first&.physical_resource_id || 'default')
|
12
17
|
end
|
13
18
|
|
14
19
|
def ecs_services
|
@@ -19,11 +24,22 @@ module Stax
|
|
19
24
|
@_ecs_task_definitions ||= Aws::Cfn.resources_by_type(stack_name, 'AWS::ECS::TaskDefinition')
|
20
25
|
end
|
21
26
|
|
27
|
+
## mangle taskdef arn into family name
|
28
|
+
def ecs_task_families
|
29
|
+
ecs_task_definitions.map do |r|
|
30
|
+
r.physical_resource_id.split(':')[-2].split('/').last
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def ecs_service_names
|
35
|
+
@_ecs_service_names ||= ecs_services.map(&:physical_resource_id)
|
36
|
+
end
|
37
|
+
|
22
38
|
def ecs_service_objects
|
23
|
-
Aws::Ecs.services(ecs_cluster_name,
|
39
|
+
Aws::Ecs.services(ecs_cluster_name, ecs_service_names)
|
24
40
|
end
|
25
41
|
|
26
|
-
## register a new revision of existing task definition
|
42
|
+
## deprecated: register a new revision of existing task definition
|
27
43
|
def ecs_update_taskdef(id)
|
28
44
|
taskdef = Aws::Ecs.task_definition(resource(id))
|
29
45
|
debug("Registering new revision of #{taskdef.family}")
|
@@ -33,7 +49,7 @@ module Stax
|
|
33
49
|
end
|
34
50
|
end
|
35
51
|
|
36
|
-
## update service to use a new task definition
|
52
|
+
## deprecated: update service to use a new task definition
|
37
53
|
def ecs_update_service(id, taskdef)
|
38
54
|
service_name = resource(id).split('/').last
|
39
55
|
taskdef_name = taskdef.task_definition_arn.split('/').last
|
@@ -42,6 +58,7 @@ module Stax
|
|
42
58
|
puts s.task_definition
|
43
59
|
end
|
44
60
|
end
|
61
|
+
|
45
62
|
end
|
46
63
|
|
47
64
|
module Cmd
|
@@ -57,6 +74,10 @@ module Stax
|
|
57
74
|
def ecs_task_definition(id)
|
58
75
|
Aws::Cfn.id(my.stack_name, id)
|
59
76
|
end
|
77
|
+
|
78
|
+
def print_event(e)
|
79
|
+
puts "#{set_color(e.created_at, :green)} #{e.message}"
|
80
|
+
end
|
60
81
|
end
|
61
82
|
|
62
83
|
desc 'clusters', 'ECS cluster for stack'
|
@@ -84,9 +105,26 @@ module Stax
|
|
84
105
|
def events
|
85
106
|
my.ecs_service_objects.each do |s|
|
86
107
|
debug("Events for #{s.service_name}")
|
87
|
-
|
88
|
-
|
89
|
-
|
108
|
+
s.events.first(options[:number]).reverse.map(&method(:print_event))
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
desc 'tail [SERVICE]', 'tail ECS events'
|
113
|
+
def tail(service = nil)
|
114
|
+
trap('SIGINT', 'EXIT') # clean exit with ctrl-c
|
115
|
+
service ||= my.ecs_service_names.first
|
116
|
+
latest_event = Aws::Ecs.services(my.ecs_cluster_name, [service]).first.events.first
|
117
|
+
print_event(latest_event)
|
118
|
+
last_seen = latest_event.id
|
119
|
+
loop do
|
120
|
+
sleep 5
|
121
|
+
unseen = []
|
122
|
+
Aws::Ecs.services(my.ecs_cluster_name, [service]).first.events.each do |e|
|
123
|
+
break if e.id == last_seen
|
124
|
+
unseen.unshift(e)
|
125
|
+
end
|
126
|
+
unseen.each(&method(:print_event))
|
127
|
+
last_seen = unseen.last.id unless unseen.empty?
|
90
128
|
end
|
91
129
|
end
|
92
130
|
|
@@ -109,39 +147,65 @@ module Stax
|
|
109
147
|
}
|
110
148
|
end
|
111
149
|
|
150
|
+
desc 'env', 'env vars for latest rev of task families'
|
151
|
+
def env
|
152
|
+
my.ecs_task_families.each do |family|
|
153
|
+
Aws::Ecs.task_definition(family).container_definitions.each do |c|
|
154
|
+
debug("Environment for #{family} #{c.name}")
|
155
|
+
print_table c.environment.map { |e|
|
156
|
+
[e.name, e.value]
|
157
|
+
}
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
112
162
|
desc 'tasks', 'ECS tasks for stack'
|
113
163
|
method_option :status, aliases: '-s', type: :string, default: 'RUNNING', desc: 'status to list'
|
114
164
|
def tasks
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
165
|
+
my.ecs_services.each do |s|
|
166
|
+
name = s.physical_resource_id.split('/').last
|
167
|
+
debug("Tasks for service #{name}")
|
168
|
+
Aws::Ecs.tasks(
|
169
|
+
cluster: my.ecs_cluster_name,
|
170
|
+
service_name: s.physical_resource_id,
|
171
|
+
desired_status: options[:status].upcase,
|
172
|
+
).map { |t|
|
173
|
+
[
|
174
|
+
t.task_arn.split('/').last,
|
175
|
+
t.task_definition_arn.split('/').last,
|
176
|
+
t.container_instance_arn&.split('/')&.last || '--',
|
177
|
+
color(t.last_status, COLORS),
|
178
|
+
"(#{t.desired_status})",
|
179
|
+
t.started_by,
|
180
|
+
]
|
181
|
+
}.tap(&method(:print_table))
|
182
|
+
end
|
125
183
|
end
|
126
184
|
|
127
185
|
desc 'containers', 'containers for running tasks'
|
128
186
|
method_option :status, aliases: '-s', type: :string, default: 'RUNNING', desc: 'status to list'
|
129
187
|
def containers
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
[
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
188
|
+
my.ecs_services.each do |s|
|
189
|
+
Aws::Ecs.tasks(
|
190
|
+
cluster: my.ecs_cluster_name,
|
191
|
+
service_name: s.physical_resource_id,
|
192
|
+
desired_status: options[:status].upcase,
|
193
|
+
).each do |t|
|
194
|
+
task = t.task_arn.split('/').last
|
195
|
+
debug("Containers for task #{task}")
|
196
|
+
print_table t.containers.map { |c|
|
197
|
+
[
|
198
|
+
c.name,
|
199
|
+
c.container_arn.split('/').last,
|
200
|
+
color(c.last_status, COLORS),
|
201
|
+
c.network_interfaces.map(&:private_ipv_4_address).join(','),
|
202
|
+
t.task_definition_arn.split('/').last,
|
203
|
+
c.exit_code,
|
204
|
+
c.reason,
|
205
|
+
]
|
206
|
+
}
|
207
|
+
end
|
208
|
+
end
|
145
209
|
end
|
146
210
|
|
147
211
|
desc 'instances', 'ECS instances'
|
@@ -178,8 +242,9 @@ module Stax
|
|
178
242
|
method_option :desired, aliases: '-d', type: :numeric, default: nil, desc: 'desired container count'
|
179
243
|
def scale
|
180
244
|
my.ecs_services.each do |s|
|
181
|
-
debug("Scaling service #{s.
|
245
|
+
debug("Scaling service #{s.physical_resource_id.split('/').last}")
|
182
246
|
Aws::Ecs.update_service(
|
247
|
+
cluster: my.ecs_cluster_name,
|
183
248
|
service: s.physical_resource_id,
|
184
249
|
desired_count: options[:desired],
|
185
250
|
).tap do |s|
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Stax
|
2
|
+
module Ecs
|
3
|
+
|
4
|
+
## convert to hash for registering new taskdef
|
5
|
+
def taskdef_to_hash(taskdef)
|
6
|
+
args = %i[family cpu memory requires_compatibilities task_role_arn execution_role_arn network_mode container_definitions volumes placement_constraints]
|
7
|
+
taskdef.to_hash.slice(*args)
|
8
|
+
end
|
9
|
+
|
10
|
+
def get_taskdef(service)
|
11
|
+
debug("Current task definition for #{service.service_name}")
|
12
|
+
Aws::Ecs.task_definition(service.task_definition).tap do |t|
|
13
|
+
puts t.task_definition_arn
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def register_taskdef(hash)
|
18
|
+
debug("Registering new revision")
|
19
|
+
Aws::Ecs.client.register_task_definition(hash).task_definition.tap do |t|
|
20
|
+
puts t.task_definition_arn
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def update_service(service, taskdef)
|
25
|
+
debug("Updating #{service.service_name} to new revision")
|
26
|
+
Aws::Ecs.update_service(
|
27
|
+
service: service.service_name,
|
28
|
+
task_definition: taskdef.task_definition_arn
|
29
|
+
).tap do |s|
|
30
|
+
puts s.deployments.first.id
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
## update taskdef for a service, triggering a deploy
|
35
|
+
## modify current taskdef in block
|
36
|
+
def ecs_deploy(id, &block)
|
37
|
+
service = Aws::Ecs.services(ecs_cluster_name, [resource(id)]).first
|
38
|
+
taskdef = get_taskdef(service)
|
39
|
+
|
40
|
+
## convert to a hash and modify in block
|
41
|
+
hash = taskdef_to_hash(taskdef)
|
42
|
+
yield(hash) if block_given?
|
43
|
+
|
44
|
+
taskdef = register_taskdef(hash)
|
45
|
+
update_service(service, taskdef)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
data/lib/stax/mixin/logs.rb
CHANGED
@@ -62,12 +62,12 @@ module Stax
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
|
-
desc '
|
65
|
+
desc 'events [STREAM]', 'show events just from latest/given log stream'
|
66
66
|
method_option :group, aliases: '-g', type: :string, default: nil, desc: 'log group to tail'
|
67
67
|
method_option :numlines, aliases: '-n', type: :numeric, default: 10, desc: 'number of lines to show'
|
68
68
|
method_option :follow, aliases: '-f', type: :boolean, default: false, desc: 'follow log output'
|
69
69
|
method_option :sleep, aliases: '-s', type: :numeric, default: 1, desc: 'seconds to sleep between poll for new data'
|
70
|
-
def
|
70
|
+
def events(stream = nil)
|
71
71
|
trap('SIGINT', 'EXIT') # clean exit with ctrl-c
|
72
72
|
group = ((g = options[:group]) ? log_groups[g] : log_groups.values.first).log_group_name
|
73
73
|
stream ||= latest_stream(group).log_stream_name
|
@@ -89,6 +89,77 @@ module Stax
|
|
89
89
|
end
|
90
90
|
end
|
91
91
|
|
92
|
+
desc 'tail', 'tail all events from log group'
|
93
|
+
method_option :group, aliases: '-g', type: :string, default: nil, desc: 'log group to tail'
|
94
|
+
method_option :streams, aliases: '-s', type: :array, default: nil, desc: 'limit to given streams'
|
95
|
+
def tail
|
96
|
+
trap('SIGINT', 'EXIT') # clean exit with ctrl-c
|
97
|
+
group = ((g = options[:group]) ? log_groups[g] : log_groups.values.first).log_group_name
|
98
|
+
|
99
|
+
debug("Log group #{group}")
|
100
|
+
token = nil
|
101
|
+
start_time = Time.now.to_i - 30 # start 30 sec ago
|
102
|
+
|
103
|
+
loop do
|
104
|
+
end_time = Time.now.to_i
|
105
|
+
resp = Aws::Logs.client.filter_log_events(
|
106
|
+
log_group_name: group,
|
107
|
+
log_stream_names: options[:streams],
|
108
|
+
start_time: start_time * 1000, # aws needs msec
|
109
|
+
end_time: end_time * 1000,
|
110
|
+
next_token: token,
|
111
|
+
interleaved: true,
|
112
|
+
)
|
113
|
+
|
114
|
+
## pretty-print the events
|
115
|
+
resp.events.each do |e|
|
116
|
+
puts("#{set_color(human_time(e.timestamp).utc, :green)} #{set_color(e.log_stream_name, :blue)} #{e.message}")
|
117
|
+
end
|
118
|
+
|
119
|
+
## token means more data available from this request, so loop and get it right away
|
120
|
+
token = resp.next_token
|
121
|
+
|
122
|
+
## no token, so sleep and start next request from end time of this one
|
123
|
+
unless token
|
124
|
+
start_time = end_time
|
125
|
+
sleep 10
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
desc 'filter', 'filter events from log group'
|
131
|
+
method_option :group, aliases: '-g', type: :string, default: nil, desc: 'log group to filter'
|
132
|
+
method_option :streams, aliases: '-s', type: :array, default: nil, desc: 'limit to given streams'
|
133
|
+
method_option :pattern, aliases: '-p', type: :string, default: nil, desc: 'pattern to filter logs'
|
134
|
+
method_option :start, aliases: '-t', type: :string, default: nil, desc: 'start time'
|
135
|
+
method_option :end, aliases: '-e', type: :string, default: nil, desc: 'end time'
|
136
|
+
def filter
|
137
|
+
trap('SIGINT', 'EXIT') # clean exit with ctrl-c
|
138
|
+
group = ((g = options[:group]) ? log_groups[g] : log_groups.values.first).log_group_name
|
139
|
+
debug("Log group #{group}")
|
140
|
+
|
141
|
+
start_time = options[:start] ? Time.parse(options[:start]).to_i*1000 : nil
|
142
|
+
end_time = options[:end] ? Time.parse(options[:end]).to_i*1000 : nil
|
143
|
+
token = nil
|
144
|
+
loop do
|
145
|
+
resp = Aws::Logs.client.filter_log_events(
|
146
|
+
log_group_name: group,
|
147
|
+
log_stream_names: options[:streams],
|
148
|
+
next_token: token,
|
149
|
+
start_time: start_time,
|
150
|
+
end_time: end_time,
|
151
|
+
filter_pattern: options[:pattern],
|
152
|
+
)
|
153
|
+
resp.events.each do |e|
|
154
|
+
time = set_color(human_time(e.timestamp).utc, :green)
|
155
|
+
stream = set_color(e.log_stream_name, :blue)
|
156
|
+
puts("#{time} #{stream} #{e.message}")
|
157
|
+
end
|
158
|
+
token = resp.next_token
|
159
|
+
break unless token
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
92
163
|
## lambdas create their own log groups, and when we delete stack they are left behind;
|
93
164
|
## this task looks up their names by stack prefix, and deletes them
|
94
165
|
desc 'cleanup', 'cleanup lambda log groups named for stack'
|