ecs_deployer 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5923a19be9af0b8b94e8a3a15ba3ef0f47c97321
4
+ data.tar.gz: 7fad5d15dde84e2320427e7ac17dec3461037be0
5
+ SHA512:
6
+ metadata.gz: 43e7796ee3372669de2c36041ee6a71e0f33a3d9ad24c29adda8f5c601d2b429f0cab8909e0c51c0a8b6a905d2affe13f6ef859c153eacfb2d64b1217474b287
7
+ data.tar.gz: 99d51da9894390e8db85e2bae8fd107f274c4e3da365b29674afbe3c1990ef8e7e936f45641e91f60a57c153da4eb64f0a50d90db4cd691e747c5f16b3bcb1ae
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.0
5
+ before_install: gem install bundler -v 1.14.5
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ecs_deployer.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # EcsDeployer
2
+
3
+ This package provides the service deployment function of ECS.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'ecs_deployer'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install ecs_deployer
20
+
21
+ ## Usage
22
+
23
+ ```
24
+ ecs_deployer = EcsDeployer::Client.new('cluster_name')
25
+ ecs_deployer.register_task('development.yml')
26
+ ecs_deployer.update_service('application')
27
+ ```
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "ecs_deployer"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,37 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ecs_deployer/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ecs_deployer"
8
+ spec.version = EcsDeployer::VERSION
9
+ spec.authors = ["naomichi-y"]
10
+ spec.email = ["n.yamakita@gmail.com"]
11
+
12
+ spec.summary = %q{Deploy application to ECS.}
13
+ spec.description = %q{This package provides the service deployment function of ECS.}
14
+ spec.homepage = ""
15
+
16
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
18
+ if spec.respond_to?(:metadata)
19
+ # spec.metadata['allowed_push_host'] = ""
20
+ else
21
+ raise "RubyGems 2.0 or newer is required to protect against " \
22
+ "public gem pushes."
23
+ end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
26
+ f.match(%r{^(test|spec|features)/})
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_dependency 'runtime_command'
33
+ spec.add_dependency 'oj'
34
+ spec.add_development_dependency "bundler", "~> 1.14"
35
+ spec.add_development_dependency "rake", "~> 10.0"
36
+ spec.add_development_dependency "rspec", "~> 3.0"
37
+ end
@@ -0,0 +1,20 @@
1
+ containerDefinitions:
2
+ - name: wordpress
3
+ links:
4
+ - mysql
5
+ image: wordpress
6
+ essential: true
7
+ portMappings:
8
+ - containerPort: 80
9
+ hostPort: 80
10
+ memory: 500
11
+ cpu: 10
12
+ - environment:
13
+ - name: MYSQL_ROOT_PASSWORD
14
+ value: password
15
+ name: mysql
16
+ image: mysql
17
+ cpu: 10
18
+ memory: 500
19
+ essential: true
20
+ family: hello_world
data/example/sample.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/setup'
2
+ require 'ecs_deployer'
3
+
4
+ task_path = File.expand_path('./fixtures/task.yml', File.dirname(File.realpath(__FILE__)))
5
+
6
+ ecs_deployer = EcsDeployer::Client.new('cluster_name')
7
+ ecs_deployer.register_task(task_path)
8
+ ecs_deployer.update_service('application')
@@ -0,0 +1,128 @@
1
+ require 'yaml'
2
+ require 'runtime_command'
3
+ require 'ecs_deployer/commander'
4
+
5
+ module EcsDeployer
6
+ class Client
7
+ PAULING_INTERVAL = 20
8
+
9
+ # @param [String] cluster_name
10
+ # @param [Hash] options
11
+ # @option options [String] :profile
12
+ # @option options [String] :region
13
+ # @return [EcsDeployer::Client]
14
+ def initialize(cluster_name, options = {})
15
+ @cluster_name = cluster_name
16
+ @ecs_command = Commander.new(cluster_name, options)
17
+ @family_name = ''
18
+ @revision = ''
19
+ @new_task_definition_arn = ''
20
+ end
21
+
22
+ # @param [String] task_path
23
+ # @return [String]
24
+ def register_task(task_path)
25
+ raise IOError.new("File does not exist. [#{task_path}]") if !File.exist?(task_path)
26
+ register_task_process(YAML.load(File.read(task_path)))
27
+ end
28
+
29
+ # @param [String] service_name
30
+ # @return [String]
31
+ def register_clone_task(service_name)
32
+ detected_service = false
33
+
34
+ result = @ecs_command.describe_services(service_name)
35
+ result['services'].each do |service|
36
+ if service['serviceName'] == service_name
37
+ result = @ecs_command.describe_task_definition(service['taskDefinition'])
38
+ @new_task_definition_arn = register_task_process(result['taskDefinition'])
39
+ detected_service = true
40
+ break
41
+ end
42
+ end
43
+
44
+ raise ServiceNotFoundError.new("'#{service_name}' service is not found.") unless detected_service
45
+
46
+ @new_task_definition_arn
47
+ end
48
+
49
+ # @param [String] service_name
50
+ # @param [Fixnum] timeout
51
+ def update_service(service_name, wait = true, timeout = 300)
52
+ register_clone_task(service_name) if @new_task_definition_arn.empty?
53
+ @ecs_command.update_service(service_name, @family_name, @revision)
54
+ wait_for_deploy if wait
55
+ end
56
+
57
+ private
58
+ def wait_for_deploy(service_name, timeout)
59
+ detected_service = false
60
+
61
+ result = @ecs_command.describe_services(service_name)
62
+ result['services'].each do |service|
63
+ next unless service['serviceName'] == service_name
64
+ detected_service = true
65
+
66
+ result = @ecs_command.describe_task_definition(service['taskDefinition'])
67
+
68
+ if service['desiredCount'] > 0
69
+ running_new_task = false
70
+ wait_time = 0
71
+ puts 'Start deploing...'
72
+
73
+ begin
74
+ sleep(PAULING_INTERVAL)
75
+ wait_time += PAULING_INTERVAL
76
+
77
+ # Get current tasks
78
+ result = @ecs_command.list_tasks(service_name)
79
+
80
+ if result['taskArns'].size > 0
81
+ success_count = 0
82
+
83
+ result = @ecs_command.describe_tasks(result['taskArns'])
84
+ result['tasks'].each do |task|
85
+ success_count += 1 if @new_task_definition_arn == task['taskDefinitionArn']
86
+ end
87
+
88
+ if result['tasks'].size == success_count
89
+ puts 'Service update succeeded.'
90
+ puts "New task definition: #{@new_task_definition_arn}"
91
+
92
+ running_new_task = true
93
+ end
94
+ else
95
+ raise TaskNotFoundError.new('Desired count is 0.')
96
+ end
97
+
98
+ if wait_time > timeout
99
+ puts "New task definition: #{@new_task_definition_arn}"
100
+ raise DeployTimeoutError.new('Service is being updating, but process is timed out.')
101
+ end
102
+
103
+ puts "Deploying... (#{wait_time} seconds elapsed)"
104
+
105
+ end while !running_new_task
106
+ end
107
+
108
+ break
109
+ end
110
+
111
+ raise ServiceNotFoundError.new("'#{service_name}' service is not found.") unless detected_service
112
+ end
113
+
114
+ # @param [Hash] task_definition
115
+ # @return [String]
116
+ def register_task_process(task_definition)
117
+ result = @ecs_command.register_task_definition(
118
+ task_definition['family'],
119
+ task_definition['containerDefinitions']
120
+ )
121
+
122
+ @family_name = result['taskDefinition']['family']
123
+ @revision = result['taskDefinition']['revision']
124
+ @new_task_definition_arn = result['taskDefinition']['taskDefinitionArn']
125
+ end
126
+ end
127
+
128
+ end
@@ -0,0 +1,104 @@
1
+ require 'oj'
2
+ require 'runtime_command'
3
+
4
+ module EcsDeployer
5
+ class Commander
6
+ # @param [String] cluster_name
7
+ # @param [Hash] options
8
+ # @return EcsDeployer::Commander
9
+ def initialize(cluster_name, options = {})
10
+ @runtime = RuntimeCommand::Builder.new
11
+ @options = options
12
+ @cluster_name = cluster_name
13
+ end
14
+
15
+ # @param [String] service_name
16
+ # @param [String] family_name
17
+ # @param [Fixnum] revision
18
+ # @return [Hash]
19
+ def update_service(service_name, family_name, revision)
20
+ args = {
21
+ 'cluster': @cluster_name,
22
+ 'service': service_name,
23
+ 'task-definition': family_name + ':' + revision.to_s
24
+ }
25
+
26
+ exec('update-service', args)
27
+ end
28
+
29
+ # @param [String] service_name
30
+ # @return [Hash]
31
+ def list_tasks(service_name)
32
+ args = {
33
+ 'cluster': @cluster_name,
34
+ 'service-name': service_name,
35
+ 'desired-status': 'RUNNING'
36
+ }
37
+ exec('list-tasks', args)
38
+ end
39
+
40
+ # @param [Array] tasks
41
+ # @return [Hash]
42
+ def describe_tasks(tasks)
43
+ args = {
44
+ 'cluster': @cluster_name,
45
+ 'tasks': tasks.join(' ')
46
+ }
47
+ exec('describe-tasks', args)
48
+ end
49
+
50
+ # @param [String] task_definition
51
+ # @return [Hash]
52
+ def describe_task_definition(task_definition)
53
+ args = {
54
+ 'task-definition': task_definition
55
+ }
56
+ exec('describe-task-definition', args)
57
+ end
58
+
59
+ # @param [String] service_name
60
+ # @return [Hash]
61
+ def describe_services(service_name)
62
+ args = {
63
+ 'cluster': @cluster_name,
64
+ 'services': service_name
65
+ }
66
+ exec('describe-services', args)
67
+ end
68
+
69
+ # @param [String] family_name
70
+ # @param [Hash] container_definitions
71
+ # @return [Hash]
72
+ def register_task_definition(family_name, container_definitions)
73
+ args = {
74
+ 'family': family_name,
75
+ 'container-definitions': '"' + Oj.dump(container_definitions).gsub('"', '\\"') + '"'
76
+ }
77
+ exec('register-task-definition', args)
78
+ end
79
+
80
+ private
81
+ # @param [String] command
82
+ # @param [Hash] args
83
+ # @return [Hash]
84
+ def exec(command, args)
85
+ arg = ''
86
+ args.each do |name, value|
87
+ arg << "--#{name} #{value} "
88
+ end
89
+
90
+ arg << "--profile #{@options[:profile]} " if @options.has_key?(:profile)
91
+ arg << "--region #{@options[:region]} " if @options.has_key?(:region)
92
+
93
+ command = "aws ecs #{command} #{arg}"
94
+ result = @runtime.exec(command)
95
+
96
+ raise EcsCommandError.new unless result.buffered_stderr.empty?
97
+
98
+ result = result.buffered_stdout
99
+ Oj.load(result)
100
+ end
101
+ end
102
+
103
+ class EcsCommandError < RuntimeError; end
104
+ end
@@ -0,0 +1,4 @@
1
+ module EcsDeployer
2
+ class Error < RuntimeError
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module EcsDeployer
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,9 @@
1
+ require 'ecs_deployer/version'
2
+ require 'ecs_deployer/client'
3
+ require 'ecs_deployer/error'
4
+
5
+ module EcsDeployer
6
+ class ServiceNotFoundError < EcsDeployer::Error; end
7
+ class TaskNotFoundError < EcsDeployer::Error; end
8
+ class DeployTimeoutError < EcsDeployer::Error; end
9
+ end
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ecs_deployer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - naomichi-y
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-02-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: runtime_command
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: oj
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.14'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.14'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ description: This package provides the service deployment function of ECS.
84
+ email:
85
+ - n.yamakita@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".rspec"
92
+ - ".travis.yml"
93
+ - Gemfile
94
+ - README.md
95
+ - Rakefile
96
+ - bin/console
97
+ - bin/setup
98
+ - ecs_deployer.gemspec
99
+ - example/fixtures/task.yml
100
+ - example/sample.rb
101
+ - lib/ecs_deployer.rb
102
+ - lib/ecs_deployer/client.rb
103
+ - lib/ecs_deployer/commander.rb
104
+ - lib/ecs_deployer/error.rb
105
+ - lib/ecs_deployer/version.rb
106
+ homepage: ''
107
+ licenses: []
108
+ metadata: {}
109
+ post_install_message:
110
+ rdoc_options: []
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 2.6.10
126
+ signing_key:
127
+ specification_version: 4
128
+ summary: Deploy application to ECS.
129
+ test_files: []