diff --git a/lib/fpm/fry/client.rb b/lib/fpm/fry/client.rb index 6fc74f2..88c34ca 100644 --- a/lib/fpm/fry/client.rb +++ b/lib/fpm/fry/client.rb @@ -22,6 +22,10 @@ class ContainerCreationFailed < StandardError include FPM::Fry::WithData end + class ContainerDeletionFailed < StandardError + include FPM::Fry::WithData + end + # Raised when trying to read file that can't be read e.g. because it's a # directory. class NotAFile < StandardError @@ -111,7 +115,7 @@ def read(name, resource) ) end rescue Excon::Error => e - @logger.error("unexpected response when reading resource: url: #{url}, error: #{e}") + logger.error("unexpected response when reading resource: url: #{url}, error: #{e}") raise end if [404,500].include? res.status @@ -165,14 +169,14 @@ def link_target(name, resource) end def copy(name, resource, map, options = {}) - ex = FPM::Fry::Tar::Extractor.new(logger: @logger) + ex = FPM::Fry::Tar::Extractor.new(logger: logger) base = File.dirname(resource) read(name, resource) do | entry | file = File.join(base, entry.full_name).chomp('/') file = file.sub(%r"\A\./",'') to = map[file] next unless to - @logger.debug("Copy",name: file, to: to) + logger.debug("Copy",name: file, to: to) ex.extract_entry(to, entry, options) end end @@ -182,7 +186,7 @@ def changes(name) res = agent.get(path: url, expects: [200, 204]) return JSON.parse(res.body) rescue Excon::Error => e - @logger.error("could not retrieve changes for: #{name}, url: #{url}, error: #{e}") + logger.error("could not retrieve changes for: #{name}, url: #{url}, error: #{e}") raise end @@ -220,27 +224,35 @@ def create(image) ) data = JSON.parse(res.body) if res.status != 201 - @logger.error(data["message"]) + logger.error(data["message"]) if res.status == 404 - @logger.info("execute docker pull #{image} first or specify --pull argument for fpm-fry") + logger.info("execute docker pull #{image} first or specify --pull argument for fpm-fry") end raise ContainerCreationFailed.new("could not create container from #{image}", message: data["message"]) end data['Id'] rescue Excon::Error => e - @logger.error("could not create container from #{image}, url: #{url}, error: #{e}") + logger.error("could not create container from #{image}, url: #{url}, error: #{e}") raise end def destroy(container) return unless container + url = self.url('containers', container) - agent.delete( + res = agent.delete( path: url, - expects: [204] + expects: [204, 409] ) + return unless res.status == 409 + data = JSON.parse(res.body) rescue ({"message" => "could not parse response body: '#{res.body}'"}) + if data["message"] =~ /removal of container .* is already in progress/ + logger.info(data["message"]) + else + raise ContainerDeletionFailed.new("could not destroy container #{container}", data) + end rescue Excon::Error => e - @logger.error("could not destroy container: #{container}, url: #{url}, error: #{e}") + logger.error("could not destroy container: #{container}, url: #{url}, error: #{e}") raise end diff --git a/lib/fpm/fry/command/cook.rb b/lib/fpm/fry/command/cook.rb index 871b1db..0a3b45e 100644 --- a/lib/fpm/fry/command/cook.rb +++ b/lib/fpm/fry/command/cook.rb @@ -2,6 +2,10 @@ module FPM; module Fry class Command::Cook < Command + class BuildFailed < StandardError + include FPM::Fry::WithData + end + option '--keep', :flag, 'Keep the container after build' option '--overwrite', :flag, 'Overwrite package', default: true option '--verbose', :flag, 'Verbose output', default: false @@ -212,79 +216,81 @@ def pull_base_image! end def build! - body = begin - url = client.url('containers','create') - args = { - headers: { - 'Content-Type' => 'application/json' - }, - path: url, - expects: [201], - body: JSON.generate({"Image" => build_image}) - } - args[:query] = { platform: platform } if platform - res = client.post(args) - JSON.parse(res.body) - rescue Excon::Error - logger.error "could not create #{build_image}, url: #{url}" - raise - end - container = body['Id'] - begin - begin - url = client.url('containers',container,'start') - client.post( - headers: { - 'Content-Type' => 'application/json' - }, - path: url, - expects: [204], - body: JSON.generate({}) - ) - rescue Excon::Error - logger.error "could not start container #{container}, url: #{url}" - raise - end + container = create_build_container + start_build_container(container) + attach_to_build_container_and_stream_logs(container) + wait_for_build_container_to_shut_down(container) + yield container + ensure + unless keep? + client.destroy(container) if container + end + end - begin - url = client.url('containers',container,'attach?stderr=1&stdout=1&stream=1&logs=1') - client.post( - path: url, - body: '', - expects: [200], - middlewares: [ - StreamParser.new(out,err), - Excon::Middleware::Expects, - Excon::Middleware::Instrumentor, - Excon::Middleware::Mock - ] - ) - rescue Excon::Error - logger.error "could not attach to container #{container}, url: #{url}" - raise - end + def create_build_container + url = client.url('containers','create') + args = { + headers: { + 'Content-Type' => 'application/json' + }, + path: url, + expects: [201], + body: JSON.generate({"Image" => build_image}) + } + args[:query] = { platform: platform } if platform + res = client.post(args) + JSON.parse(res.body)['Id'] + rescue Excon::Error + logger.error "could not create #{build_image}, url: #{url}" + raise + end - begin - res = client.post( - path: client.url('containers',container,'wait'), - expects: [200], - body: '' - ) - json = JSON.parse(res.body) - if json["StatusCode"] != 0 - raise Fry::WithData("Build failed", exit_code: json["StatusCode"]) - end - rescue Excon::Error - logger.error "could not wait successfully for container #{container}, url: #{url}" - raise - end + def start_build_container(container) + url = client.url('containers',container,'start') + client.post( + headers: { + 'Content-Type' => 'application/json' + }, + path: url, + expects: [204], + body: JSON.generate({}) + ) + rescue Excon::Error + logger.error "could not start container #{container}, url: #{url}" + raise + end - yield container - ensure - unless keep? - client.destroy(container) - end + def attach_to_build_container_and_stream_logs(container) + url = client.url('containers',container,'attach?stderr=1&stdout=1&stream=1&logs=1') + client.post( + path: url, + body: '', + expects: [200], + middlewares: [ + StreamParser.new(out,err), + Excon::Middleware::Expects, + Excon::Middleware::Instrumentor, + Excon::Middleware::Mock + ] + ) + rescue Excon::Error + logger.error "could not attach to container #{container}, url: #{url}" + raise + end + + def wait_for_build_container_to_shut_down(container) + res = client.post( + path: client.url('containers',container,'wait'), + expects: [200], + body: '' + ) + json = JSON.parse(res.body) + if json["StatusCode"] != 0 + raise BuildFailed.new("Build script failed with non zero exit code", json) end + rescue Excon::Error + logger.error "could not wait successfully for container #{container}, url: #{url}" + raise end def input_package(container)