ecsutil 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,3 @@
1
+ module ECSUtil
2
+ VERSION = "0.1.0"
3
+ 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: []