ecs-tools 0.0.4

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 007e703284dfe0d89de72cc5a06ca50e1755259d
4
+ data.tar.gz: aa11ceabcbd5210e198b264ab7c8a2f4f0c9d8fa
5
+ SHA512:
6
+ metadata.gz: 5424d08bdae0603d0a92df619ef96eba32b947078c1f13c6fefda9b165044e276f7d27227e4f84eaf824dc53d006e66fa405435d4c8e89eeab6f615f7b1b2c3c
7
+ data.tar.gz: 8eff845e545b8d20193eec37c0f074ae849d22c75f81851b1ca83b9c8a250e36313b5bcb762da1c6aa3cf296846aeb4cf4aef834a225541437787a4d7f268575
data/bin/ecr-build ADDED
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env ruby
2
+ # ecr-build: Simple script for building docker images and pushing them to Amazon Elastic Container Service Registry (ECR)
3
+ #
4
+ # Usage: ecr-build [options]...
5
+ #
6
+ # Arguments:
7
+ #
8
+ # -k | --aws-access-key AWS Access Key ID. May also be set as environment variable AWS_ACCESS_KEY_ID
9
+ # -s | --aws-secret-key AWS Secret Access Key. May also be set as environment variable AWS_SECRET_ACCESS_KEY
10
+ # -r | --region AWS Region Name. May also be set as environment variable AWS_DEFAULT_REGION
11
+ #
12
+ # -f | --file Location of the Dockerfile (default: Dockerfile)
13
+ # -r | --repository-url Specify the ECR repository url (default: url from first repository)
14
+ # -t | --tag Tag of the docker image, eg. latest
15
+ #
16
+ require "open3"
17
+ $stderr.sync = true
18
+ require "ecs_deploy"
19
+
20
+ def usage
21
+ exec "grep '^# ' < '#{__FILE__}' | cut -c4-"
22
+ end
23
+
24
+ def format_image_tag(tag)
25
+ STDERR.puts "Warning: Provided image tag is longer than 30 characters. Resulting tag will be cut short." if tag.length > 30
26
+ tag[0..29].gsub(/[^a-z0-9_.-]/, ".")
27
+ end
28
+
29
+ def cmd(command)
30
+ # popen2e merges stdout and stderr
31
+ Open3.popen2e(command) do |stdin, output, thread|
32
+ while line = output.gets do
33
+ puts line
34
+ end
35
+ process_status = thread.value
36
+ unless process_status.success?
37
+ STDERR.puts "Error: '#{command}' failed with status #{process_status.exitstatus}"
38
+ exit 1
39
+ end
40
+ end
41
+ end
42
+
43
+ if ARGV.first == "format-image-tag"
44
+ ARGV.shift
45
+ puts format_image_tag(ARGV.shift)
46
+ exit 0
47
+ end
48
+
49
+ # default options
50
+ aws_access_key = ENV["AWS_ACCESS_KEY_ID"]
51
+ aws_secret_key = ENV["AWS_SECRET_ACCESS_KEY"]
52
+ aws_region = ENV["AWS_REGION"] || ENV["AWS_DEFAULT_REGION"]
53
+ dockerfile = "Dockerfile"
54
+ repository = nil
55
+ image_tag = "latest"
56
+
57
+ # parse arguments
58
+ ARGV.options do |opts|
59
+ # aws options
60
+ opts.on("-k", "--aws-access-key=key", String) { |v| aws_access_key = v }
61
+ opts.on("-s", "--aws-secret-key=secret", String) { |v| aws_secret_key = v }
62
+ opts.on("-r", "--region=aws-region", String) { |v| aws_region = v }
63
+ # ecr-build options
64
+ opts.on("-f", "--file=name", String) { |v| dockerfile = v }
65
+ opts.on("-r", "--repository-url=url", String) { |v| repository = v }
66
+ opts.on("-t", "--tag=name", String) { |v| image_tag = v }
67
+ opts.on_tail("-h", "--help") { usage }
68
+ opts.parse!
69
+ end
70
+
71
+ unless File.exist?(dockerfile)
72
+ STDERR.puts "Dockerfile does not exist at #{`pwd`.strip}/#{dockerfile}"
73
+ exit 1
74
+ end
75
+
76
+ unless repository
77
+ STDERR.puts "Repository url must be defined using -r/--repository-url=url"
78
+ exit 1
79
+ end
80
+
81
+ # configure Aws
82
+ Aws.config.update(region: aws_region, credentials: Aws::Credentials.new(aws_access_key, aws_secret_key))
83
+ ecr = Aws::ECR::Client.new
84
+
85
+ image_tag = format_image_tag(image_tag)
86
+ image_name = repository.sub(/\/.*$/, ":#{image_tag}")
87
+ target = "#{repository}:#{image_tag}"
88
+
89
+ # build the docker image
90
+ cmd "docker build --file #{dockerfile} --tag #{image_name} ."
91
+
92
+ # tag the docker image
93
+ cmd "docker tag #{image_name} #{target}"
94
+
95
+ # login
96
+ authorization_response = ecr.get_authorization_token
97
+ authorization_data = authorization_response.authorization_data.first
98
+ user, token = Base64.decode64(authorization_data.authorization_token).split(":")
99
+ cmd "docker login -u '#{user}' -p '#{token}' -e none #{authorization_data.proxy_endpoint}"
100
+
101
+ # push the image
102
+ cmd "docker push #{target}"
103
+ puts target
104
+
data/bin/ecs-console ADDED
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env ruby
2
+ # ecs-console: Simple script for connecting you to a docker instance on Amazon Elastic Container Service
3
+ #
4
+ # Usage: ecs-console [options] <command>
5
+ #
6
+ # Arguments:
7
+ #
8
+ # -k | --aws-access-key AWS Access Key ID. May also be set as environment variable AWS_ACCESS_KEY_ID
9
+ # -s | --aws-secret-key AWS Secret Access Key. May also be set as environment variable AWS_SECRET_ACCESS_KEY
10
+ # -r | --region AWS Region Name. May also be set as environment variable AWS_DEFAULT_REGION
11
+ # -c | --cluster Specify the name of the cluster to use
12
+ # -p | --private-key Path to the EC2 *.pem private key file (default: '~/.ssh/<cluster>.pem')
13
+ #
14
+ # -i | --image Full repository url to the docker image
15
+ # -n | --container-name Specify the name of the container definition of your ecs_deploy.yml
16
+ # -s | --service Attach to a running service (alternative to image/container)
17
+ # -f | --file Use the following ecs config file (default: config/ecs_deploy.yml)
18
+ #
19
+ $stderr.sync = true
20
+ require "ecs_deploy"
21
+ require "net/scp"
22
+ require 'byebug'
23
+
24
+ def usage
25
+ exec "grep '^# ' < '#{__FILE__}' | cut -c4-"
26
+ end
27
+
28
+ def repository_slug
29
+ `git remote -v`.scan(/origin.+github.com:([^\.]+)\.git/).flatten.first
30
+ end
31
+
32
+ # default options
33
+ aws_access_key = ENV["AWS_ACCESS_KEY_ID"]
34
+ aws_secret_key = ENV["AWS_SECRET_ACCESS_KEY"]
35
+ aws_region = ENV["AWS_REGION"] || ENV["AWS_DEFAULT_REGION"]
36
+ file = branch = image = cluster = container = service = private_key = nil
37
+
38
+ # parse arguments
39
+ ARGV.options do |opts|
40
+ opts.on("-b", "--branch=git-branch", String) { |v| branch = v }
41
+ # aws/ecs options
42
+ opts.on("-k", "--aws-access-key", String) { |v| aws_access_key = v }
43
+ opts.on("-s", "--aws-secret-key", String) { |v| aws_secret_key = v }
44
+ opts.on("-r", "--region=aws-region", String) { |v| aws_region = v }
45
+ opts.on("-c", "--cluster=name", String) { |v| cluster = v }
46
+ opts.on("-p", "--private-key=path", String) { |v| private_key = v }
47
+ # ecs-console options
48
+ opts.on("-i", "--image=name", String) { |v| image = v }
49
+ opts.on("-n", "--container-name=name", String) { |v| container = v }
50
+ opts.on("-s", "--service=name", String) { |v| service = v }
51
+ opts.on("-f", "--file=path", String) { |v| file = v }
52
+
53
+ opts.on_tail("-h", "--help") { usage }
54
+ opts.parse!
55
+ end
56
+
57
+ file ||= "config/ecs_#{branch}.yml"
58
+ command = ARGV.join(" ")
59
+
60
+ # configure Aws
61
+ Aws.config.update(region: aws_region, credentials: Aws::Credentials.new(aws_access_key, aws_secret_key))
62
+ ec2 = Aws::EC2::Client.new
63
+ ecs = Aws::ECS::Client.new
64
+ unless image
65
+ ecr = Aws::ECR::Client.new
66
+ repository_uri = ecr.describe_repositories.repositories.find { |repository| repository.repository_name == repository_slug }.repository_uri
67
+ image_details = ecr.describe_images(repository_name: repository_slug).image_details
68
+ image_details = image_details.select { |image_detail| image_detail.image_tags&.first.to_s =~ /^#{branch}/ }
69
+ last_image_tag = image_details.sort_by(&:image_pushed_at).last.image_tags.first
70
+ image = "#{repository_uri}:#{last_image_tag}"
71
+ end
72
+
73
+ config = YAML.load_file(file).deep_symbolize_keys
74
+ container ||= config[:container_definitions].keys.first
75
+ container_definition = config[:container_definitions][container.to_sym]
76
+ cluster ||= config[:cluster]
77
+ cluster_name = cluster.sub(/-ECSCluster.+$/, '')
78
+ private_key ||= "~/.ssh/#{cluster_name}.pem"
79
+
80
+ container_instance_arns = ecs.list_container_instances(cluster: cluster).container_instance_arns
81
+ instance_id = ecs.describe_container_instances(cluster: cluster, container_instances: container_instance_arns).container_instances.sample.ec2_instance_id
82
+ public_dns_name = ec2.describe_instances(instance_ids: [instance_id]).reservations.first.instances.first.public_dns_name
83
+
84
+ environment_data = container_definition[:environment].map { |env| "#{env[:name]}=#{env[:value]}" }.join("\n")
85
+ remote_env_file = "/home/ec2-user/.ecs-console.#{Time.now.to_f}.tmp.env"
86
+ Net::SCP.upload!(public_dns_name, "ec2-user", StringIO.new(environment_data), remote_env_file, ssh: {keys: [File.expand_path(private_key)]})
87
+
88
+ puts "Connecting to ec2-user@#{public_dns_name}, running '#{command}' inside #{image}"
89
+ ssh_command = [
90
+ "chmod 400 #{remote_env_file}",
91
+ "\\$(aws ecr get-login --region #{aws_region})",
92
+ "docker run --rm --env-file=#{remote_env_file} -ti #{image} #{command}",
93
+ "rm -f #{remote_env_file}"
94
+ ].join("; ")
95
+ exec %Q[ssh -ti "#{private_key}" ec2-user@#{public_dns_name} "#{ssh_command}"]
data/bin/ecs-deploy ADDED
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env ruby
2
+ # ecs-deploy: Simple script for deploying docker images to Amazon Elastic Container Service
3
+ #
4
+ # Usage: deploy [options]...
5
+ #
6
+ # Arguments:
7
+ #
8
+ # -k | --aws-access-key AWS Access Key ID. May also be set as environment variable AWS_ACCESS_KEY_ID
9
+ # -s | --aws-secret-key AWS Secret Access Key. May also be set as environment variable AWS_SECRET_ACCESS_KEY
10
+ # -r | --region AWS Region Name. May also be set as environment variable AWS_DEFAULT_REGION
11
+ #
12
+ # -i | --image Full repository url to the docker image
13
+ # -c | --cluster Specify the name of the cluster to use
14
+ # -w | --wait-time Time given to wait until all services have the new task definition running
15
+ # -f | --file ECS deploy configuration file (default: config/ecs_deploy.yml)
16
+ #
17
+ $stderr.sync = true
18
+ require "ecs_deploy"
19
+
20
+ def usage
21
+ exec "grep '^# ' < '#{__FILE__}' | cut -c4-"
22
+ end
23
+
24
+ class Aws::ECS::Client
25
+ def upsert_service(options)
26
+ send(:update_service, options)
27
+ rescue Aws::ECS::Errors::ServiceNotFoundException, Aws::ECS::Errors::ServiceNotActiveException
28
+ options[:service_name] = options.delete(:service)
29
+ send(:create_service, options)
30
+ end
31
+ end
32
+
33
+ # default options
34
+ aws_access_key = ENV["AWS_ACCESS_KEY_ID"]
35
+ aws_secret_key = ENV["AWS_SECRET_ACCESS_KEY"]
36
+ aws_region = ENV["AWS_REGION"] || ENV["AWS_DEFAULT_REGION"]
37
+ image = cluster = nil
38
+ debug = false
39
+ wait_time = 420 # 7 minutes
40
+ config_file = "config/ecs_deploy.yml"
41
+
42
+ # parse arguments
43
+ ARGV.options do |opts|
44
+ # aws options
45
+ opts.on("-k", "--aws-access-key=key", String) { |v| aws_access_key = v }
46
+ opts.on("-s", "--aws-secret-key=secret", String) { |v| aws_secret_key = v }
47
+ opts.on("-r", "--region=aws-region", String) { |v| aws_region = v }
48
+ # ecs-deploy options
49
+ opts.on("-i", "--image=name", String) { |v| image = v }
50
+ opts.on("-c", "--cluster=name", String) { |v| cluster = v }
51
+ opts.on("-w", "--wait-time=seconds", Integer) { |v| wait_time = v }
52
+ opts.on("-f", "--file=path", String) { |v| config_file = v }
53
+ # opts.on("-i", "--int=val", Integer) { |val| integer = val }
54
+ # opts.on("--list=[x,y,z]", Array) { |val| list = val }
55
+ opts.on("--debug") { debug = true }
56
+ opts.on_tail("-h", "--help") { usage }
57
+ opts.parse!
58
+ end
59
+
60
+ # configure Aws
61
+ Aws.config.update(region: aws_region, credentials: Aws::Credentials.new(aws_access_key, aws_secret_key))
62
+ ecs = Aws::ECS::Client.new
63
+
64
+ config = YAML.load_file(config_file).deep_symbolize_keys
65
+
66
+ cluster ||= config[:cluster]
67
+
68
+ # register new task definitions
69
+ task_definitions = {}
70
+ config[:task_definitions].each do |family, task_definition|
71
+ definition = deep_dup(task_definition)
72
+ definition[:family] = family
73
+ definition[:container_definitions].each { |definition| definition[:image] = image } if image
74
+ response = ecs.register_task_definition(definition)
75
+ definition[:arn] = response.task_definition.task_definition_arn
76
+ puts "New task definition: #{definition[:arn]}"
77
+ task_definitions[family] = definition
78
+ end
79
+
80
+ def report_run_task_failures(failures)
81
+ return if failures.empty?
82
+ failures.each do |failure|
83
+ STDERR.puts "Error: run task failure '#{failure.reason}'"
84
+ end
85
+ exit 1
86
+ end
87
+
88
+ # handle one off tasks
89
+ config.fetch(:one_off_commands, []).each do |one_off_command|
90
+ task_definition = task_definitions[one_off_command[:task_family].to_sym]
91
+ puts "Running '#{one_off_command[:command]}'"
92
+ response = ecs.run_task(
93
+ cluster: cluster,
94
+ task_definition: task_definition[:arn],
95
+ count: 1,
96
+ started_by: "ecs-deploy: one_off_commands",
97
+ overrides: {
98
+ container_overrides: [
99
+ {
100
+ name: task_definition[:container_definitions].first[:name],
101
+ command: Array(one_off_command[:command])
102
+ }
103
+ ]
104
+ }
105
+ )
106
+ # handle potential failures
107
+ report_run_task_failures(response.failures)
108
+
109
+ task_arn = response.tasks.first.task_arn
110
+ print "Waiting for '#{one_off_command[:command]}' to finish"
111
+ waiting = 0
112
+ last_now = Time.now
113
+ task = nil
114
+ while waiting <= wait_time do
115
+ task = ecs.describe_tasks(tasks: [task_arn], cluster: cluster).tasks.first
116
+ break if task.last_status == "STOPPED"
117
+ print "."
118
+ now = Time.now
119
+ waiting += (now - last_now).to_i
120
+ last_now = now
121
+ sleep 5
122
+ end
123
+ if waiting > wait_time
124
+ STDERR.puts "Error: wait time exceeded"
125
+ exit 1
126
+ end
127
+ if task.containers.first.exit_code != 0
128
+ STDERR.puts "Error: '#{one_off_command[:command]}' finished with a non-zero exit code! Aborting."
129
+ exit 1
130
+ end
131
+ puts " done!"
132
+ end
133
+
134
+ # handle services
135
+ task_definition_arns = []
136
+ config[:services].each do |service|
137
+ task_definition = task_definitions[service[:task_family].to_sym]
138
+ task_definition_arns << task_definition[:arn]
139
+
140
+ ecs.upsert_service(
141
+ cluster: cluster,
142
+ service: service[:name],
143
+ desired_count: service[:desired_count],
144
+ task_definition: task_definition[:arn],
145
+ deployment_configuration: service[:deployment_configuration]
146
+ )
147
+ end
148
+
149
+ print "Waiting #{wait_time}s until all service tasks come up running"
150
+ waiting = 0
151
+ last_now = Time.now
152
+ while waiting <= wait_time do
153
+ running_tasks = ecs.describe_tasks(cluster: cluster, tasks: ecs.list_tasks(cluster: cluster, desired_status: "RUNNING").task_arns).tasks
154
+ break if task_definition_arns.all? { |arn| running_tasks.any? { |task| task.task_definition_arn == arn } }
155
+ print "."
156
+ waiting += (Time.now - last_now).to_i
157
+ sleep 5
158
+ end
159
+ if waiting > wait_time
160
+ STDERR.puts "Error: wait time exceeded"
161
+ exit 1
162
+ else
163
+ puts " done!"
164
+ end
165
+
data/lib/ecs_deploy.rb ADDED
@@ -0,0 +1,25 @@
1
+ require "optparse"
2
+ require "yaml"
3
+ require "aws-sdk"
4
+
5
+ def deep_dup(obj)
6
+ Marshal.load(Marshal.dump(obj))
7
+ end
8
+
9
+ class Array
10
+ def deep_symbolize_keys
11
+ map do |object|
12
+ object.respond_to?(:deep_symbolize_keys) ? object.deep_symbolize_keys : object
13
+ end
14
+ end
15
+ end
16
+
17
+ class Hash
18
+ def deep_symbolize_keys
19
+ inject({}) do |hash, (key, value)|
20
+ hash[key.to_sym] = value.respond_to?(:deep_symbolize_keys) ? value.deep_symbolize_keys : value
21
+ hash
22
+ end
23
+ end
24
+ end
25
+
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ecs-tools
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ platform: ruby
6
+ authors:
7
+ - Lukas Rieder
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-06-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 2.8.0
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 2.8.14
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: 2.8.0
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 2.8.14
33
+ - !ruby/object:Gem::Dependency
34
+ name: net-ssh
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.2'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '3.2'
47
+ - !ruby/object:Gem::Dependency
48
+ name: net-scp
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.2'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.2'
61
+ description: Deploy your containerized application into Amazon Elastic Container Service
62
+ email: l.rieder@gmail.com
63
+ executables:
64
+ - ecs-deploy
65
+ - ecs-console
66
+ - ecr-build
67
+ extensions: []
68
+ extra_rdoc_files: []
69
+ files:
70
+ - bin/ecr-build
71
+ - bin/ecs-console
72
+ - bin/ecs-deploy
73
+ - lib/ecs_deploy.rb
74
+ homepage: https://github.com/Overbryd/ecs-tools
75
+ licenses:
76
+ - MIT
77
+ metadata: {}
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubyforge_project:
94
+ rubygems_version: 2.6.11
95
+ signing_key:
96
+ specification_version: 4
97
+ summary: AWS ECS deployment
98
+ test_files: []