stax 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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/aws/dynamodb.rb
CHANGED
@@ -14,6 +14,12 @@ module Stax
|
|
14
14
|
client.describe_table(table_name: name).table
|
15
15
|
end
|
16
16
|
|
17
|
+
def global_table(name)
|
18
|
+
client.describe_global_table(global_table_name: name)&.global_table_description
|
19
|
+
rescue ::Aws::DynamoDB::Errors::GlobalTableNotFoundException
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
|
17
23
|
def gsi(name)
|
18
24
|
client.describe_table(table_name: name).table.global_secondary_indexes || []
|
19
25
|
end
|
@@ -71,6 +77,10 @@ module Stax
|
|
71
77
|
end
|
72
78
|
end
|
73
79
|
|
80
|
+
def put(opt)
|
81
|
+
client.put_item(opt)
|
82
|
+
end
|
83
|
+
|
74
84
|
def list_backups(opt = {})
|
75
85
|
last_arn = nil
|
76
86
|
backups = []
|
data/lib/stax/aws/ec2.rb
CHANGED
@@ -16,7 +16,18 @@ module Stax
|
|
16
16
|
end.map(&:instances).flatten
|
17
17
|
end
|
18
18
|
|
19
|
+
## list AMIs
|
20
|
+
def images(opt = {})
|
21
|
+
client.describe_images(opt).images.sort_by(&:creation_date)
|
22
|
+
end
|
23
|
+
|
24
|
+
## tag AMIs
|
25
|
+
def create_tags(opt)
|
26
|
+
client.create_tags(opt)
|
27
|
+
end
|
28
|
+
|
19
29
|
end
|
30
|
+
|
20
31
|
end
|
21
32
|
end
|
22
33
|
end
|
data/lib/stax/aws/ecr.rb
CHANGED
@@ -14,10 +14,27 @@ module Stax
|
|
14
14
|
client.get_authorization_token.authorization_data
|
15
15
|
end
|
16
16
|
|
17
|
+
def repositories(opt = {})
|
18
|
+
paginate(:repositories) do |next_token|
|
19
|
+
client.describe_repositories(opt.merge(next_token: next_token))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
17
23
|
def exists?(repo, tag)
|
18
24
|
!client.batch_get_image(repository_name: repo, image_ids: [{image_tag: tag}]).images.empty?
|
19
25
|
end
|
20
26
|
|
27
|
+
def login(*registry_ids)
|
28
|
+
ids = registry_ids.empty? ? nil : Array(registry_ids)
|
29
|
+
client.get_authorization_token(registry_ids: ids).authorization_data
|
30
|
+
end
|
31
|
+
|
32
|
+
def images(opt = {})
|
33
|
+
paginate(:image_details) do |next_token|
|
34
|
+
client.describe_images(opt.merge(next_token: next_token))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
21
38
|
end
|
22
39
|
|
23
40
|
end
|
data/lib/stax/aws/ecs.rb
CHANGED
@@ -24,18 +24,18 @@ module Stax
|
|
24
24
|
client.describe_task_definition(task_definition: name).task_definition
|
25
25
|
end
|
26
26
|
|
27
|
-
def list_tasks(
|
27
|
+
def list_tasks(opt)
|
28
28
|
paginate(:task_arns) do |token|
|
29
|
-
client.list_tasks(
|
29
|
+
client.list_tasks(opt.merge(next_token: token))
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
-
def tasks(
|
34
|
-
tasks = list_tasks(
|
33
|
+
def tasks(opt = {})
|
34
|
+
tasks = list_tasks(opt)
|
35
35
|
if tasks.empty?
|
36
36
|
[]
|
37
37
|
else
|
38
|
-
client.describe_tasks(cluster: cluster, tasks: tasks).tasks
|
38
|
+
client.describe_tasks(cluster: opt[:cluster], tasks: tasks).tasks
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Stax
|
2
|
+
module Aws
|
3
|
+
class Route53 < Sdk
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def client
|
8
|
+
@_client ||= ::Aws::Route53::Client.new
|
9
|
+
end
|
10
|
+
|
11
|
+
## list all zones
|
12
|
+
def zones
|
13
|
+
client.list_hosted_zones.hosted_zones
|
14
|
+
end
|
15
|
+
|
16
|
+
## list limited number of zones, starting at named zone
|
17
|
+
def zones_by_name(name, max_items = nil)
|
18
|
+
client.list_hosted_zones_by_name(
|
19
|
+
dns_name: name,
|
20
|
+
max_items: max_items,
|
21
|
+
)&.hosted_zones
|
22
|
+
end
|
23
|
+
|
24
|
+
## get single matching zone, or nil
|
25
|
+
def zone_by_name(name)
|
26
|
+
zones_by_name(name, 1).find do |zone|
|
27
|
+
zone.name == name
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
## record sets for named zone
|
32
|
+
def record_sets(opt = {})
|
33
|
+
client.list_resource_record_sets(opt)&.resource_record_sets
|
34
|
+
end
|
35
|
+
|
36
|
+
def record(name, type = :A)
|
37
|
+
zone = name.split('.').last(2).join('.') + '.'
|
38
|
+
Aws::Route53.record_sets(
|
39
|
+
hosted_zone_id: zone_by_name(zone).id,
|
40
|
+
start_record_name: name,
|
41
|
+
start_record_type: type,
|
42
|
+
).select do |record|
|
43
|
+
(record.name == name) && (record.type == type.to_s)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/stax/base.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'stax/aws/sts'
|
2
|
+
|
1
3
|
module Stax
|
2
4
|
class Base < Thor
|
3
5
|
|
@@ -14,6 +16,14 @@ module Stax
|
|
14
16
|
@_stack_prefix ||= [app_name, branch_name].compact.join('-') + '-'
|
15
17
|
end
|
16
18
|
|
19
|
+
def aws_account_id
|
20
|
+
@_aws_account_id ||= Aws::Sts.id.account
|
21
|
+
end
|
22
|
+
|
23
|
+
def aws_region
|
24
|
+
@_aws_region ||= ENV['AWS_REGION']
|
25
|
+
end
|
26
|
+
|
17
27
|
## find or create a stack object
|
18
28
|
def stack(id)
|
19
29
|
object = Stax.const_get(id.to_s.capitalize)
|
@@ -104,6 +114,14 @@ module Stax
|
|
104
114
|
timestamp.nil? ? '-' : Time.at(timestamp.to_i/1000)
|
105
115
|
end
|
106
116
|
|
117
|
+
## convert a diff in seconds to d h m s
|
118
|
+
def human_time_diff(t, n = 5)
|
119
|
+
mm, ss = t.divmod(60)
|
120
|
+
hh, mm = mm.divmod(60)
|
121
|
+
dd, hh = hh.divmod(24)
|
122
|
+
{d: dd, h: hh, m: mm, s: ss}.reject{ |_,v| v == 0 }.map{ |k,v| "#{v.round}#{k}" }.first(n).join
|
123
|
+
end
|
124
|
+
|
107
125
|
## convert bytes to nearest unit
|
108
126
|
def human_bytes(bytes, precision = 0)
|
109
127
|
return 0.to_s if bytes < 1
|
data/lib/stax/cfer.rb
CHANGED
@@ -1,18 +1,45 @@
|
|
1
1
|
require 'cfer'
|
2
2
|
|
3
|
+
## TODO: remove these hacks once merged and released in upstream cfer
|
4
|
+
## see cfer PRs: #52, #54
|
5
|
+
module Cfer::Core::Functions
|
6
|
+
def get_azs(region = '')
|
7
|
+
{"Fn::GetAZs" => region}
|
8
|
+
end
|
9
|
+
|
10
|
+
def cidr(ip_block, count, size_mask)
|
11
|
+
{"Fn::Cidr" => [ip_block, count, size_mask]}
|
12
|
+
end
|
13
|
+
|
14
|
+
def import_value(value)
|
15
|
+
{"Fn::ImportValue" => value}
|
16
|
+
end
|
17
|
+
|
18
|
+
def split(*args)
|
19
|
+
{"Fn::Split" => [ *args ].flatten }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
## see cfer PR: #56
|
24
|
+
module Cfer::Core
|
25
|
+
class Stack < Cfer::Block
|
26
|
+
def output(name, value, options = {})
|
27
|
+
opt = options.each_with_object({}) { |(k,v),h| h[k.to_s.capitalize] = v } # capitalize all keys
|
28
|
+
export = opt.has_key?('Export') ? {'Name' => opt['Export']} : nil
|
29
|
+
self[:Outputs][name] = opt.merge('Value' => value, 'Export' => export).compact
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
3
34
|
module Stax
|
4
35
|
class Stack < Base
|
5
36
|
class_option :use_previous_value, aliases: '-u', type: :array, default: [], desc: 'params to use previous value'
|
6
37
|
|
7
38
|
no_commands do
|
8
39
|
|
40
|
+
## backward-compatibility
|
9
41
|
def cfer_parameters
|
10
|
-
|
11
|
-
end
|
12
|
-
|
13
|
-
## location of cfer template file
|
14
|
-
def cfer_template
|
15
|
-
File.join('cf', "#{class_name}.rb")
|
42
|
+
cfn_parameters
|
16
43
|
end
|
17
44
|
|
18
45
|
## override with S3 bucket for upload of large templates as needed
|
@@ -29,37 +56,20 @@ module Stax
|
|
29
56
|
false
|
30
57
|
end
|
31
58
|
|
32
|
-
##
|
33
|
-
def
|
34
|
-
|
35
|
-
parameters: stringify_keys(cfer_parameters).except(*options[:use_previous_value]),
|
36
|
-
template: cfer_template,
|
37
|
-
follow: true,
|
38
|
-
number: 1,
|
39
|
-
s3_path: cfer_s3_path,
|
40
|
-
notification_arns: cfer_notification_arns,
|
41
|
-
enable_termination_protection: cfer_termination_protection,
|
42
|
-
}
|
43
|
-
Cfer.converge!(stack_name, opts.merge(args))
|
44
|
-
end
|
45
|
-
|
46
|
-
## generate JSON for stack without sending to cloudformation
|
47
|
-
def cfer_generate
|
48
|
-
opts = {parameters: stringify_keys(cfer_parameters)}
|
49
|
-
Cfer.generate!(cfer_template, opts)
|
59
|
+
## location of template file
|
60
|
+
def cfn_template_path
|
61
|
+
File.join('cf', "#{class_name}.rb")
|
50
62
|
end
|
51
63
|
|
52
|
-
|
53
|
-
|
54
|
-
capture_stdout do
|
55
|
-
cfer_generate
|
56
|
-
end
|
64
|
+
def cfer_client
|
65
|
+
@_cfer_client ||= Cfer::Cfn::Client.new({})
|
57
66
|
end
|
58
67
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
68
|
+
## generate JSON for stack without sending to cloudformation
|
69
|
+
def cfer_generate
|
70
|
+
Cfer::stack_from_file(cfn_template_path, client: cfer_client, parameters: stringify_keys(cfn_parameters)).to_json
|
71
|
+
rescue Cfer::Util::FileDoesNotExistError => e
|
72
|
+
fail_task(e.message)
|
63
73
|
end
|
64
74
|
|
65
75
|
## temporarily grab stdout to a string
|
data/lib/stax/cli.rb
CHANGED
@@ -2,8 +2,6 @@ require 'stax/aws/cfn'
|
|
2
2
|
|
3
3
|
module Stax
|
4
4
|
class Cli < Base
|
5
|
-
include Aws
|
6
|
-
|
7
5
|
class_option :branch, type: :string, default: Git.branch, desc: 'git branch to use'
|
8
6
|
class_option :app, type: :string, default: File.basename(Git.toplevel), desc: 'application name'
|
9
7
|
|
@@ -14,11 +12,11 @@ module Stax
|
|
14
12
|
|
15
13
|
desc 'ls', 'list stacks for this branch'
|
16
14
|
def ls
|
17
|
-
print_table Cfn.stacks.select { |s|
|
15
|
+
print_table Aws::Cfn.stacks.select { |s|
|
18
16
|
s.stack_name.start_with?(stack_prefix)
|
19
17
|
}.map { |s|
|
20
|
-
[s.stack_name, s.creation_time, color(s.stack_status, Cfn::COLORS), s.template_description]
|
21
|
-
}
|
18
|
+
[s.stack_name, s.creation_time, color(s.stack_status, Aws::Cfn::COLORS), s.template_description]
|
19
|
+
}.sort
|
22
20
|
end
|
23
21
|
|
24
22
|
end
|
data/lib/stax/meta.rb
CHANGED
@@ -24,6 +24,24 @@ module Stax
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
+
desc 'update', 'meta update task'
|
28
|
+
def update
|
29
|
+
stack_objects.each do |s|
|
30
|
+
if s.exists? && y_or_n?("Update #{s.stack_name}?", :yellow)
|
31
|
+
s.update
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
desc 'change', 'meta change task'
|
37
|
+
def change
|
38
|
+
stack_objects.each do |s|
|
39
|
+
if s.exists?
|
40
|
+
s.change
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
27
45
|
desc 'delete', 'meta delete task'
|
28
46
|
def delete
|
29
47
|
stack_objects.reverse.each do |s|
|
data/lib/stax/mixin/asg.rb
CHANGED
@@ -102,7 +102,7 @@ module Stax
|
|
102
102
|
method_option :min_size, aliases: '-m', type: :numeric, default: nil, desc: 'set minimum capacity'
|
103
103
|
method_option :max_size, aliases: '-M', type: :numeric, default: nil, desc: 'set maximum capacity'
|
104
104
|
def scale
|
105
|
-
opt = options.slice(
|
105
|
+
opt = options.slice(*%w[desired_capacity min_size max_size])
|
106
106
|
fail_task('No change requested') if opt.empty?
|
107
107
|
stack_asgs.each do |a|
|
108
108
|
debug("Scaling to #{opt} for #{a.logical_resource_id} #{a.physical_resource_id}")
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'stax/aws/codebuild'
|
2
|
+
|
3
|
+
module Stax
|
4
|
+
module Codebuild
|
5
|
+
def self.included(thor)
|
6
|
+
thor.desc(:codebuild, 'Codebuild subcommands')
|
7
|
+
thor.subcommand(:codebuild, Cmd::Codebuild)
|
8
|
+
end
|
9
|
+
|
10
|
+
def stack_projects
|
11
|
+
@_stack_projects ||= Aws::Cfn.resources_by_type(stack_name, 'AWS::CodeBuild::Project')
|
12
|
+
end
|
13
|
+
|
14
|
+
def stack_project_names
|
15
|
+
@_stack_project_names ||= stack_projects.map(&:physical_resource_id)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module Cmd
|
20
|
+
class Codebuild < SubCommand
|
21
|
+
COLORS = {
|
22
|
+
SUCCEEDED: :green,
|
23
|
+
FAILED: :red,
|
24
|
+
FAULT: :red,
|
25
|
+
CLIENT_ERROR: :red,
|
26
|
+
STOPPED: :red,
|
27
|
+
}
|
28
|
+
|
29
|
+
no_commands do
|
30
|
+
def print_phase(p)
|
31
|
+
duration = (d = p.duration_in_seconds) ? "#{d}s" : ''
|
32
|
+
status = p.phase_status || (p.phase_type == 'COMPLETED' ? '' : 'in progress')
|
33
|
+
puts "%-16s %-12s %4s %s" % [p.phase_type, color(status, COLORS), duration, p.end_time]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
desc 'projects', 'list projects'
|
38
|
+
def projects
|
39
|
+
print_table Aws::Codebuild.projects(my.stack_project_names).map { |p|
|
40
|
+
[p.name, p.source.location, p.environment.image, p.environment.compute_type, p.last_modified]
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
desc 'builds', 'list builds for stack projects'
|
45
|
+
method_option :number, aliases: '-n', type: :numeric, default: 10, desc: 'number of builds to list'
|
46
|
+
def builds
|
47
|
+
my.stack_project_names.each do |project|
|
48
|
+
debug("Builds for #{project}")
|
49
|
+
ids = Aws::Codebuild.builds_for_project(project, options[:number])
|
50
|
+
print_table Aws::Codebuild.builds(ids).map { |b|
|
51
|
+
duration = human_time_diff(b.end_time - b.start_time)
|
52
|
+
[b.id, b.initiator, color(b.build_status, COLORS), duration, b.end_time]
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
desc 'phases [ID]', 'show build phases for given or most recent build'
|
58
|
+
def phases(id = nil)
|
59
|
+
id ||= Aws::Codebuild.builds_for_project(my.stack_project_names.first, 1).first
|
60
|
+
debug("Phases for build #{id}")
|
61
|
+
Aws::Codebuild.builds([id]).first.phases.each(&method(:print_phase))
|
62
|
+
end
|
63
|
+
|
64
|
+
desc 'tail [ID]', 'tail build phases for build'
|
65
|
+
def tail(id = nil)
|
66
|
+
trap('SIGINT', 'EXIT') # clean exit with ctrl-c
|
67
|
+
id ||= Aws::Codebuild.builds_for_project(my.stack_project_names.first, 1).first
|
68
|
+
debug("Phases for build #{id}")
|
69
|
+
seen = {}
|
70
|
+
loop do
|
71
|
+
(Aws::Codebuild.builds([id]).first.phases || []).each do |p|
|
72
|
+
i = p.phase_type + p.phase_status.to_s
|
73
|
+
print_phase(p) unless seen[i]
|
74
|
+
seen[i] = true
|
75
|
+
end
|
76
|
+
break if seen['COMPLETED']
|
77
|
+
sleep(3)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
desc 'start', 'start a build'
|
82
|
+
method_option :project, type: :string, default: nil, desc: 'project to build'
|
83
|
+
method_option :version, type: :string, default: nil, desc: 'source version to build (sha/branch/tag)'
|
84
|
+
def start
|
85
|
+
project = options[:project] || my.stack_project_names.first
|
86
|
+
version = options[:version] || Git.sha
|
87
|
+
debug("Starting build for #{project} #{version}")
|
88
|
+
build = Aws::Codebuild.start(
|
89
|
+
project_name: project,
|
90
|
+
source_version: version,
|
91
|
+
)
|
92
|
+
puts build.id
|
93
|
+
tail build.id
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'stax/aws/codepipeline'
|
2
|
+
|
3
|
+
module Stax
|
4
|
+
module Codepipeline
|
5
|
+
def self.included(thor)
|
6
|
+
thor.desc(:codepipeline, 'Codepipeline subcommands')
|
7
|
+
thor.subcommand(:codepipeline, Cmd::Codepipeline)
|
8
|
+
end
|
9
|
+
|
10
|
+
def stack_pipelines
|
11
|
+
@_stack_pipelines ||= Aws::Cfn.resources_by_type(stack_name, 'AWS::CodePipeline::Pipeline')
|
12
|
+
end
|
13
|
+
|
14
|
+
def stack_pipeline_names
|
15
|
+
@_stack_pipeline_names ||= stack_pipelines.map(&:physical_resource_id)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module Cmd
|
20
|
+
class Codepipeline < SubCommand
|
21
|
+
COLORS = {
|
22
|
+
Succeeded: :green,
|
23
|
+
Failed: :red,
|
24
|
+
}
|
25
|
+
|
26
|
+
desc 'stages', 'list pipeline stages'
|
27
|
+
def stages
|
28
|
+
my.stack_pipeline_names.each do |name|
|
29
|
+
debug("Stages for #{name}")
|
30
|
+
print_table Aws::Codepipeline.stages(name).map { |s|
|
31
|
+
actions = s.actions.map{ |a| a&.action_type_id&.provider }.join(' ')
|
32
|
+
[s.name, actions]
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
desc 'history', 'pipeline execution history'
|
38
|
+
method_option :number, aliases: '-n', type: :numeric, default: 10, desc: 'number of items'
|
39
|
+
def history
|
40
|
+
my.stack_pipeline_names.each do |name|
|
41
|
+
debug("Execution history for #{name}")
|
42
|
+
print_table Aws::Codepipeline.executions(name, options[:number]).map { |e|
|
43
|
+
r = Aws::Codepipeline.execution(name, e.pipeline_execution_id)&.artifact_revisions&.first
|
44
|
+
age = human_time_diff(Time.now - e.last_update_time, 1)
|
45
|
+
duration = human_time_diff(e.last_update_time - e.start_time)
|
46
|
+
[e.pipeline_execution_id, color(e.status, COLORS), "#{age} ago", duration, r&.revision_id&.slice(0,7) + ':' + r&.revision_summary]
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
desc 'state', 'pipeline state'
|
52
|
+
def state
|
53
|
+
my.stack_pipeline_names.each do |name|
|
54
|
+
state = Aws::Codepipeline.state(name)
|
55
|
+
debug("State for #{name} at #{state.updated}")
|
56
|
+
print_table state.stage_states.map { |s|
|
57
|
+
s.action_states.map { |a|
|
58
|
+
l = a.latest_execution
|
59
|
+
percent = (l&.percent_complete || 100).to_s + '%'
|
60
|
+
sha = a.current_revision&.revision_id&.slice(0,7)
|
61
|
+
ago = (t = l&.last_status_change) ? human_time_diff(Time.now - t, 1) : '?'
|
62
|
+
[s.stage_name, a.action_name, color(l&.status || '', COLORS), percent, "#{ago} ago", (sha || l&.token), l&.error_details&.message]
|
63
|
+
}
|
64
|
+
}.flatten(1)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
desc 'approvals', 'approve or reject pending approvals'
|
69
|
+
method_option :approved, type: :boolean, default: false, desc: 'approve the request'
|
70
|
+
method_option :rejected, type: :boolean, default: false, desc: 'reject the request'
|
71
|
+
def approvals
|
72
|
+
my.stack_pipeline_names.each do |name|
|
73
|
+
debug("Pending approvals for #{name}")
|
74
|
+
Aws::Codepipeline.state(name).stage_states.each do |s|
|
75
|
+
s.action_states.each do |a|
|
76
|
+
next unless (a.latest_execution&.token && a.latest_execution&.status == 'InProgress')
|
77
|
+
l = a.latest_execution
|
78
|
+
ago = (t = l&.last_status_change) ? human_time_diff(Time.now - t, 1) : '?'
|
79
|
+
puts "#{a.action_name} #{l&.token} #{ago} ago"
|
80
|
+
resp = (options[:approved] && :approved) || (options[:rejected] && :rejected) || ask('approved,rejected,[skip]?', :yellow)
|
81
|
+
status = resp.to_s.capitalize
|
82
|
+
if (status == 'Rejected') || (status == 'Approved')
|
83
|
+
Aws::Codepipeline.client.put_approval_result(
|
84
|
+
pipeline_name: name,
|
85
|
+
stage_name: s.stage_name,
|
86
|
+
action_name: a.action_name,
|
87
|
+
token: l.token,
|
88
|
+
result: {status: status, summary: "#{status} by #{ENV['USER']}"},
|
89
|
+
).tap { |r| puts "#{status} at #{r&.approved_at}" }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
desc 'start [NAME]', 'start execution for pipeline'
|
97
|
+
def start(name = nil)
|
98
|
+
name ||= my.stack_pipeline_names.first
|
99
|
+
debug("Starting execution for #{name}")
|
100
|
+
puts Aws::Codepipeline.start(name)
|
101
|
+
tail name
|
102
|
+
end
|
103
|
+
|
104
|
+
desc 'tail [NAME]', 'tail pipeline state changes'
|
105
|
+
def tail(name = nil)
|
106
|
+
trap('SIGINT', 'EXIT') # clean exit with ctrl-c
|
107
|
+
name ||= my.stack_pipeline_names.first
|
108
|
+
last_seen = nil
|
109
|
+
loop do
|
110
|
+
state = Aws::Codepipeline.state(name)
|
111
|
+
now = Time.now
|
112
|
+
stages = state.stage_states.map do |s|
|
113
|
+
last_change = s.action_states.map { |a| a&.latest_execution&.last_status_change }.compact.max
|
114
|
+
revisions = s.action_states.map { |a| a.current_revision&.revision_id&.slice(0,7) }.join(' ')
|
115
|
+
ago = last_change ? human_time_diff(now - last_change, 1) : '?'
|
116
|
+
[s.stage_name, color(s&.latest_execution&.status || '', COLORS), "#{ago} ago", revisions].join(' ')
|
117
|
+
end
|
118
|
+
puts [set_color(now, :blue), stages].flatten.join(' ')
|
119
|
+
sleep 5
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|