ecsutil 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|