stax 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- [ t.table_name, color(t.table_status, COLORS), t.item_count, t.table_size_bytes, t.creation_date_time ]
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
@@ -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
@@ -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, ecs_services.map(&:physical_resource_id))
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
- print_table s.events.first(options[:number]).map { |e|
88
- [set_color(e.created_at, :green), e.message]
89
- }.reverse
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
- print_table Aws::Ecs.tasks(my.ecs_cluster_name, options[:status].upcase).map { |t|
116
- [
117
- t.task_arn.split('/').last,
118
- t.task_definition_arn.split('/').last,
119
- t.container_instance_arn&.split('/')&.last || '--',
120
- color(t.last_status, COLORS),
121
- "(#{t.desired_status})",
122
- t.started_by,
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
- debug("Containers for cluster #{my.ecs_cluster_name}")
131
- print_table Aws::Ecs.tasks(my.ecs_cluster_name, options[:status].upcase).map { |task|
132
- task_defn = task.task_definition_arn.split('/').last
133
- task.containers.map { |c|
134
- [
135
- c.container_arn.split('/').last,
136
- c.name,
137
- color(c.last_status, COLORS),
138
- c.network_interfaces.map(&:private_ipv_4_address).join(','),
139
- task_defn,
140
- c.exit_code,
141
- c.reason,
142
- ]
143
- }
144
- }.flatten(1)
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.logical_resource_id}")
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
@@ -62,12 +62,12 @@ module Stax
62
62
  end
63
63
  end
64
64
 
65
- desc 'tail [STREAM]', 'tail latest/given log stream'
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 tail(stream = nil)
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'