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