hako 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b7b7ad2ffbeb17c94a9e690bf278bb4019852b3e
4
- data.tar.gz: 27024dcecc995a1acc893e3072029cd693fd8e0a
3
+ metadata.gz: 41e144225804bd958a9c8be85ad1f2998cc3a70b
4
+ data.tar.gz: 746df8720d4697f12ed9b7fd66d73699af39e8c1
5
5
  SHA512:
6
- metadata.gz: 874b5c28b65dd52d728c83a441f23d057807d1f445fe9edc5a5b44200eeb9b8fbcfd9770568a86f2ecee30686dcc389e713a2baed9dfef4e7ade3c05633905f1
7
- data.tar.gz: 3218b993bcc4d924f0e6aaaf72a74dc84e2d97f07babcea01295c25625e5fcd13bcb9fad70ea254d30518e42778a14afc17d5df95cec657ddbd7d49f5909748c
6
+ metadata.gz: 47a8418855c3fe25132a0033375a00628df9cb07385e6ee1ccb50c6d18c1e14ae0deb1f9284cf32d354568631f7fbfe67d84a1a12b95e6ce1823d43dd48e7d92
7
+ data.tar.gz: d4274e025153b4cebaac5e6b4d81ef05443afa9a497cccb54b77cf54a8612decf9be2dc5d17980dae692e08cfa112e4f640122f6cf739dded359005657ebfdde
@@ -0,0 +1,40 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
3
+ AllCops:
4
+ DisplayCopNames: true
5
+
6
+ Style/GuardClause:
7
+ Enabled: false
8
+
9
+ Style/HashSyntax:
10
+ Exclude:
11
+ - 'Rakefile'
12
+
13
+ Style/IfUnlessModifier:
14
+ Enabled: false
15
+
16
+ Style/Next:
17
+ Enabled: false
18
+
19
+ Style/NumericLiterals:
20
+ Enabled: false
21
+
22
+ Style/PercentLiteralDelimiters:
23
+ PreferredDelimiters:
24
+ '%w': '[]'
25
+ '%i': '[]'
26
+
27
+ Style/RaiseArgs:
28
+ EnforcedStyle: compact
29
+
30
+ Style/SignalException:
31
+ Enabled: false
32
+
33
+ Style/TrailingCommaInArguments:
34
+ Enabled: false
35
+
36
+ Style/TrailingCommaInLiteral:
37
+ Enabled: false
38
+
39
+ Performance/RedundantBlockCall:
40
+ Enabled: false
@@ -0,0 +1,23 @@
1
+ Metrics/AbcSize:
2
+ Enabled: false
3
+
4
+ Metrics/ClassLength:
5
+ Enabled: false
6
+
7
+ Metrics/CyclomaticComplexity:
8
+ Enabled: false
9
+
10
+ Metrics/LineLength:
11
+ Enabled: false
12
+
13
+ Metrics/MethodLength:
14
+ Enabled: false
15
+
16
+ Metrics/PerceivedComplexity:
17
+ Enabled: false
18
+
19
+ Metrics/BlockNesting:
20
+ Enabled: false
21
+
22
+ Style/Documentation:
23
+ Enabled: false
@@ -1,4 +1,11 @@
1
1
  language: ruby
2
+ sudo: false
2
3
  rvm:
3
- - 2.2.3
4
- before_install: gem install bundler -v 1.10.6
4
+ - 2.1
5
+ - 2.2
6
+ - ruby-head
7
+ before_install:
8
+ - gem install bundler
9
+ matrix:
10
+ allow_failures:
11
+ - rvm: ruby-head
data/README.md CHANGED
@@ -1,4 +1,7 @@
1
1
  # Hako
2
+ [![Gem Version](https://badge.fury.io/rb/hako.svg)](http://badge.fury.io/rb/hako)
3
+ [![Build Status](https://travis-ci.org/eagletmt/hako.svg)](https://travis-ci.org/eagletmt/hako)
4
+
2
5
  Deploy Docker container.
3
6
 
4
7
  ## Status
@@ -36,7 +39,7 @@ I, [2015-10-02T12:56:12.262760 #8141] INFO -- : Deployment isn't needed
36
39
  Load balancer:
37
40
  hako-hello-XXXXXXXXXX.ap-northeast-1.elb.amazonaws.com:80 -> front:80
38
41
  Deployments:
39
- [PRIMARY] desired_count=2, pending_count=0, running_count=2
42
+ [PRIMARY] hello:30 desired_count=2, pending_count=0, running_count=2
40
43
  Tasks:
41
44
  [RUNNING]: i-XXXXXXXX (ecs-001)
42
45
  [RUNNING]: i-YYYYYYYY (ecs-002)
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
3
4
 
4
5
  RSpec::Core::RakeTask.new(:spec)
6
+ RuboCop::RakeTask.new(:rubocop)
5
7
 
6
- task :default => :spec
8
+ task :default => [:spec, :rubocop]
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "bundler/setup"
4
- require "hako"
3
+ require 'bundler/setup'
4
+ require 'hako'
5
5
 
6
6
  # You can add fixtures and/or initialization code here to make experimenting
7
7
  # with your gem easier. You can also use a different console, if you like.
@@ -10,5 +10,5 @@ require "hako"
10
10
  # require "pry"
11
11
  # Pry.start
12
12
 
13
- require "irb"
13
+ require 'irb'
14
14
  IRB.start
@@ -17,6 +17,7 @@ scheduler:
17
17
  elb:
18
18
  listeners:
19
19
  - load_balancer_port: 80
20
+ protocol: HTTP
20
21
  subnets:
21
22
  - subnet-XXXXXXXX
22
23
  - subnet-YYYYYYYY
@@ -29,3 +30,8 @@ front:
29
30
  region: ap-northeast-1
30
31
  bucket: nanika
31
32
  prefix: hako/front_config
33
+ extra:
34
+ locations:
35
+ /:
36
+ allow_only_from:
37
+ - 10.0.0.0/24
@@ -2,7 +2,7 @@ image: ryotarai/hello-sinatra
2
2
  env:
3
3
  $providers:
4
4
  - type: file
5
- path: examples/hello.env
5
+ path: hello.env
6
6
  PORT: 3000
7
7
  MESSAGE: '#{username}-san'
8
8
  port: 3000
@@ -4,24 +4,25 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'hako/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "hako"
7
+ spec.name = 'hako'
8
8
  spec.version = Hako::VERSION
9
- spec.authors = ["Kohei Suzuki"]
10
- spec.email = ["eagletmt@gmail.com"]
9
+ spec.authors = ['Kohei Suzuki']
10
+ spec.email = ['eagletmt@gmail.com']
11
11
 
12
- spec.summary = %q{Deploy Docker container}
13
- spec.description = %q{Deploy Docker container}
14
- spec.homepage = "https://github.com/eagletmt/hako"
12
+ spec.summary = 'Deploy Docker container'
13
+ spec.description = 'Deploy Docker container'
14
+ spec.homepage = 'https://github.com/eagletmt/hako'
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
- spec.bindir = "exe"
17
+ spec.bindir = 'exe'
18
18
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
- spec.require_paths = ["lib"]
19
+ spec.require_paths = ['lib']
20
20
 
21
- spec.add_dependency "aws-sdk", "~> 2.1.0"
22
- spec.add_dependency "thor"
21
+ spec.add_dependency 'aws-sdk', '>= 2.1.0'
22
+ spec.add_dependency 'thor'
23
23
 
24
- spec.add_development_dependency "bundler"
25
- spec.add_development_dependency "rake"
26
- spec.add_development_dependency "rspec"
24
+ spec.add_development_dependency 'bundler'
25
+ spec.add_development_dependency 'rake'
26
+ spec.add_development_dependency 'rspec'
27
+ spec.add_development_dependency 'rubocop', '>= 0.36.0'
27
28
  end
@@ -1,8 +1,12 @@
1
1
  require 'logger'
2
- require "hako/version"
2
+ require 'hako/version'
3
3
 
4
4
  module Hako
5
5
  def self.logger
6
- @logger ||= Logger.new($stdout)
6
+ @logger ||=
7
+ begin
8
+ $stdout.sync = true
9
+ Logger.new($stdout)
10
+ end
7
11
  end
8
12
  end
@@ -0,0 +1,4 @@
1
+ module Hako
2
+ module AfterScripts
3
+ end
4
+ end
@@ -0,0 +1,22 @@
1
+ require 'yaml'
2
+
3
+ module Hako
4
+ class Application
5
+ attr_reader :id, :root_path, :yaml
6
+
7
+ def initialize(yaml_path)
8
+ path = Pathname.new(yaml_path)
9
+ @id = path.basename.sub_ext('').to_s
10
+ @root_path = path.parent
11
+ @yaml = YAML.load(load_default_yaml(@root_path) + path.read)
12
+ end
13
+
14
+ private
15
+
16
+ def load_default_yaml(root_path)
17
+ root_path.join('default.yml').read
18
+ rescue Errno::ENOENT
19
+ ''
20
+ end
21
+ end
22
+ end
@@ -4,15 +4,33 @@ module Hako
4
4
  class CLI < Thor
5
5
  desc 'deploy FILE', 'Run deployment'
6
6
  option :force, aliases: %w[-f], type: :boolean, default: false, desc: 'Run deployment even if nothing is changed'
7
+ option :tag, aliases: %w[-t], type: :string, default: 'latest', desc: 'Specify tag (default: latest)'
7
8
  def deploy(yaml_path)
9
+ require 'hako/application'
8
10
  require 'hako/commander'
9
- Commander.new(yaml_path).deploy(force: options[:force])
11
+ Commander.new(Application.new(yaml_path)).deploy(force: options[:force], tag: options[:tag])
12
+ end
13
+
14
+ desc 'oneshot FILE COMMAND ARG...', 'Run oneshot task'
15
+ option :tag, aliases: %w[-t], type: :string, default: 'latest', desc: 'Specify tag (default: latest)'
16
+ def oneshot(yaml_path, command, *args)
17
+ require 'hako/application'
18
+ require 'hako/commander'
19
+ Commander.new(Application.new(yaml_path)).oneshot([command, *args], tag: options[:tag])
10
20
  end
11
21
 
12
22
  desc 'status FILE', 'Show deployment status'
13
23
  def status(yaml_path)
24
+ require 'hako/application'
25
+ require 'hako/commander'
26
+ Commander.new(Application.new(yaml_path)).status
27
+ end
28
+
29
+ desc 'remove FILE', 'Destroy the application'
30
+ def remove(yaml_path)
31
+ require 'hako/application'
14
32
  require 'hako/commander'
15
- Commander.new(yaml_path).status
33
+ Commander.new(Application.new(yaml_path)).remove
16
34
  end
17
35
  end
18
36
  end
@@ -1,4 +1,4 @@
1
- require 'yaml'
1
+ require 'hako/after_scripts'
2
2
  require 'hako/env_expander'
3
3
  require 'hako/error'
4
4
  require 'hako/front_config'
@@ -7,32 +7,53 @@ require 'hako/schedulers'
7
7
 
8
8
  module Hako
9
9
  class Commander
10
- PROVIDERS_KEY = '$providers'
10
+ PROVIDERS_KEY = '$providers'.freeze
11
11
 
12
- def initialize(yaml_path)
13
- @app_id = Pathname.new(yaml_path).basename.sub_ext('').to_s
14
- @yaml = YAML.load_file(yaml_path)
12
+ def initialize(app)
13
+ @app = app
14
+ $LOAD_PATH << @app.root_path.join('lib')
15
15
  end
16
16
 
17
- def deploy(force: false)
18
- env = @yaml['env'].dup
19
- providers = load_providers(env.delete(PROVIDERS_KEY) || [])
20
- env = EnvExpander.new(providers).expand(env)
21
-
22
- front = load_front(@yaml['front'])
17
+ def deploy(force: false, tag: 'latest')
18
+ env = load_environment(@app.yaml['env'])
19
+ front = load_front(@app.yaml['front'])
20
+ scheduler = load_scheduler(@app.yaml['scheduler'])
21
+ app_port = @app.yaml.fetch('port', nil)
22
+ image = @app.yaml.fetch('image') { raise Error.new('image must be set') }
23
+ image_tag = "#{image}:#{tag}"
24
+ after_scripts = @app.yaml.fetch('after_scripts', []).map { |config| load_after_script(config) }
23
25
 
24
- scheduler = load_scheduler(@yaml['scheduler'])
25
- app_port = @yaml.fetch('port', nil)
26
- image_tag = @yaml['image'] # TODO: Append revision
27
26
  scheduler.deploy(image_tag, env, app_port, front, force: force)
27
+
28
+ after_scripts.each(&:after_deploy)
29
+ end
30
+
31
+ def oneshot(commands, tag: 'latest')
32
+ env = load_environment(@app.yaml['env'])
33
+ scheduler = load_scheduler(@app.yaml['scheduler'])
34
+ image = @app.yaml.fetch('image') { raise Error.new('image must be set') }
35
+ image_tag = "#{image}:#{tag}"
36
+ exit scheduler.oneshot(image_tag, env, commands)
28
37
  end
29
38
 
30
39
  def status
31
- load_scheduler(@yaml['scheduler']).status
40
+ load_scheduler(@app.yaml['scheduler']).status
41
+ end
42
+
43
+ def remove
44
+ after_scripts = @app.yaml.fetch('after_scripts', []).map { |config| load_after_script(config) }
45
+ load_scheduler(@app.yaml['scheduler']).remove
46
+ after_scripts.each(&:after_remove)
32
47
  end
33
48
 
34
49
  private
35
50
 
51
+ def load_environment(env)
52
+ env = env.dup
53
+ providers = load_providers(env.delete(PROVIDERS_KEY) || [])
54
+ EnvExpander.new(providers).expand(env)
55
+ end
56
+
36
57
  def load_providers(provider_configs)
37
58
  provider_configs.map do |config|
38
59
  type = config['type']
@@ -40,7 +61,7 @@ module Hako
40
61
  raise Error.new("type must be set in each #{PROVIDERS_KEY} element")
41
62
  end
42
63
  require "hako/env_providers/#{type}"
43
- Hako::EnvProviders.const_get(camelize(type)).new(config)
64
+ Hako::EnvProviders.const_get(camelize(type)).new(@app.root_path, config)
44
65
  end
45
66
  end
46
67
 
@@ -50,7 +71,7 @@ module Hako
50
71
  raise Error.new('type must be set in scheduler')
51
72
  end
52
73
  require "hako/schedulers/#{type}"
53
- Hako::Schedulers.const_get(camelize(type)).new(@app_id, scheduler_config)
74
+ Hako::Schedulers.const_get(camelize(type)).new(@app.id, scheduler_config)
54
75
  end
55
76
 
56
77
  def load_front(yaml)
@@ -59,6 +80,12 @@ module Hako
59
80
  Hako::Fronts.const_get(camelize(front_config.type)).new(front_config)
60
81
  end
61
82
 
83
+ def load_after_script(config)
84
+ type = config.fetch('type')
85
+ require "hako/after_scripts/#{type}"
86
+ Hako::AfterScripts.const_get(camelize(type)).new(@app, config)
87
+ end
88
+
62
89
  def camelize(name)
63
90
  name.split('_').map(&:capitalize).join('')
64
91
  end
@@ -8,11 +8,8 @@ module Hako
8
8
  class ExpansionError < Error
9
9
  end
10
10
 
11
- class Literal < Struct.new(:literal)
12
- end
13
-
14
- class Variable < Struct.new(:name)
15
- end
11
+ Literal = Struct.new(:literal)
12
+ Variable = Struct.new(:name)
16
13
 
17
14
  def initialize(providers)
18
15
  @providers = providers
@@ -59,13 +56,13 @@ module Hako
59
56
  tokens = []
60
57
  pos = 0
61
58
  while s.scan_until(/#\{(.*?)\}/)
62
- pre = s.string.byteslice(pos ... (s.pos - s.matched.size))
59
+ pre = s.string.byteslice(pos...(s.pos - s.matched.size))
63
60
  var = s[1]
64
61
  unless pre.empty?
65
62
  tokens << Literal.new(pre)
66
63
  end
67
64
  if var.empty?
68
- raise ExpansionError.new("Empty interpolation is not allowed")
65
+ raise ExpansionError.new('Empty interpolation is not allowed')
69
66
  else
70
67
  tokens << Variable.new(var)
71
68
  end
@@ -5,7 +5,7 @@ module Hako
5
5
  class ValidationError < Error
6
6
  end
7
7
 
8
- def initialize(_options)
8
+ def initialize(_root_path, _options)
9
9
  raise NotImplementedError
10
10
  end
11
11
 
@@ -3,11 +3,11 @@ require 'hako/env_provider'
3
3
  module Hako
4
4
  module EnvProviders
5
5
  class File < EnvProvider
6
- def initialize(options)
6
+ def initialize(root_path, options)
7
7
  unless options['path']
8
- validation_error!("path must be set")
8
+ validation_error!('path must be set')
9
9
  end
10
- @path = options['path']
10
+ @path = root_path.join(options['path'])
11
11
  end
12
12
 
13
13
  def ask(variables)
@@ -1,8 +1,9 @@
1
1
  require 'erb'
2
2
 
3
3
  module Hako
4
- class FrontConfig < Struct.new(:type, :image_tag, :s3)
5
- class S3Config < Struct.new(:region, :bucket, :prefix)
4
+ FrontConfig = Struct.new(:type, :image_tag, :s3, :extra)
5
+ class FrontConfig
6
+ S3Config = Struct.new(:region, :bucket, :prefix) do
6
7
  def initialize(options)
7
8
  self.region = options.fetch('region')
8
9
  self.bucket = options.fetch('bucket')
@@ -22,6 +23,7 @@ module Hako
22
23
  self.type = options.fetch('type')
23
24
  self.image_tag = options.fetch('image_tag')
24
25
  self.s3 = S3Config.new(options.fetch('s3'))
26
+ self.extra = options.fetch('extra', {})
25
27
  end
26
28
  end
27
29
  end
@@ -11,8 +11,48 @@ module Hako
11
11
 
12
12
  private
13
13
 
14
+ def templates_directory
15
+ File.expand_path('../../templates', __FILE__)
16
+ end
17
+
14
18
  def nginx_conf_erb
15
- File.expand_path('../../templates/nginx.conf.erb', __FILE__)
19
+ File.join(templates_directory, 'nginx.conf.erb')
20
+ end
21
+
22
+ def nginx_location_conf_erb
23
+ File.join(templates_directory, 'nginx.location.conf.erb')
24
+ end
25
+
26
+ def locations
27
+ locs = @config.extra.fetch('locations', {}).dup
28
+ locs['/'] ||= {}
29
+ locs.keys.each do |k|
30
+ locs[k] = Location.new(locs[k])
31
+ end
32
+ locs
33
+ end
34
+
35
+ def client_max_body_size
36
+ @config.extra.fetch('client_max_body_size', nil)
37
+ end
38
+
39
+ def render_location(listen_spec, location)
40
+ ERB.new(File.read(nginx_location_conf_erb), nil, '-').result(binding).each_line.map do |line|
41
+ " #{line}"
42
+ end.join('')
43
+ end
44
+
45
+ class Location
46
+ def initialize(config)
47
+ @config = config
48
+ end
49
+
50
+ def allow_only_from
51
+ allow = @config.fetch('allow_only_from', nil)
52
+ if allow
53
+ allow.flatten
54
+ end
55
+ end
16
56
  end
17
57
  end
18
58
  end
@@ -16,6 +16,10 @@ module Hako
16
16
  raise NotImplementedError
17
17
  end
18
18
 
19
+ def remove
20
+ raise NotImplementedError
21
+ end
22
+
19
23
  def upload_front_config(app_id, front, app_port)
20
24
  front_conf = front.generate_config(app_port)
21
25
  s3_config = front.config.s3
@@ -0,0 +1,28 @@
1
+ require 'hako/scheduler'
2
+
3
+ module Hako
4
+ module Schedulers
5
+ class Echo < Scheduler
6
+ def initialize(app_id, _options)
7
+ @app_id = app_id
8
+ end
9
+
10
+ def deploy(image_tag, env, app_port, _front, force: false)
11
+ puts "Deploy #{image_tag} with app_port=#{app_port}, force=#{force}"
12
+ puts 'Environment variables:'
13
+ env.each do |key, val|
14
+ puts " #{key}=#{val.inspect}"
15
+ end
16
+ end
17
+
18
+ def oneshot(image_tag, env, commands)
19
+ puts "Run #{image_tag} with oneshot commands=#{commands.inspect}"
20
+ puts 'Environment variables:'
21
+ env.each do |key, val|
22
+ puts " #{key}=#{val.inspect}"
23
+ end
24
+ 0
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,13 +1,13 @@
1
1
  require 'aws-sdk'
2
2
  require 'hako'
3
- require 'hako/error'
4
3
  require 'hako/scheduler'
5
4
  require 'hako/schedulers/ecs_definition_comparator'
5
+ require 'hako/schedulers/ecs_elb'
6
6
 
7
7
  module Hako
8
8
  module Schedulers
9
9
  class Ecs < Scheduler
10
- DEFAULT_CLUSTER = 'default'
10
+ DEFAULT_CLUSTER = 'default'.freeze
11
11
  DEFAULT_FRONT_PORT = 10000
12
12
 
13
13
  def initialize(app_id, options)
@@ -19,9 +19,8 @@ module Hako
19
19
  region = options.fetch('region') { validation_error!('region must be set') }
20
20
  @role = options.fetch('role', nil)
21
21
  @ecs = Aws::ECS::Client.new(region: region)
22
- @elb = Aws::ElasticLoadBalancing::Client.new(region: region)
22
+ @elb = EcsElb.new(app_id, Aws::ElasticLoadBalancing::Client.new(region: region), options.fetch('elb', nil))
23
23
  @ec2 = Aws::EC2::Client.new(region: region)
24
- @elb_config = options.fetch('elb', nil)
25
24
  end
26
25
 
27
26
  def deploy(image_tag, env, app_port, front, force: false)
@@ -31,7 +30,7 @@ module Hako
31
30
  'S3_CONFIG_BUCKET' => front.config.s3.bucket,
32
31
  'S3_CONFIG_KEY' => front.config.s3.key(@app_id),
33
32
  }
34
- front_port = determine_front_port(front)
33
+ front_port = determine_front_port
35
34
  task_definition = register_task_definition(image_tag, env, front.config, front_env, front_port)
36
35
  if task_definition == :noop
37
36
  Hako.logger.info "Task definition isn't changed"
@@ -48,11 +47,21 @@ module Hako
48
47
  Hako.logger.info "Updated service: #{service.service_arn}"
49
48
  wait_for_ready(service)
50
49
  end
51
- Hako.logger.info "Deployment completed"
50
+ Hako.logger.info 'Deployment completed'
51
+ end
52
+
53
+ def oneshot(image_tag, env, commands)
54
+ task_definition = register_task_definition_for_oneshot(image_tag)
55
+ Hako.logger.info "Registered task definition: #{task_definition.task_definition_arn}"
56
+ task = run_task(task_definition, env, commands)
57
+ Hako.logger.info "Started task: #{task.task_arn}"
58
+ exit_code = wait_for_task(task)
59
+ Hako.logger.info 'Oneshot task finished'
60
+ exit_code
52
61
  end
53
62
 
54
63
  def status
55
- service = @ecs.describe_services(cluster: @cluster, services: [@app_id]).services[0]
64
+ service = describe_service
56
65
  unless service
57
66
  puts 'Unavailable'
58
67
  exit 1
@@ -60,7 +69,7 @@ module Hako
60
69
 
61
70
  unless service.load_balancers.empty?
62
71
  lb = service.load_balancers[0]
63
- lb_detail = @elb.describe_load_balancers(load_balancer_names: [lb.load_balancer_name]).load_balancer_descriptions[0]
72
+ lb_detail = @elb.describe_load_balancer
64
73
  puts 'Load balancer:'
65
74
  lb_detail.listener_descriptions.each do |ld|
66
75
  l = ld.listener
@@ -70,11 +79,12 @@ module Hako
70
79
 
71
80
  puts 'Deployments:'
72
81
  service.deployments.each do |d|
73
- puts " [#{d.status}] desired_count=#{d.desired_count}, pending_count=#{d.pending_count}, running_count=#{d.running_count}"
82
+ abbrev_task_definition = d.task_definition.slice(%r{task-definition/(.+)\z}, 1)
83
+ puts " [#{d.status}] #{abbrev_task_definition} desired_count=#{d.desired_count}, pending_count=#{d.pending_count}, running_count=#{d.running_count}"
74
84
  end
75
85
 
76
86
  puts 'Tasks:'
77
- @ecs.list_tasks(cluster: @cluster, service_name: @app_id).each do |page|
87
+ @ecs.list_tasks(cluster: @cluster, service_name: service.service_arn).each do |page|
78
88
  unless page.task_arns.empty?
79
89
  tasks = @ecs.describe_tasks(cluster: @cluster, tasks: page.task_arns).tasks
80
90
  container_instances = {}
@@ -108,25 +118,49 @@ module Hako
108
118
  end
109
119
  end
110
120
 
121
+ def remove
122
+ service = describe_service
123
+ if service
124
+ @ecs.delete_service(cluster: @cluster, service: @app_id)
125
+ Hako.logger.info "#{service.service_arn} is deleted"
126
+ else
127
+ puts "Service #{@app_id} doesn't exist"
128
+ end
129
+
130
+ @elb.destroy
131
+ end
132
+
111
133
  private
112
134
 
113
- def determine_front_port(front)
135
+ def describe_service
114
136
  service = @ecs.describe_services(cluster: @cluster, services: [@app_id]).services[0]
137
+ if service && service.status != 'INACTIVE'
138
+ service
139
+ end
140
+ end
141
+
142
+ def determine_front_port
143
+ service = describe_service
115
144
  if service
116
145
  find_front_port(service)
117
146
  else
118
147
  max_port = -1
119
148
  @ecs.list_services(cluster: @cluster).each do |page|
120
149
  unless page.service_arns.empty?
121
- @ecs.describe_services(cluster: @cluster, services: page.service_arns).services.each do |service|
122
- max_port = [max_port, find_front_port(service)].max
150
+ @ecs.describe_services(cluster: @cluster, services: page.service_arns).services.each do |s|
151
+ if s.status != 'INACTIVE'
152
+ port = find_front_port(s)
153
+ if port
154
+ max_port = [max_port, port].max
155
+ end
156
+ end
123
157
  end
124
158
  end
125
159
  end
126
160
  if max_port == -1
127
161
  DEFAULT_FRONT_PORT
128
162
  else
129
- max_port+1
163
+ max_port + 1
130
164
  end
131
165
  end
132
166
  end
@@ -137,7 +171,9 @@ module Hako
137
171
  task_definition.container_definitions.each do |c|
138
172
  container_definitions[c.name] = c
139
173
  end
140
- container_definitions['front'].port_mappings[0].host_port
174
+ if container_definitions.size == 2 && container_definitions['front'] && container_definitions['app']
175
+ container_definitions['front'].port_mappings[0].host_port
176
+ end
141
177
  end
142
178
 
143
179
  def task_definition_changed?(front, app)
@@ -172,6 +208,23 @@ module Hako
172
208
  end
173
209
  end
174
210
 
211
+ def register_task_definition_for_oneshot(image_tag)
212
+ @ecs.register_task_definition(
213
+ family: "#{@app_id}-oneshot",
214
+ container_definitions: [
215
+ {
216
+ name: 'oneshot',
217
+ image: image_tag,
218
+ cpu: @cpu,
219
+ memory: @memory,
220
+ links: [],
221
+ port_mappings: [],
222
+ environment: [],
223
+ },
224
+ ],
225
+ ).task_definition
226
+ end
227
+
175
228
  def front_container(front_config, env, front_port)
176
229
  environment = env.map { |k, v| { name: k, value: v } }
177
230
  {
@@ -180,7 +233,7 @@ module Hako
180
233
  cpu: 100,
181
234
  memory: 100,
182
235
  links: ['app:app'],
183
- port_mappings: [{container_port: 80, host_port: front_port, protocol: 'tcp'}],
236
+ port_mappings: [{ container_port: 80, host_port: front_port, protocol: 'tcp' }],
184
237
  essential: true,
185
238
  environment: environment,
186
239
  }
@@ -200,9 +253,69 @@ module Hako
200
253
  }
201
254
  end
202
255
 
256
+ def run_task(task_definition, env, commands)
257
+ environment = env.map { |k, v| { name: k, value: v } }
258
+ @ecs.run_task(
259
+ cluster: @cluster,
260
+ task_definition: task_definition.task_definition_arn,
261
+ overrides: {
262
+ container_overrides: [
263
+ {
264
+ name: 'oneshot',
265
+ command: commands,
266
+ environment: environment,
267
+ },
268
+ ],
269
+ },
270
+ count: 1,
271
+ started_by: "hako oneshot #{@app_id}",
272
+ ).tasks[0]
273
+ end
274
+
275
+ def wait_for_task(task)
276
+ task_arn = task.task_arn
277
+ container_instance_arn = nil
278
+ started_at = nil
279
+ loop do
280
+ task = @ecs.describe_tasks(cluster: @cluster, tasks: [task_arn]).tasks[0]
281
+ if container_instance_arn != task.container_instance_arn
282
+ container_instance_arn = task.container_instance_arn
283
+ report_container_instance(container_instance_arn)
284
+ end
285
+ unless started_at
286
+ started_at = task.started_at
287
+ if started_at
288
+ Hako.logger.info "Started at #{started_at}"
289
+ end
290
+ end
291
+
292
+ Hako.logger.info " status #{task.last_status}"
293
+
294
+ if task.last_status == 'STOPPED'
295
+ Hako.logger.info "Stopped at #{task.stopped_at}"
296
+ container = task.containers[0]
297
+ Hako.logger.info "Exit code is #{container.exit_code}"
298
+ return container.exit_code
299
+ end
300
+ sleep 1
301
+ end
302
+ end
303
+
304
+ def report_container_instance(container_instance_arn)
305
+ container_instance = @ecs.describe_container_instances(cluster: @cluster, container_instances: [container_instance_arn]).container_instances[0]
306
+ @ec2.describe_tags(filters: [{ name: 'resource-id', values: [container_instance.ec2_instance_id] }]).each do |page|
307
+ tag = page.tags.find { |t| t.key == 'Name' }
308
+ if tag
309
+ Hako.logger.info "Container instance is #{container_instance_arn} (#{tag.value} #{container_instance.ec2_instance_id})"
310
+ else
311
+ Hako.logger.info "Container instance is #{container_instance_arn} (#{container_instance.ec2_instance_id})"
312
+ end
313
+ end
314
+ end
315
+
203
316
  def create_or_update_service(task_definition_arn, front_port)
204
- services = @ecs.describe_services(cluster: @cluster, services: [@app_id]).services
205
- if services.empty?
317
+ service = describe_service
318
+ if service.nil?
206
319
  params = {
207
320
  cluster: @cluster,
208
321
  service_name: @app_id,
@@ -210,24 +323,18 @@ module Hako
210
323
  desired_count: @desired_count,
211
324
  role: @role,
212
325
  }
213
- if @elb_config
214
- name = find_or_create_load_balancer(front_port)
215
- params.merge!(
216
- load_balancers: [
217
- {
218
- load_balancer_name: name,
219
- container_name: 'front',
220
- container_port: 80,
221
- },
222
- ],
223
- )
326
+ name = @elb.find_or_create_load_balancer(front_port)
327
+ if name
328
+ params[:load_balancers] = [
329
+ {
330
+ load_balancer_name: name,
331
+ container_name: 'front',
332
+ container_port: 80,
333
+ },
334
+ ]
224
335
  end
225
336
  @ecs.create_service(params).service
226
337
  else
227
- service = services[0]
228
- if service.status != 'ACTIVE'
229
- raise Error.new("Service #{service.service_arn} is already exist but the status is #{service.status}")
230
- end
231
338
  params = {
232
339
  cluster: @cluster,
233
340
  service: @app_id,
@@ -242,7 +349,7 @@ module Hako
242
349
  end
243
350
  end
244
351
 
245
- SERVICE_KEYS = %i[desired_count task_definition]
352
+ SERVICE_KEYS = %i[desired_count task_definition].freeze
246
353
 
247
354
  def service_changed?(service, params)
248
355
  SERVICE_KEYS.each do |key|
@@ -254,7 +361,7 @@ module Hako
254
361
  end
255
362
 
256
363
  def wait_for_ready(service)
257
- latest_event_id = service.events[0].id
364
+ latest_event_id = find_latest_event_id(service.events)
258
365
  loop do
259
366
  s = @ecs.describe_services(cluster: service.cluster_arn, services: [service.service_arn]).services[0]
260
367
  s.events.each do |e|
@@ -263,7 +370,7 @@ module Hako
263
370
  end
264
371
  Hako.logger.info "#{e.created_at}: #{e.message}"
265
372
  end
266
- latest_event_id = s.events[0].id
373
+ latest_event_id = find_latest_event_id(s.events)
267
374
  finished = s.deployments.all? { |d| d.status != 'ACTIVE' }
268
375
  if finished
269
376
  return
@@ -273,37 +380,12 @@ module Hako
273
380
  end
274
381
  end
275
382
 
276
- def find_or_create_load_balancer(front_port)
277
- unless load_balancer_exist?(elb_name)
278
- listeners = @elb_config.fetch('listeners').map do |l|
279
- {
280
- protocol: 'tcp',
281
- load_balancer_port: l.fetch('load_balancer_port'),
282
- instance_port: front_port,
283
- ssl_certificate_id: l.fetch('ssl_certificate_id', nil),
284
- }
285
- end
286
- lb = @elb.create_load_balancer(
287
- load_balancer_name: elb_name,
288
- listeners: listeners,
289
- subnets: @elb_config.fetch('subnets'),
290
- security_groups: @elb_config.fetch('security_groups'),
291
- tags: @elb_config.fetch('tags', {}).map { |k, v| { key: k, value: v.to_s } },
292
- )
293
- Hako.logger.info "Created ELB #{lb.dns_name} with instance_port=#{front_port}"
383
+ def find_latest_event_id(events)
384
+ if events.empty?
385
+ nil
386
+ else
387
+ events[0].id
294
388
  end
295
- elb_name
296
- end
297
-
298
- def load_balancer_exist?(name)
299
- @elb.describe_load_balancers(load_balancer_names: [elb_name])
300
- true
301
- rescue Aws::ElasticLoadBalancing::Errors::LoadBalancerNotFound
302
- false
303
- end
304
-
305
- def elb_name
306
- "hako-#{@app_id}"
307
389
  end
308
390
  end
309
391
  end
@@ -5,9 +5,9 @@ module Hako
5
5
  @expected_container = expected_container
6
6
  end
7
7
 
8
- CONTAINER_KEYS = %i[image cpu memory links]
9
- PORT_MAPPING_KEYS = %i[container_port host_port protocol]
10
- ENVIRONMENT_KEYS = %i[name value]
8
+ CONTAINER_KEYS = %i[image cpu memory links].freeze
9
+ PORT_MAPPING_KEYS = %i[container_port host_port protocol].freeze
10
+ ENVIRONMENT_KEYS = %i[name value].freeze
11
11
 
12
12
  def different?(actual_container)
13
13
  unless actual_container
@@ -0,0 +1,64 @@
1
+ require 'aws-sdk'
2
+ require 'hako'
3
+
4
+ module Hako
5
+ module Schedulers
6
+ class EcsElb
7
+ def initialize(app_id, elb, elb_config)
8
+ @app_id = app_id
9
+ @elb = elb
10
+ @elb_config = elb_config
11
+ end
12
+
13
+ def describe_load_balancer
14
+ @elb.describe_load_balancers(load_balancer_names: [name]).load_balancer_descriptions[0]
15
+ end
16
+
17
+ def find_or_create_load_balancer(front_port)
18
+ if @elb_config
19
+ unless exist?
20
+ listeners = @elb_config.fetch('listeners').map do |l|
21
+ {
22
+ protocol: l.fetch('protocol'),
23
+ load_balancer_port: l.fetch('load_balancer_port'),
24
+ instance_port: front_port,
25
+ ssl_certificate_id: l.fetch('ssl_certificate_id', nil),
26
+ }
27
+ end
28
+ lb = @elb.create_load_balancer(
29
+ load_balancer_name: name,
30
+ listeners: listeners,
31
+ subnets: @elb_config.fetch('subnets'),
32
+ security_groups: @elb_config.fetch('security_groups'),
33
+ tags: @elb_config.fetch('tags', {}).map { |k, v| { key: k, value: v.to_s } },
34
+ )
35
+ Hako.logger.info "Created ELB #{lb.dns_name} with instance_port=#{front_port}"
36
+ end
37
+ name
38
+ end
39
+ end
40
+
41
+ def destroy
42
+ if exist?
43
+ @elb.delete_load_balancer(load_balancer_name: name)
44
+ Hako.logger.info "Deleted ELB #{name}"
45
+ else
46
+ Hako.logger.info "ELB #{name} doesn't exist"
47
+ end
48
+ end
49
+
50
+ def exist?
51
+ describe_load_balancer
52
+ true
53
+ rescue Aws::ElasticLoadBalancing::Errors::LoadBalancerNotFound
54
+ false
55
+ end
56
+
57
+ private
58
+
59
+ def name
60
+ "hako-#{@app_id}"
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,13 +1,13 @@
1
1
  server {
2
2
  listen 80;
3
3
 
4
- location / {
5
- proxy_pass http://<%= listen_spec %>;
6
- proxy_set_header Host $host;
7
- proxy_set_header Connection ""; # for upstream keepalive
8
- proxy_http_version 1.1; # for upstream keepalive
9
- proxy_connect_timeout 5s;
10
- proxy_send_timeout 20s;
11
- proxy_read_timeout 20s;
4
+ <%- if client_max_body_size -%>
5
+ client_max_body_size <%= client_max_body_size %>;
6
+ <%- end -%>
7
+
8
+ <%- locations.each do |path, location| -%>
9
+ location <%= path %> {
10
+ <%= render_location(listen_spec, location) %>
12
11
  }
12
+ <%- end -%>
13
13
  }
@@ -0,0 +1,14 @@
1
+ <%- if location.allow_only_from -%>
2
+ <%- location.allow_only_from.each do |ip| -%>
3
+ allow <%= ip %>;
4
+ <%- end -%>
5
+ deny all;
6
+ <%- end -%>
7
+
8
+ proxy_pass http://<%= listen_spec %>;
9
+ proxy_set_header Host $host;
10
+ proxy_set_header Connection ""; # for upstream keepalive
11
+ proxy_http_version 1.1; # for upstream keepalive
12
+ proxy_connect_timeout 5s;
13
+ proxy_send_timeout 20s;
14
+ proxy_read_timeout 20s;
@@ -1,3 +1,3 @@
1
1
  module Hako
2
- VERSION = "0.1.0"
2
+ VERSION = '0.2.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,27 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hako
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kohei Suzuki
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-10-05 00:00:00.000000000 Z
11
+ date: 2016-01-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: 2.1.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 2.1.0
27
27
  - !ruby/object:Gem::Dependency
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 0.36.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: 0.36.0
83
97
  description: Deploy Docker container
84
98
  email:
85
99
  - eagletmt@gmail.com
@@ -90,6 +104,8 @@ extra_rdoc_files: []
90
104
  files:
91
105
  - ".gitignore"
92
106
  - ".rspec"
107
+ - ".rubocop.yml"
108
+ - ".rubocop_todo.yml"
93
109
  - ".travis.yml"
94
110
  - Gemfile
95
111
  - README.md
@@ -102,6 +118,8 @@ files:
102
118
  - exe/hako
103
119
  - hako.gemspec
104
120
  - lib/hako.rb
121
+ - lib/hako/after_scripts.rb
122
+ - lib/hako/application.rb
105
123
  - lib/hako/cli.rb
106
124
  - lib/hako/commander.rb
107
125
  - lib/hako/env_expander.rb
@@ -115,9 +133,12 @@ files:
115
133
  - lib/hako/fronts/nginx.rb
116
134
  - lib/hako/scheduler.rb
117
135
  - lib/hako/schedulers.rb
136
+ - lib/hako/schedulers/echo.rb
118
137
  - lib/hako/schedulers/ecs.rb
119
138
  - lib/hako/schedulers/ecs_definition_comparator.rb
139
+ - lib/hako/schedulers/ecs_elb.rb
120
140
  - lib/hako/templates/nginx.conf.erb
141
+ - lib/hako/templates/nginx.location.conf.erb
121
142
  - lib/hako/version.rb
122
143
  homepage: https://github.com/eagletmt/hako
123
144
  licenses: []