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
data/lib/stax/base.rb
CHANGED
@@ -1,8 +1,36 @@
|
|
1
|
-
require 'thor'
|
2
|
-
|
3
1
|
module Stax
|
4
2
|
class Base < Thor
|
3
|
+
|
5
4
|
no_commands do
|
5
|
+
def app_name
|
6
|
+
@_app_name ||= options[:app].empty? ? nil : cfn_safe(options[:app])
|
7
|
+
end
|
8
|
+
|
9
|
+
def branch_name
|
10
|
+
@_branch_name ||= cfn_safe(options[:branch])
|
11
|
+
end
|
12
|
+
|
13
|
+
def stack_prefix
|
14
|
+
@_stack_prefix ||= [app_name, branch_name].compact.join('-') + '-'
|
15
|
+
end
|
16
|
+
|
17
|
+
## find or create a stack object
|
18
|
+
def stack(id)
|
19
|
+
object = Stax.const_get(id.to_s.capitalize)
|
20
|
+
ObjectSpace.each_object(object).first || object.new([], options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def ensure_stack(*stacks)
|
24
|
+
stacks.each do |s|
|
25
|
+
stack(s)&.exists? or fail_task("#{s} stack is required")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
## alias for stack to preserve semantics
|
30
|
+
def command(id)
|
31
|
+
stack(id)
|
32
|
+
end
|
33
|
+
|
6
34
|
def debug(message)
|
7
35
|
say "[DEBUG] #{message}", :blue
|
8
36
|
end
|
@@ -16,14 +44,74 @@ module Stax
|
|
16
44
|
exit(1) if quit
|
17
45
|
end
|
18
46
|
|
47
|
+
## return true only if all given env vars are set
|
48
|
+
def env_set?(*vars)
|
49
|
+
vars.map{ |v| ENV.has_key?(v) }.all?
|
50
|
+
end
|
51
|
+
|
52
|
+
## fail unless given env vars are set
|
53
|
+
def ensure_env(*vars)
|
54
|
+
unless env_set?(*vars)
|
55
|
+
fail_task("Please set env: #{vars.join(' ')}")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def color(string, hash)
|
60
|
+
set_color(string, hash.fetch(string.to_sym, :yellow))
|
61
|
+
end
|
62
|
+
|
19
63
|
## make string safe to use in naming CFN stuff
|
20
64
|
def cfn_safe(string)
|
21
65
|
string.gsub(/[\W_]/, '-')
|
22
66
|
end
|
23
67
|
|
24
|
-
def
|
25
|
-
|
68
|
+
def prepend(prefix, id)
|
69
|
+
p = prefix.to_s
|
70
|
+
id.start_with?(p) ? id : p + id
|
71
|
+
end
|
72
|
+
|
73
|
+
def append(suffix, id)
|
74
|
+
s = suffix.to_s
|
75
|
+
id.end_with?(s) ? id : id + s
|
76
|
+
end
|
77
|
+
|
78
|
+
def stringify_keys(thing)
|
79
|
+
if thing.is_a?(Hash)
|
80
|
+
Hash[ thing.map { |k,v| [ k.to_s, stringify_keys(v) ] } ]
|
81
|
+
elsif thing.respond_to?(:map)
|
82
|
+
thing.map { |v| stringify_keys(v) }
|
83
|
+
else
|
84
|
+
thing
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
## psycho version of thor yes?() that demands a y or n answer
|
89
|
+
def y_or_n?(statement, color = nil)
|
90
|
+
loop do
|
91
|
+
case ask(statement, color, :add_to_history => false).downcase
|
92
|
+
when 'y'
|
93
|
+
return true
|
94
|
+
when 'n'
|
95
|
+
return false
|
96
|
+
else
|
97
|
+
puts "please respond 'y' or 'n'"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
## make epoch human-readable
|
103
|
+
def human_time(timestamp)
|
104
|
+
timestamp.nil? ? '-' : Time.at(timestamp.to_i/1000)
|
105
|
+
end
|
106
|
+
|
107
|
+
## convert bytes to nearest unit
|
108
|
+
def human_bytes(bytes, precision = 0)
|
109
|
+
return 0.to_s if bytes < 1
|
110
|
+
{T: 1000*1000*1000*1000, G: 1000*1000*1000, M: 1000*1000, K: 1000, B: 1}.each do |unit, value|
|
111
|
+
return "#{(bytes.to_f/value).round(precision)}#{unit}" if bytes >= value
|
112
|
+
end
|
26
113
|
end
|
114
|
+
|
27
115
|
end
|
28
116
|
end
|
29
117
|
end
|
data/lib/stax/cfer.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'cfer'
|
2
|
+
|
3
|
+
module Stax
|
4
|
+
class Stack < Base
|
5
|
+
class_option :use_previous_value, aliases: '-u', type: :array, default: [], desc: 'params to use previous value'
|
6
|
+
|
7
|
+
no_commands do
|
8
|
+
def cfer_parameters
|
9
|
+
{}
|
10
|
+
end
|
11
|
+
|
12
|
+
## location of cfer template file
|
13
|
+
def cfer_template
|
14
|
+
File.join('cf', "#{class_name}.rb")
|
15
|
+
end
|
16
|
+
|
17
|
+
## override with S3 bucket for upload of large templates as needed
|
18
|
+
def cfer_s3_path
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
## override with SNS ARNs as needed
|
23
|
+
def cfer_notification_arns
|
24
|
+
[]
|
25
|
+
end
|
26
|
+
|
27
|
+
def cfer_termination_protection
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
## create/update the stack
|
32
|
+
def cfer_converge(args = {})
|
33
|
+
opts = {
|
34
|
+
parameters: stringify_keys(cfer_parameters).except(*options[:use_previous_value]),
|
35
|
+
template: cfer_template,
|
36
|
+
follow: true,
|
37
|
+
number: 1,
|
38
|
+
s3_path: cfer_s3_path,
|
39
|
+
notification_arns: cfer_notification_arns,
|
40
|
+
enable_termination_protection: cfer_termination_protection,
|
41
|
+
}
|
42
|
+
Cfer.converge!(stack_name, opts.merge(args))
|
43
|
+
end
|
44
|
+
|
45
|
+
## generate JSON for stack without sending to cloudformation
|
46
|
+
def cfer_generate
|
47
|
+
opts = {parameters: stringify_keys(cfer_parameters)}
|
48
|
+
Cfer.generate!(cfer_template, opts)
|
49
|
+
end
|
50
|
+
|
51
|
+
def cfer_tail
|
52
|
+
Cfer.tail!(stack_name, follow: true, number: 1)
|
53
|
+
rescue ::Aws::CloudFormation::Errors::ValidationError => e
|
54
|
+
puts e.message
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
data/lib/stax/cli.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
|
+
require 'stax/aws/cfn'
|
2
|
+
|
1
3
|
module Stax
|
2
4
|
class Cli < Base
|
3
|
-
include
|
5
|
+
include Aws
|
6
|
+
|
7
|
+
class_option :branch, type: :string, default: Git.branch, desc: 'git branch to use'
|
8
|
+
class_option :app, type: :string, default: File.basename(Git.toplevel), desc: 'application name'
|
4
9
|
|
5
10
|
desc 'version', 'show version'
|
6
11
|
def version
|
@@ -8,9 +13,13 @@ module Stax
|
|
8
13
|
end
|
9
14
|
|
10
15
|
desc 'ls', 'list stacks for this branch'
|
11
|
-
def ls
|
12
|
-
|
13
|
-
|
16
|
+
def ls
|
17
|
+
print_table Cfn.stacks.select { |s|
|
18
|
+
s.stack_name.start_with?(stack_prefix)
|
19
|
+
}.map { |s|
|
20
|
+
[s.stack_name, s.creation_time, color(s.stack_status, Cfn::COLORS), s.template_description]
|
21
|
+
}
|
14
22
|
end
|
23
|
+
|
15
24
|
end
|
16
25
|
end
|
data/lib/stax/docker.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'stax/aws/sts'
|
2
|
+
require 'stax/aws/ecr'
|
3
|
+
|
4
|
+
module Stax
|
5
|
+
class Docker < Base
|
6
|
+
|
7
|
+
no_commands do
|
8
|
+
## default to ECR registry for this account
|
9
|
+
def docker_registry
|
10
|
+
@_docker_registry ||= "#{Aws::Sts.id.account}.dkr.ecr.#{ENV['AWS_REGION']}.amazonaws.com"
|
11
|
+
end
|
12
|
+
|
13
|
+
## name the docker repo after the git repo
|
14
|
+
def docker_repository
|
15
|
+
@_docker_repository ||= File.basename(Git.toplevel)
|
16
|
+
end
|
17
|
+
|
18
|
+
## full image name for docker push
|
19
|
+
def docker_image
|
20
|
+
@_docker_image ||= "#{docker_registry}/#{docker_repository}"
|
21
|
+
end
|
22
|
+
|
23
|
+
## build a docker image locally
|
24
|
+
def docker_local_build
|
25
|
+
debug("Docker build #{docker_image}")
|
26
|
+
system "docker build -t #{docker_image} #{Git.toplevel}"
|
27
|
+
end
|
28
|
+
|
29
|
+
## push docker image from local
|
30
|
+
def docker_push
|
31
|
+
debug("Docker push #{docker_image}")
|
32
|
+
system "docker push #{docker_image}"
|
33
|
+
end
|
34
|
+
|
35
|
+
## override this for your argus setup
|
36
|
+
def docker_argus_queue
|
37
|
+
@_docker_argus_queue ||= Aws::Sqs.queue_url('argus.fifo')
|
38
|
+
end
|
39
|
+
|
40
|
+
def docker_argus_build
|
41
|
+
debug("Sending to argus #{Git.branch}:#{Git.sha}")
|
42
|
+
org, repo = Git.repo.split('/')
|
43
|
+
Aws::Sqs.send(
|
44
|
+
queue_url: docker_argus_queue,
|
45
|
+
message_group_id: repo,
|
46
|
+
message_body: {
|
47
|
+
org: org,
|
48
|
+
repo: repo,
|
49
|
+
branch: Git.branch,
|
50
|
+
sha: Git.sha,
|
51
|
+
}.to_json
|
52
|
+
).tap do |r|
|
53
|
+
puts r&.message_id
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
desc 'registry', 'show registry name'
|
59
|
+
def registry
|
60
|
+
puts docker_registry
|
61
|
+
end
|
62
|
+
|
63
|
+
desc 'repository', 'show repository name'
|
64
|
+
def repository
|
65
|
+
puts docker_repository
|
66
|
+
end
|
67
|
+
|
68
|
+
desc 'image', 'show image name'
|
69
|
+
def image
|
70
|
+
puts docker_image
|
71
|
+
end
|
72
|
+
|
73
|
+
## override this method with the desired builder
|
74
|
+
desc 'build', 'build docker image'
|
75
|
+
def build
|
76
|
+
docker_local_build
|
77
|
+
# docker_argus_build
|
78
|
+
end
|
79
|
+
|
80
|
+
desc 'login', 'login to registry'
|
81
|
+
def login
|
82
|
+
Aws::Ecr.auth.each do |auth|
|
83
|
+
debug("Login to ECR registry #{auth.proxy_endpoint}")
|
84
|
+
user, pass = Base64.decode64(auth.authorization_token).split(':')
|
85
|
+
system "docker login -u #{user} -p #{pass} #{auth.proxy_endpoint}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
desc 'push', 'push docker image to registry'
|
90
|
+
def push
|
91
|
+
docker_push
|
92
|
+
end
|
93
|
+
|
94
|
+
desc 'exists', 'check if docker image exists in ECR'
|
95
|
+
def exists
|
96
|
+
puts Aws::Ecr.exists?(docker_repository, Git.sha)
|
97
|
+
end
|
98
|
+
|
99
|
+
desc 'poll', 'poll ECR until docker image exists'
|
100
|
+
def poll
|
101
|
+
debug("Waiting for image in ECR #{docker_repository}:#{Git.sha}")
|
102
|
+
sleep 10 until Aws::Ecr.exists?(docker_repository, Git.sha)
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
data/lib/stax/dsl.rb
ADDED
data/lib/stax/git.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'git_clone_url'
|
2
|
+
|
3
|
+
module Stax
|
4
|
+
class Git < Base
|
5
|
+
|
6
|
+
no_commands do
|
7
|
+
def self.branch
|
8
|
+
@_branch ||= `git symbolic-ref --short HEAD`.chomp
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.sha
|
12
|
+
@_sha ||= `git rev-parse HEAD`.chomp
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.short_sha
|
16
|
+
@_short_sha ||= self.sha.slice(0,7)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.origin_url
|
20
|
+
@_origin_url ||= `git config --get remote.origin.url`.chomp
|
21
|
+
end
|
22
|
+
|
23
|
+
## path like org/repo
|
24
|
+
def self.repo
|
25
|
+
@_repo ||= GitCloneUrl.parse(origin_url)&.path&.sub(/\.git$/, '')
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.origin_sha
|
29
|
+
@_origin_sha ||= `git rev-parse origin/#{branch}`.chomp
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.toplevel
|
33
|
+
@_toplevel ||= `git rev-parse --show-toplevel`.chomp
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.uncommitted_changes?
|
37
|
+
!`git diff --shortstat`.chomp.empty?
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.unpushed_commits?
|
41
|
+
sha != origin_sha
|
42
|
+
end
|
43
|
+
|
44
|
+
## tag the sha and push to origin
|
45
|
+
def self.tag(tag, sha)
|
46
|
+
system "git tag #{tag} #{sha} && git push origin #{tag} --quiet"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
desc 'branch', 'show current git branch'
|
51
|
+
def branch
|
52
|
+
puts Git.branch
|
53
|
+
end
|
54
|
+
|
55
|
+
desc 'sha', 'show current local git sha'
|
56
|
+
def sha
|
57
|
+
puts Git.sha
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
data/lib/stax/github.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'octokit'
|
2
|
+
|
3
|
+
module Stax
|
4
|
+
class Github < Base
|
5
|
+
|
6
|
+
no_commands do
|
7
|
+
def self.octokit
|
8
|
+
abort('Please set GITHUB_TOKEN') unless ENV['GITHUB_TOKEN']
|
9
|
+
@_octokit ||= Octokit::Client.new(access_token: ENV['GITHUB_TOKEN'])
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.tags
|
13
|
+
@_tags ||= octokit.tags(Git.repo)
|
14
|
+
end
|
15
|
+
|
16
|
+
## check if this sha exists in github
|
17
|
+
def self.exists?
|
18
|
+
!octokit.commit(Git.repo, Git.sha).nil?
|
19
|
+
rescue Octokit::NotFound
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
data/lib/stax/iam.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'stax/aws/iam'
|
2
|
+
require 'stax/aws/sts'
|
3
|
+
|
4
|
+
module Stax
|
5
|
+
class Iam < Base
|
6
|
+
|
7
|
+
desc 'id', 'get account id'
|
8
|
+
def id
|
9
|
+
puts Aws::Sts.account_id
|
10
|
+
end
|
11
|
+
|
12
|
+
desc 'aliases', 'get account aliases'
|
13
|
+
def aliases
|
14
|
+
puts Aws::Iam.aliases
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
data/lib/stax/keypair.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
require 'awful/keypair'
|
3
|
+
require 'awful/param'
|
4
|
+
|
5
|
+
module Stax
|
6
|
+
module Keypair
|
7
|
+
def self.included(thor)
|
8
|
+
thor.class_eval do
|
9
|
+
|
10
|
+
no_commands do
|
11
|
+
def key_pair_name
|
12
|
+
@_key_pair_name ||= stack_name
|
13
|
+
end
|
14
|
+
|
15
|
+
## parameter store name to store private key
|
16
|
+
def key_pair_store_name
|
17
|
+
"#{key_pair_name}.key_pair"
|
18
|
+
end
|
19
|
+
|
20
|
+
def key_pair_describe
|
21
|
+
keypair(:ls, [key_pair_name], long: true)
|
22
|
+
rescue Aws::EC2::Errors::InvalidKeyPairNotFound => e
|
23
|
+
warn(e.message)
|
24
|
+
end
|
25
|
+
|
26
|
+
## create a new key pair and return private key
|
27
|
+
def key_pair_create
|
28
|
+
keypair(:create, [key_pair_name], quiet: true).key_material
|
29
|
+
rescue Aws::EC2::Errors::InvalidKeyPairDuplicate => e
|
30
|
+
warn(e.message)
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
|
34
|
+
## create a key and store it in parameter store
|
35
|
+
def key_pair_store
|
36
|
+
key = key_pair_create or return
|
37
|
+
param(:put, [key_pair_store_name], value: key, type: 'SecureString', key_id: try(:kms_id), overwrite: true)
|
38
|
+
end
|
39
|
+
|
40
|
+
## get private key from store and write to a tempfile for ssh to find; return file object
|
41
|
+
def key_pair_get
|
42
|
+
Tempfile.new(stack_name).tap do |file|
|
43
|
+
key = param(:get, [key_pair_store_name], decrypt: true, quiet: true).first
|
44
|
+
File.chmod(0400, file.path) # ssh needs this mode
|
45
|
+
file.write(key.value)
|
46
|
+
file.close
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
## delete the key pair and the parameter store
|
51
|
+
def key_pair_delete
|
52
|
+
keypair(:delete, [key_pair_name], yes: true)
|
53
|
+
param(:delete, [key_pair_store_name], yes: true)
|
54
|
+
rescue Aws::SSM::Errors::ParameterNotFound
|
55
|
+
warn("Parameter #{key_pair_store_name} does not exist")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
desc 'key', 'key pair tasks'
|
60
|
+
method_option :create, type: :boolean, default: false, desc: 'create a new key pair'
|
61
|
+
method_option :delete, type: :boolean, default: false, desc: 'delete key pair'
|
62
|
+
def key
|
63
|
+
if options[:create]
|
64
|
+
key_pair_store
|
65
|
+
elsif options[:delete]
|
66
|
+
key_pair_delete
|
67
|
+
else
|
68
|
+
key_pair_describe
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|