ecs-tools 0.0.4

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