ecsutil 0.1.0
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 +7 -0
- data/.gitignore +18 -0
- data/README.md +118 -0
- data/Rakefile +9 -0
- data/bin/ecsutil +9 -0
- data/ecsutil.gemspec +22 -0
- data/lib/ecsutil.rb +6 -0
- data/lib/ecsutil/aws.rb +258 -0
- data/lib/ecsutil/command.rb +24 -0
- data/lib/ecsutil/commands/deploy.rb +70 -0
- data/lib/ecsutil/commands/destroy.rb +19 -0
- data/lib/ecsutil/commands/help.rb +14 -0
- data/lib/ecsutil/commands/init.rb +94 -0
- data/lib/ecsutil/commands/run.rb +47 -0
- data/lib/ecsutil/commands/scale.rb +24 -0
- data/lib/ecsutil/commands/secrets.rb +91 -0
- data/lib/ecsutil/commands/status.rb +41 -0
- data/lib/ecsutil/config.rb +60 -0
- data/lib/ecsutil/helpers.rb +53 -0
- data/lib/ecsutil/runner.rb +119 -0
- data/lib/ecsutil/shared.rb +67 -0
- data/lib/ecsutil/terraform.rb +24 -0
- data/lib/ecsutil/vault.rb +42 -0
- data/lib/ecsutil/version.rb +3 -0
- metadata +123 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
require "json"
|
2
|
+
require "tempfile"
|
3
|
+
require "yaml"
|
4
|
+
require "securerandom"
|
5
|
+
|
6
|
+
module ECSUtil
|
7
|
+
module Helpers
|
8
|
+
def step_info(message, *params)
|
9
|
+
message = sprintf(message, *params) if params.any?
|
10
|
+
puts "----> #{message}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def confirm(title = nil, required = "Y")
|
14
|
+
title ||= "Are you sure?"
|
15
|
+
print "#{title} (Y/N): "
|
16
|
+
|
17
|
+
if STDIN.gets.strip != required
|
18
|
+
puts "Aborted"
|
19
|
+
exit 1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def terminate(message)
|
24
|
+
puts message
|
25
|
+
exit 1
|
26
|
+
end
|
27
|
+
|
28
|
+
def json_file(data)
|
29
|
+
f = Tempfile.new
|
30
|
+
f.write(JSON.pretty_generate(data))
|
31
|
+
f.flush
|
32
|
+
f.path
|
33
|
+
end
|
34
|
+
|
35
|
+
def array_hash(data = {}, key_name = :key, value_name = :value)
|
36
|
+
data.to_a.map do |k,v|
|
37
|
+
{
|
38
|
+
key_name.to_sym => k,
|
39
|
+
value_name.to_sym => v
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def parse_env_data(data)
|
45
|
+
data.
|
46
|
+
split("\n").
|
47
|
+
map(&:strip).
|
48
|
+
reject { |l| l.start_with?("#") || l.empty? }.
|
49
|
+
map { |l| l.split("=", 2) }.
|
50
|
+
to_h
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require "ecsutil/config"
|
2
|
+
require "ecsutil/terraform"
|
3
|
+
require "ecsutil/shared"
|
4
|
+
|
5
|
+
require "ecsutil/commands/help"
|
6
|
+
require "ecsutil/commands/init"
|
7
|
+
require "ecsutil/commands/deploy"
|
8
|
+
require "ecsutil/commands/run"
|
9
|
+
require "ecsutil/commands/scale"
|
10
|
+
require "ecsutil/commands/secrets"
|
11
|
+
require "ecsutil/commands/status"
|
12
|
+
require "ecsutil/commands/destroy"
|
13
|
+
|
14
|
+
module ECSUtil
|
15
|
+
class Runner
|
16
|
+
include ECSUtil::Terraform
|
17
|
+
|
18
|
+
def initialize(dir, args)
|
19
|
+
stage = args.shift
|
20
|
+
command = args.shift
|
21
|
+
command_parts = command&.split(":", 2)
|
22
|
+
|
23
|
+
@dir = dir
|
24
|
+
@stage = stage
|
25
|
+
@command = command_parts&.shift
|
26
|
+
@args = args
|
27
|
+
@action = command_parts&.shift
|
28
|
+
@config_path = File.join(@dir, "deploy", "#{stage}.yml")
|
29
|
+
end
|
30
|
+
|
31
|
+
def run
|
32
|
+
return print_help unless @command
|
33
|
+
return terminate("Please provide stage") unless @stage
|
34
|
+
|
35
|
+
config = read_config
|
36
|
+
|
37
|
+
klass = command_class(@command)
|
38
|
+
if !klass
|
39
|
+
terminate "Invalid command: #{@command}"
|
40
|
+
end
|
41
|
+
|
42
|
+
klass.new(config, @action, @args).run
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def command_class(name = "help")
|
48
|
+
{
|
49
|
+
help: ECSUtil::Commands::HelpCommand,
|
50
|
+
init: ECSUtil::Commands::InitCommand,
|
51
|
+
deploy: ECSUtil::Commands::DeployCommand,
|
52
|
+
run: ECSUtil::Commands::RunCommand,
|
53
|
+
scale: ECSUtil::Commands::ScaleCommand,
|
54
|
+
status: ECSUtil::Commands::StatusCommand,
|
55
|
+
secrets: ECSUtil::Commands::SecretsCommand,
|
56
|
+
destroy: ECSUtil::Commands::DestroyCommand,
|
57
|
+
}[name.to_sym]
|
58
|
+
end
|
59
|
+
|
60
|
+
def print_help
|
61
|
+
command_class("help").new(nil, nil, nil).run
|
62
|
+
end
|
63
|
+
|
64
|
+
def terminate(message)
|
65
|
+
puts message
|
66
|
+
exit 1
|
67
|
+
end
|
68
|
+
|
69
|
+
def read_config
|
70
|
+
return nil unless @command && @stage
|
71
|
+
|
72
|
+
outputs = {}
|
73
|
+
terraform_dir = File.join(@dir, "terraform/#{@stage}")
|
74
|
+
|
75
|
+
unless File.exists?(@config_path)
|
76
|
+
puts "Config file #{@config_path} does not exist, creating..."
|
77
|
+
|
78
|
+
example = <<~END
|
79
|
+
aws_profile: your AWS CLI profile
|
80
|
+
|
81
|
+
app: #{File.basename(@dir)}
|
82
|
+
env: #{@stage}
|
83
|
+
|
84
|
+
cluster: #{@stage}
|
85
|
+
repository: your ECR repository
|
86
|
+
subnets:
|
87
|
+
- subnet 1
|
88
|
+
- subnet 2
|
89
|
+
|
90
|
+
roles:
|
91
|
+
task: ECS task role ARN
|
92
|
+
execution: ECS execution role ARN
|
93
|
+
|
94
|
+
tasks:
|
95
|
+
example:
|
96
|
+
security_groups:
|
97
|
+
- sg1
|
98
|
+
- sg2
|
99
|
+
ports:
|
100
|
+
- 500
|
101
|
+
awslogs:
|
102
|
+
region: us-east-1
|
103
|
+
group: /ecs/#{File.basename(@dir)}/#{@stage}
|
104
|
+
END
|
105
|
+
|
106
|
+
FileUtils.mkdir_p(File.dirname(@config_path))
|
107
|
+
File.write(@config_path, example)
|
108
|
+
end
|
109
|
+
|
110
|
+
if File.exists?(terraform_dir)
|
111
|
+
outputs = read_terraform_outputs(terraform_dir)
|
112
|
+
else
|
113
|
+
warn "No terraform found at #{terraform_dir}"
|
114
|
+
end
|
115
|
+
|
116
|
+
ECSUtil::Config.read(@config_path, @stage, outputs)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module ECSUtil
|
2
|
+
module Shared
|
3
|
+
def load_active_task_definitions
|
4
|
+
step_info "Loading active task definitions"
|
5
|
+
@existing_tasks = list_active_task_definitions
|
6
|
+
@existing_tasks
|
7
|
+
end
|
8
|
+
|
9
|
+
def load_secrets
|
10
|
+
step_info "Loading secrets from %s", config["secrets_prefix"]
|
11
|
+
@config["secrets_data"] = fetch_parameter_store_keys(config["secrets_prefix"])
|
12
|
+
end
|
13
|
+
|
14
|
+
def load_services
|
15
|
+
step_info "Loading services"
|
16
|
+
@existing_services = list_services(config["cluster"])
|
17
|
+
end
|
18
|
+
|
19
|
+
def deregister_tasks
|
20
|
+
prefix = sprintf("%s-%s", config["app"], config["env"])
|
21
|
+
|
22
|
+
@existing_tasks.each do |arn|
|
23
|
+
name = arn.split("/", 2).last
|
24
|
+
next unless name.start_with?(prefix)
|
25
|
+
|
26
|
+
step_info "Deregistering #{arn}"
|
27
|
+
degerister_task_definition(arn)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def deregister_scheduled_tasks
|
32
|
+
prefix = sprintf("%s-%s", config["app"], config["env"])
|
33
|
+
|
34
|
+
list_rules.each do |rule|
|
35
|
+
next unless rule["Name"].start_with?(prefix)
|
36
|
+
|
37
|
+
task_name = rule["Name"].sub(prefix + "-", "")
|
38
|
+
next if config["scheduled_tasks"][task_name]
|
39
|
+
|
40
|
+
step_info "Removing scheduled task: #{task_name}"
|
41
|
+
delete_rule(rule["Name"])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def deregister_services
|
46
|
+
key = sprintf("%s-%s", config["app"], config["env"])
|
47
|
+
current_keys = config["services"].map do |k, _|
|
48
|
+
sprintf("%s-%s", key, k)
|
49
|
+
end
|
50
|
+
|
51
|
+
@existing_services.each do |service|
|
52
|
+
next unless service.start_with?(key)
|
53
|
+
next if current_keys.include?(service)
|
54
|
+
|
55
|
+
step_info "Deleting service: #{service}"
|
56
|
+
delete_service(config, service)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def deregister_secrets
|
61
|
+
(config["secrets_data"] || []).each do |secret|
|
62
|
+
step_info "Removing %s", secret[:name]
|
63
|
+
aws_call("ssm", "delete-parameter", "--name=#{secret[:name]}")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
module ECSUtil
|
4
|
+
module Terraform
|
5
|
+
def read_terraform_outputs(dir)
|
6
|
+
outputs = {}
|
7
|
+
|
8
|
+
Dir.chdir(dir) do
|
9
|
+
puts "----> Loading terraform outputs from #{dir}"
|
10
|
+
|
11
|
+
result = `terraform output -json`.strip
|
12
|
+
unless $?.success?
|
13
|
+
fail "Terraform error: #{result}"
|
14
|
+
end
|
15
|
+
|
16
|
+
JSON.load(result).each_pair do |key, data|
|
17
|
+
outputs[key] = data["value"]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
outputs
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "tempfile"
|
2
|
+
require "ansible/vault"
|
3
|
+
|
4
|
+
module ECSUtil
|
5
|
+
module Vault
|
6
|
+
def vault_read(path, password_path)
|
7
|
+
Ansible::Vault.read(
|
8
|
+
path: path,
|
9
|
+
password: File.read(password_path).strip
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
def vault_write(path, password_path, data)
|
14
|
+
Ansible::Vault.write(
|
15
|
+
path: path,
|
16
|
+
password: File.read(password_path).strip,
|
17
|
+
plaintext: data
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def vault_edit(path, password_path)
|
22
|
+
temp = Tempfile.new
|
23
|
+
temp.write(vault_read(path, password_path))
|
24
|
+
temp.flush
|
25
|
+
|
26
|
+
editor_path = `which $EDITOR`.strip
|
27
|
+
if editor_path.empty?
|
28
|
+
fail "EDITOR is not set!"
|
29
|
+
end
|
30
|
+
|
31
|
+
system "#{editor_path} #{temp.path}"
|
32
|
+
unless $?.success?
|
33
|
+
fail "Unable to save temp file"
|
34
|
+
end
|
35
|
+
|
36
|
+
vault_write(path, password_path, File.read(temp.path))
|
37
|
+
|
38
|
+
temp.close
|
39
|
+
temp.unlink
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
metadata
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ecsutil
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dan Sosedoff
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-05-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: json
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: ansible-vault
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: hashie
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '4'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '4'
|
69
|
+
description: TBD
|
70
|
+
email:
|
71
|
+
- dan.sosedoff@gmail.com
|
72
|
+
executables:
|
73
|
+
- ecsutil
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- ".gitignore"
|
78
|
+
- README.md
|
79
|
+
- Rakefile
|
80
|
+
- bin/ecsutil
|
81
|
+
- ecsutil.gemspec
|
82
|
+
- lib/ecsutil.rb
|
83
|
+
- lib/ecsutil/aws.rb
|
84
|
+
- lib/ecsutil/command.rb
|
85
|
+
- lib/ecsutil/commands/deploy.rb
|
86
|
+
- lib/ecsutil/commands/destroy.rb
|
87
|
+
- lib/ecsutil/commands/help.rb
|
88
|
+
- lib/ecsutil/commands/init.rb
|
89
|
+
- lib/ecsutil/commands/run.rb
|
90
|
+
- lib/ecsutil/commands/scale.rb
|
91
|
+
- lib/ecsutil/commands/secrets.rb
|
92
|
+
- lib/ecsutil/commands/status.rb
|
93
|
+
- lib/ecsutil/config.rb
|
94
|
+
- lib/ecsutil/helpers.rb
|
95
|
+
- lib/ecsutil/runner.rb
|
96
|
+
- lib/ecsutil/shared.rb
|
97
|
+
- lib/ecsutil/terraform.rb
|
98
|
+
- lib/ecsutil/vault.rb
|
99
|
+
- lib/ecsutil/version.rb
|
100
|
+
homepage: ''
|
101
|
+
licenses:
|
102
|
+
- MIT
|
103
|
+
metadata: {}
|
104
|
+
post_install_message:
|
105
|
+
rdoc_options: []
|
106
|
+
require_paths:
|
107
|
+
- lib
|
108
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
requirements: []
|
119
|
+
rubygems_version: 3.0.6
|
120
|
+
signing_key:
|
121
|
+
specification_version: 4
|
122
|
+
summary: TBD
|
123
|
+
test_files: []
|