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.
@@ -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: []