class Freighter::Deploy
Constants
- PortMap
Attributes
config[R]
logger[R]
Public Class Methods
new()
click to toggle source
# File lib/freighter/deploy.rb, line 9 def initialize @parser = Parser.new OPTIONS.config_path @logger = LOGGER @config = OPTIONS.config @connection_config = @config.fetch('connection') environments = @config.fetch('environments') @environment = environments.fetch(OPTIONS.environment) rescue logger.config_error("environments/#{OPTIONS.environment}") connection_type = @connection_config['type'] case connection_type when 'ssh' deploy_with_ssh else logger.error "Unknown configuration option for type: #{connection_type}" end end
Public Instance Methods
deploy_with_ssh()
click to toggle source
# File lib/freighter/deploy.rb, line 26 def deploy_with_ssh ssh_options = @connection_config.fetch('ssh_options') ssh_options.extend Helpers::Hash ssh_options = ssh_options.symbolize_keys @environment.fetch('hosts').each_with_index do |host, i| host_name = host.fetch('host') @current_host_name = host_name images = @parser.images(host_name) ssh = SSH.new(host_name, ssh_options) local_port = 7000 + i # docker_api = DockerRestAPI.new("http://localhost:#{local_port}") ssh.tunneled_proxy(local_port) do |session| logger.debug msg "Connected" begin # The timeout is needed in the case that we are unable to communicate with the docker REST API Timeout::timeout(5) do setup_docker_client(local_port) end rescue Timeout::Error ssh.thread.exit logger.error msg "Could not reach the docker REST API" end images.each do |image| image_name = image['name'] # pull image if OPTIONS.pull_image logger.info msg "Pulling image: #{image_name}" pull_response = Docker::Image.create 'fromImage' => image_name else logger.info msg "Skip pull image" logger.error msg "Skipping is not yet implemented. Please run again without the --no-pull option" end # find existing images on the machine image_ids = Docker::Image.all.select do |img| img.info['RepoTags'].member?(image_name) end.map { |img| img.id[0...12] } logger.info msg "Existing image(s) found #{image_ids.join(', ')}" # determine if a the latest version of the image is currently running matching_containers = containers_matching_port_map(Docker::Container.all, image['containers'].map { |c| c['port_mapping'] }) # stop previous container and start up a new container with the latest image stopped_containers = [] current_running_containers = [] matching_containers.each do |container| if pull_response.id =~ /^#{container.info['Image']}/ current_running_containers << container else Docker::Container.get(container.id).stop stopped_containers << container end end if !current_running_containers.empty? and stopped_containers.empty? logger.info msg "Container already running with the latest image: #{pull_response.id}" else logger.info msg "Stopped containers: #{stopped_containers.map(&:info).map(&:to_json)}" results = update_containers matching_containers, image logger.info msg "Finished:" logger.info msg " started: #{results[:started]}" logger.info msg " stopped: #{results[:stopped]}" logger.info msg " started container ids: #{results[:container_ids_started]}" # cleanup old containers cleanup_old_containers # cleanup unused/outdated images cleanup_dangling_images end end end end end
Private Instance Methods
cleanup_dangling_images()
click to toggle source
# File lib/freighter/deploy.rb, line 222 def cleanup_dangling_images thread_pool = [] Docker::Image.all(filters: '{"dangling":["true"]}').each do |image| thread_pool << Thread.new do image.remove logger.info msg "Removed image: #{image.info.to_json}" end end logger.info msg "Waiting for dangling images to be cleaned up" ThreadsWait.all_waits(*thread_pool) end
cleanup_old_containers()
click to toggle source
cleans up all exited containers
# File lib/freighter/deploy.rb, line 206 def cleanup_old_containers thread_pool = [] Docker::Container.all(all: true).select { |c| c.info['Status'] =~ /^Exited/ }.each do |container| thread_pool << Thread.new do logger.info msg "Removing container: #{container.info.to_json}" container.remove end end if thread_pool.empty? logger.info msg "No containers need to be cleaned up" else logger.info msg "Waiting for old containers to be cleaned up" ThreadsWait.all_waits(*thread_pool) end end
containers_matching_port_map(containers, port_mappings)
click to toggle source
# File lib/freighter/deploy.rb, line 131 def containers_matching_port_map(containers, port_mappings) port_mappings.map do |port_map| ports = map_ports(port_map) containers.select do |c| c.info['Ports'].detect do |p| p['PrivatePort'] == ports.container && p['PublicPort'] == ports.host end end end.compact.flatten end
map_ports(port_map)
click to toggle source
# File lib/freighter/deploy.rb, line 143 def map_ports(port_map) # for some unknown reason, containers started without a port mapping specified # are getting a default port mapping of 80/tcp. This is why a default port map is # being assigned to port 80 return PortMap.new(nil, nil, 80) if port_map.nil? port_map.match(/^(\d{1,3}\.[\.0-9]*)?:?(\d+)->(\d+)$/) begin raise if $2.nil? or $3.nil? PortMap.new($1, $2.to_i, $3.to_i) rescue raise "port_mappings needs to be in the format of <ip-address>:<host-port-number>-><container-port-number>. received: #{port_map}" end end
msg(message)
click to toggle source
Used for logging to prefix log messages with the current host
# File lib/freighter/deploy.rb, line 109 def msg(message) "#{@current_host_name}: #{message}" end
setup_docker_client(local_port)
click to toggle source
Sets up the Docker gem by setting the local URL and authenticating to the host's REST API
# File lib/freighter/deploy.rb, line 114 def setup_docker_client(local_port) Docker.url = "http://localhost:#{local_port}" Docker.connection.options[:scheme] = 'http' begin logger.debug "Requesting docker version" response = Docker.version logger.debug "Docker version: #{response.inspect}" logger.debug "Requesting docker authenticaiton" response = Docker.authenticate!('username' => ENV['DOCKER_HUB_USER_NAME'], 'password' => ENV['DOCKER_HUB_PASSWORD'], 'email' => ENV['DOCKER_HUB_EMAIL']) logger.debug "Docker authentication: #{response.inspect}" rescue Excon::Errors::SocketError => e abort e.message rescue Exception => e abort e.message end end
update_containers(existing_containers=[], image)
click to toggle source
# File lib/freighter/deploy.rb, line 157 def update_containers(existing_containers=[], image) totals = { stopped: 0, started: 0, container_ids_started: [] } # stop the existing matching containers existing_containers.map do |container| Thread.new do existing_container = Docker::Container.get(container.id) logger.info msg "Stopping container: #{contianer.id}" existing_container.stop existing_container.wait() logger.info msg "Container stopped (#{container.id}" totals[:stopped] += 1 end end.join # start up some new containers image['containers'].map do |container| port_map = map_ports(container['port_mapping']) # env = container['env'].inject("") { |r, (k,v)| r << "#{k}='#{v}',\n" } env = container['env'].map { |k,v| "#{k}=#{v}" } container_options = { "Image" => image['name'], "Env" => env } start_options = {} if port_map.host container_options.merge!({ "ExposedPorts" => { "#{port_map.container}/tcp" => {} } }) start_options.merge!({ "PortBindings" => { "#{port_map.container}/tcp" => [{ "HostPort" => port_map.host.to_s, "HostIp" => port_map.ip }] } }) logger.info msg "Starting container with port_mapping: host #{[port_map.ip, port_map.host].join(':')}, container #{port_map.container}" end new_container = Docker::Container.create container_options new_container.start start_options totals[:container_ids_started] << new_container.id logger.info msg "New container started with id: #{new_container.id}" totals[:started] += 1 end totals end