hako 0.1.0 → 0.2.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 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: []