docker-armada 2.0.53

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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +5 -0
  5. data/LICENSE +20 -0
  6. data/README.md +293 -0
  7. data/Rakefile +9 -0
  8. data/Thorfile +1 -0
  9. data/armada.gemspec +37 -0
  10. data/bin/armada +5 -0
  11. data/lib/armada.rb +37 -0
  12. data/lib/armada/clean.rb +2 -0
  13. data/lib/armada/clean/containers.rb +30 -0
  14. data/lib/armada/clean/images.rb +29 -0
  15. data/lib/armada/cli.rb +40 -0
  16. data/lib/armada/cli/clean.rb +23 -0
  17. data/lib/armada/cli/deploy.rb +32 -0
  18. data/lib/armada/cli/inspect.rb +28 -0
  19. data/lib/armada/configuration.rb +33 -0
  20. data/lib/armada/connection.rb +3 -0
  21. data/lib/armada/connection/docker.rb +22 -0
  22. data/lib/armada/connection/health_check.rb +66 -0
  23. data/lib/armada/connection/remote.rb +26 -0
  24. data/lib/armada/deploy.rb +2 -0
  25. data/lib/armada/deploy/parallel.rb +58 -0
  26. data/lib/armada/deploy/rolling.rb +60 -0
  27. data/lib/armada/deploy_dsl.rb +156 -0
  28. data/lib/armada/docker.rb +6 -0
  29. data/lib/armada/docker/config.rb +55 -0
  30. data/lib/armada/docker/container.rb +120 -0
  31. data/lib/armada/docker/host.rb +68 -0
  32. data/lib/armada/docker/image.rb +86 -0
  33. data/lib/armada/thor.rb +1 -0
  34. data/lib/armada/ui.rb +15 -0
  35. data/lib/armada/utils.rb +2 -0
  36. data/lib/armada/utils/array.rb +9 -0
  37. data/lib/armada/utils/time.rb +23 -0
  38. data/lib/armada/version.rb +3 -0
  39. data/spec/connection/health_check_spec.rb +36 -0
  40. data/spec/deploy_dsl_spec.rb +84 -0
  41. data/spec/docker/container_spec.rb +124 -0
  42. data/spec/docker/image_spec.rb +110 -0
  43. data/spec/spec_helper.rb +12 -0
  44. metadata +289 -0
@@ -0,0 +1,2 @@
1
+ require_relative 'clean/containers'
2
+ require_relative 'clean/images'
@@ -0,0 +1,30 @@
1
+ module Armada
2
+ module Clean
3
+ class Containers
4
+ def initialize(options)
5
+ @hosts = options[:hosts]
6
+ @force = options[:force]
7
+ @options = options
8
+ end
9
+
10
+ def run
11
+ Armada.ui.info "******* DRY RUN *******" unless @force
12
+ @hosts.each_in_parallel do |host|
13
+ docker_host = Armada::Host.create(host, @options)
14
+ docker_host.get_all_containers.each do |container|
15
+ running = container.json["State"]["Running"]
16
+ paused = container.json["State"]["Paused"]
17
+ unless running || paused
18
+ begin
19
+ Armada.ui.info "#{docker_host.host} -- #{container.json["Name"]} with id [#{container.id[0..11]}]"
20
+ container.remove if @force
21
+ rescue Exception => e
22
+ Armada.ui.error "#{docker_host.host} -- unable to remove container #{container.json["Name"]} with id [#{container.id[0..11]}] because of the following error - #{e.message}"
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,29 @@
1
+ module Armada
2
+ module Clean
3
+ class Images
4
+ def initialize(options)
5
+ @hosts = options[:hosts]
6
+ @force = options[:force]
7
+ @noprune = !options[:prune]
8
+ @options = options
9
+ end
10
+
11
+ def run
12
+ Armada.ui.info "******* DRY RUN *******" unless @force
13
+ @hosts.each_in_parallel do |host|
14
+ docker_host = Armada::Host.create(host, @options)
15
+ docker_host.get_all_images.each do |image|
16
+ if image.info["RepoTags"].include?("<none>:<none>")
17
+ begin
18
+ Armada.ui.info "#{docker_host.host} -- #{image.id[0..11]} is an abandoned image and will be removed"
19
+ image.remove({:force => true, :noprune => @noprune}) if @force
20
+ rescue Exception => e
21
+ Armada.ui.error "#{docker_host.host} -- unable to remove image #{image.id[0..11]} because of the following error - #{e.message}"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
data/lib/armada/cli.rb ADDED
@@ -0,0 +1,40 @@
1
+ require_relative 'cli/deploy'
2
+ require_relative 'cli/inspect'
3
+ require_relative 'cli/clean'
4
+
5
+ module Armada
6
+ class Cli < Thor
7
+
8
+ def initialize(*args)
9
+ super(*args)
10
+
11
+ if @options[:quiet]
12
+ Aramda.ui.mute!
13
+ end
14
+
15
+ @options = options.dup
16
+ end
17
+
18
+ class_option :quiet,
19
+ desc: "Silence all informational output",
20
+ type: :boolean,
21
+ aliases: "-q",
22
+ default: false
23
+
24
+ desc "version", "Display Aramda version."
25
+ def version
26
+ Armada.ui.info "#{Armada.executable_name} #{Armada::VERSION}"
27
+ end
28
+
29
+
30
+ desc "deploy SUBCOMMAND ...ARGS", "Deploy a docker container"
31
+ subcommand "deploy", Armada::DeployCli
32
+
33
+ desc "inspect SUBCOMMAND ...ARGS", "Info about the state of a docker host"
34
+ subcommand "inspect", Armada::InspectCli
35
+
36
+ desc "clean SUBCOMMAND ...ARGS", "Clean a docker host(s)"
37
+ subcommand "clean", Armada::CleanCli
38
+
39
+ end
40
+ end
@@ -0,0 +1,23 @@
1
+ module Armada
2
+ class CleanCli < Thor
3
+
4
+ desc "containers", "Remove all exited containers from a host(s)"
5
+ option :hosts, :type => :array, :aliases => :h, :desc => "The docker host(s) to deploy to. This can be a comma sepearted list."
6
+ option :ssh_gateway, :type => :string, :aliases => :G, :desc => "SSH Gateway Host"
7
+ option :ssh_gateway_user, :type => :string, :aliases => :U, :desc => "SSH Gateway User"
8
+ option :force, :type => :boolean, :aliases => :f, :desc => "Must specify the force option if you want the containers removed", :default => false, :lazy_default => true
9
+ def containers
10
+ Armada::Clean::Containers.new(options).run
11
+ end
12
+
13
+ desc "images", "Remove all untagged images from a host(s)"
14
+ option :hosts, :type => :array, :aliases => :h, :desc => "The docker host(s) to deploy to. This can be a comma sepearted list."
15
+ option :ssh_gateway, :type => :string, :aliases => :G, :desc => "SSH Gateway Host"
16
+ option :ssh_gateway_user, :type => :string, :aliases => :U, :desc => "SSH Gateway User"
17
+ option :force, :type => :boolean, :aliases => :f, :desc => "Must specify the force option if you want the containers removed", :default => false, :lazy_default => true
18
+ option :prune, :type => :boolean, :aliases => :p, :desc => "Whether to prune child images or not"
19
+ def images
20
+ Armada::Clean::Images.new(options).run
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,32 @@
1
+ module Armada
2
+ class DeployCli < Thor
3
+
4
+ no_commands {
5
+ def self.common_options
6
+ option :hosts, :type => :array, :aliases => :h, :desc => "The docker host(s) to deploy to. This can be a comma sepearted list."
7
+ option :image, :type => :string, :aliases => :i, :desc => "The image to use when deploying"
8
+ option :tag, :type => :string, :aliases => :t, :desc => "Which version of the image to use", :lazy_default => "latest"
9
+ option :username, :type => :string, :aliases => :u, :desc => "Docker registry username, if specified, --password must also be specified"
10
+ option :password, :type => :string, :aliases => :p, :desc => "Docker registry password, if specified, --username must also be specified"
11
+ option :health_check, :type => :boolean, :aliases => :c, :desc => "Perform health check of container. Default is true", :default => true
12
+ option :env_vars, :type => :hash, :aliases => :e, :desc => "Environment Variables to pass into the container"
13
+ option :pull, :type => :boolean, :desc => "Whether to pull the image from the docker registry", :default => true
14
+ option :ssh_gateway, :type => :string, :aliases => :G, :desc => "SSH Gateway Host"
15
+ option :ssh_gateway_user, :type => :string, :aliases => :U, :desc => "SSH Gateway User"
16
+ option :dockercfg, :type => :string, :desc => "Path to dockercfg file, used to authenticate against the docker registry", :default => '~/.dockercfg'
17
+ end
18
+ }
19
+
20
+ desc "parallel <project> <environment>", "Deploy the specified project to a set of hosts in parallel."
21
+ DeployCli.common_options
22
+ def parallel(project, environment)
23
+ Armada::Deploy::Parallel.new(project, environment, options).run
24
+ end
25
+
26
+ desc "rolling <project> <environment>", "Perform a rolling deploy across a set of hosts."
27
+ DeployCli.common_options
28
+ def rolling(project, environment)
29
+ Armada::Deploy::Rolling.new(project, environment, options).run
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,28 @@
1
+ module Armada
2
+ class InspectCli < Thor
3
+
4
+ desc "short", "Return basic information on all running containers for a given host"
5
+ option :hosts, :type => :array, :aliases => :h, :desc => "The Docker host to inspect"
6
+ def short
7
+ # Armada.ui.info "Gathering information for #{@options[:hosts].join(', ')}..."
8
+
9
+ # hosts = Armada::.new(@options[:hosts])
10
+ # hosts.each do |connection|
11
+ # running_containers = []
12
+ # Armada::Container.all(connection).each do |container|
13
+ # state = container.json["State"]
14
+ # if state["Running"]
15
+ # running_containers <<
16
+ # {
17
+ # :container_id => container.id[0..10],
18
+ # :container_name => container.json["Name"],
19
+ # :pid => state["Pid"],
20
+ # :uptime => Time.seconds_to_string(Time.now - Time.parse(state["StartedAt"])),
21
+ # }
22
+ # end
23
+ # end
24
+ # tp running_containers
25
+ # end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,33 @@
1
+ require 'rake'
2
+
3
+ module Armada
4
+ module Configuration
5
+ def self.load!(project, environment, cli_options = {})
6
+ projects_dir = Dir.getwd()
7
+ config_file = "#{project}.rake"
8
+
9
+ if File.exists?(File.join(projects_dir, config_file))
10
+ Rake.load_rakefile File.join(File.join(projects_dir, config_file))
11
+ elsif File.exists?(config_file)
12
+ Rake.load_rakefile config_file
13
+ else
14
+ Armada.ui.error "Can't find '#{config_file}'!"
15
+ end
16
+
17
+ Object.send :include, Armada::DeployDSL
18
+ task = Rake::Task["environment:#{environment}"]
19
+ task.set_current_environment environment.to_sym
20
+ task.invoke
21
+
22
+ task_options = Thor::CoreExt::HashWithIndifferentAccess.new(env[environment.to_sym])
23
+ env_vars = task_options[:env_vars]
24
+ env_vars.merge!(cli_options[:env_vars]) if cli_options[:env_vars] # If I try and merge cli_options into task_options it overrides the task_options[:env_vars] hash.
25
+ options = task_options.merge(cli_options)
26
+ options[:env_vars] = env_vars
27
+ options[:tag] = 'latest' unless options[:tag]
28
+ options[:health_check_endpoint] = '/' unless options[:health_check_endpoint]
29
+ options[:dockercfg] = Armada::Docker::Config.load options[:dockercfg]
30
+ options
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ require_relative 'connection/remote'
2
+ require_relative 'connection/docker'
3
+ require_relative 'connection/health_check'
@@ -0,0 +1,22 @@
1
+ module Armada
2
+ module Connection
3
+ class Docker < Remote
4
+
5
+ attr_reader :connection
6
+ def initialize(host, gateway_host = nil, gateway_user = nil)
7
+ super(host, nil, gateway_host, gateway_user)
8
+ @connection = create_connection
9
+ end
10
+
11
+ def to_s
12
+ "#{@host}:#{@port}"
13
+ end
14
+
15
+ private
16
+ def create_connection
17
+ return ::Docker::Connection.new("http://localhost:#{@tunneled_port}", {}) if @gateway
18
+ return ::Docker::Connection.new("http://#{@host}:#{@port}", {})
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,66 @@
1
+ module Armada
2
+ module Connection
3
+ class HealthCheck < Remote
4
+
5
+ attr_reader :endpoint
6
+ def initialize(host, port, endpoint = nil, delay = nil, retries = nil, gateway_host = nil, gateway_user = nil)
7
+ super(host, port, gateway_host, gateway_user)
8
+ @endpoint = endpoint ||= '/'
9
+ @delay = delay ||= 1
10
+ @retries = retries ||= 60
11
+ end
12
+
13
+ def to_s
14
+ "#{@host}:#{@port}#{@endpoint}"
15
+ end
16
+
17
+ def run
18
+ info "Performing health check at - :#{@port}#{@endpoint}. Will retry every #{@delay} second(s) for #{@retries} times."
19
+ 1.upto(@retries) do |i|
20
+ initialize_gateway!
21
+ unless healthy?
22
+ info "Still waiting for health check to pass at - :#{@port}#{@endpoint} endpoint..." if i % (@retries/10) == 0
23
+ sleep(@delay)
24
+ else
25
+ info "Health check succeeded!"
26
+ return true
27
+ end
28
+ end
29
+ return false
30
+ end
31
+
32
+ def healthy?
33
+ response = begin
34
+ Excon.get("http://#{health_check_host}:#{health_check_port}#{@endpoint}")
35
+ rescue Exception => e
36
+ return false
37
+ end
38
+
39
+ return false unless response
40
+ return true if response.status >= 200 && response.status < 300
41
+
42
+ warn "Got HTTP status: #{response.status}"
43
+ false
44
+ end
45
+
46
+ private
47
+
48
+ def health_check_host
49
+ return "localhost" if @gateway
50
+ return @host
51
+ end
52
+
53
+ def health_check_port
54
+ return @tunneled_port ||= port
55
+ end
56
+
57
+ def info(message)
58
+ Armada.ui.info "#{@host} -- #{message}"
59
+ end
60
+
61
+ def warn(message)
62
+ Armada.ui.warn "#{@host} -- #{message}"
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,26 @@
1
+ module Armada
2
+ module Connection
3
+ class Remote
4
+
5
+ attr_reader :host, :port, :gateway
6
+ def initialize(host, port = nil, gateway_host = nil, gateway_user = nil)
7
+ @host, @port = host.split(":")
8
+ @port = port ||= @port
9
+ @gateway_host = gateway_host
10
+ @gateway_user = gateway_user
11
+ initialize_gateway!
12
+ end
13
+
14
+ def to_s
15
+ "#{@host}:#{@port}"
16
+ end
17
+
18
+ def initialize_gateway!
19
+ if @gateway_host
20
+ @gateway = Net::SSH::Gateway.new(@gateway_host, @gateway_user)
21
+ @tunneled_port = @gateway.open(@host, @port)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,2 @@
1
+ require_relative 'deploy/parallel'
2
+ require_relative 'deploy/rolling'
@@ -0,0 +1,58 @@
1
+ module Armada
2
+ module Deploy
3
+ class Parallel
4
+
5
+ attr_reader :project, :environment, :options
6
+ def initialize(project, environment, options)
7
+ @project = project
8
+ @environment = environment
9
+ @options = Armada::Configuration.load!(project, environment, options)
10
+ end
11
+
12
+ def run
13
+ Armada.ui.info "Deploying the following image [#{@options[:image]}:#{@options[:tag]}] to these host(s) #{@options[:hosts].join(', ')} in PARALLEL"
14
+
15
+ begin
16
+ @options[:hosts].each_in_parallel do |host|
17
+ docker_host = Armada::Host.create(host, options)
18
+ image = docker_host.get_image @options[:image], @options[:tag], @options
19
+ image.pull
20
+
21
+ container = Armada::Container.new(image, docker_host, @options)
22
+ container.stop
23
+ container.start
24
+
25
+ if @options[:health_check] && @options[:health_check_port]
26
+ ports = container.ports
27
+
28
+ if ports.empty?
29
+ raise "No ports exposed for this container. Please expose a port for the health check or use the --no-health-check option!"
30
+ end
31
+
32
+ begin
33
+ health_check_port = ports["#{@options[:health_check_port]}/tcp"][0]["HostPort"]
34
+ rescue Exception => e
35
+ raise "Could not find the host port for [#{health_check_port}]. Make sure you put the container port as the :health_check_port."
36
+ end
37
+
38
+ health_check = Armada::Connection::HealthCheck.new(
39
+ host,
40
+ health_check_port,
41
+ @options[:health_check_endpoint],
42
+ @options[:health_check_delay],
43
+ @options[:health_check_retries],
44
+ @options[:ssh_gateway],
45
+ @options[:ssh_gateway_user]
46
+ )
47
+
48
+ raise "Health check failed! - #{host}" unless health_check.run
49
+ end
50
+ end
51
+ rescue Exception => e
52
+ Armada.ui.error "#{e.message} \n\n #{e.backtrace.join("\n")}"
53
+ exit(1)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,60 @@
1
+ module Armada
2
+ module Deploy
3
+ class Rolling
4
+
5
+ attr_reader :project, :environment, :options
6
+ def initialize(project, environment, options)
7
+ @project = project
8
+ @environment = environment
9
+ @options = Armada::Configuration.load!(project, environment, options)
10
+ end
11
+
12
+ def run
13
+ Armada.ui.info "Deploying the following image [#{@options[:image]}:#{@options[:tag]}] to these host(s) #{@options[:hosts].join(', ')}"
14
+ begin
15
+ @options[:hosts].each_in_parallel do |host|
16
+ docker_host = Armada::Host.create(host, options)
17
+ docker_host.get_image(@options[:image], @options[:tag], @options).pull
18
+ end
19
+
20
+ @options[:hosts].each do |host|
21
+ docker_host = Armada::Host.create(host, options)
22
+ image = docker_host.get_image @options[:image], @options[:tag], @options
23
+ container = Armada::Container.new(image, docker_host, @options)
24
+ container.stop
25
+ container.start
26
+
27
+ if @options[:health_check] && @options[:health_check_port]
28
+ ports = container.ports
29
+
30
+ if ports.empty?
31
+ raise "No ports exposed for this container. Please expose a port for the health check or use the --no-health-check option!"
32
+ end
33
+
34
+ begin
35
+ health_check_port = ports["#{@options[:health_check_port]}/tcp"][0]["HostPort"]
36
+ rescue Exception => e
37
+ raise "Could not find the host port for [#{health_check_port}]. Make sure you put the container port as the :health_check_port."
38
+ end
39
+
40
+ health_check = Armada::Connection::HealthCheck.new(
41
+ host,
42
+ health_check_port,
43
+ @options[:health_check_endpoint],
44
+ @options[:health_check_delay],
45
+ @options[:health_check_retries],
46
+ @options[:ssh_gateway],
47
+ @options[:ssh_gateway_user]
48
+ )
49
+
50
+ raise "Health check failed! - #{host}" unless health_check.run
51
+ end
52
+ end
53
+ rescue Exception => e
54
+ Armada.ui.error "#{e.message} \n\n #{e.backtrace.join("\n")}"
55
+ exit(1)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end