lib/cide/cli.rb in cide-0.7.0 vs lib/cide/cli.rb in cide-0.8.0

- old
+ new

@@ -1,8 +1,10 @@ +require 'cide/builder' +require 'cide/config_file' require 'cide/constants' require 'cide/docker' -require 'cide/build' +require 'cide/runner' require 'thor' require 'json' require 'securerandom' @@ -12,16 +14,16 @@ # Command-line option-parsing and execution for cide class CLI < Thor include CIDE::Docker include Thor::Actions - default_command 'build' + default_command 'exec' - desc 'build', 'Builds an image and executes the run script' + desc 'exec', 'Builds an image and executes the run script' method_option 'name', - desc: 'Name of the build', + desc: 'Name of the image', aliases: %w(-n -t), default: File.basename(Dir.pwd) method_option 'export', desc: 'Whenever to export artifacts', @@ -40,11 +42,11 @@ method_option 'run', desc: 'Override the script to run', type: :string, aliases: ['-r'], - default: [] + default: nil method_option 'pull', desc: 'Whenever to pull for new images on build', type: :boolean, default: true @@ -52,121 +54,165 @@ method_option 'ssh_key', desc: 'Path to a ssh key to import into the docker image', aliases: ['-s'], default: '~/.ssh/id_rsa' - def build - containers = [] - + def exec setup_docker - ## Config ## + tag = name_to_tag options.name + banner 'Config' - build = Build::Config.load(Dir.pwd) - exit 1 if build.nil? - ssh_key = File.expand_path(options.ssh_key) - build.run = ['sh', '-e', '-c', options.run] unless options.run.empty? - name = CIDE::Docker.id options.name - tag = "cide/#{name}" - say_status :config, build.inspect + config = ConfigFile.load(Dir.pwd) + say_status :config, config.inspect ## Build ## banner 'Build' - if build.use_ssh - unless File.exist?(ssh_key) - fail ArgumentError, "SSH key #{ssh_key} not found" - end - create_tmp_file TEMP_SSH_KEY, File.read(ssh_key) - end - create_tmp_file DOCKERFILE, build.to_dockerfile - build_options = ['--force-rm'] - build_options << '--pull' if options.pull - build_options.push '-f', DOCKERFILE - build_options.push '-t', tag - build_options << '.' - docker :build, *build_options + builder = Builder.new(config) + builder.build( + pull: options.pull, + ssh_key: File.expand_path(options.ssh_key), + tag: tag, + ) - ## CI ## + ## Run ## banner 'Run' - build.links.each do |link| - args = ['--detach'] - link.env.each_pair do |key, value| - args.push('--env', [key, value].join('=')) - end - args << link.image - args << link.run if link.run - link.id = docker(:run, *args, capture: true).strip - containers << link.id - end - run_options = ['--detach'] + command = options.run ? ['sh', '-e', '-c', options.run] : config.run - build.env.each_pair do |key, value| - run_options.push '--env', [key, value].join('=') - end + runner = Runner.new( + command: command, + env: config.env, + links: config.links, + tag: tag, + ) + runner.run! - build.links.each do |link| - run_options.push '--link', [link.id, link.name].join(':') - end + ## Export ## + return unless options.export + banner 'Export' + runner.export!( + guest_dir: options.guest_export_dir || config.export_dir, + host_dir: options.export_dir || config.export_dir, + ) + rescue Docker::Error => ex + exit ex.exitstatus + rescue RuntimeError => ex + $stderr.puts ex.to_s + exit 1 + ensure + runner.cleanup! if runner + end - id = SecureRandom.hex - run_options.push '--name', id + desc 'package', 'Builds a package from the container script/build' + method_option 'name', + desc: 'Name of the image', + aliases: %w(-n -t), + default: File.basename(Dir.pwd) - run_options.push tag - run_options.push(*build.run) + method_option 'ssh_key', + desc: 'Path to a ssh key to import into the docker image', + aliases: ['-s'], + default: '~/.ssh/id_rsa' - containers << id - docker(:run, *run_options, capture: true).strip - docker(:attach, id) + method_option 'package', + desc: 'Name of the package', + default: File.basename(Dir.pwd) - say_status :status, 'SUCCESS', :green + method_option 'upload', + desc: 'Whenever to upload the result to S3', + type: :boolean, + default: true + method_option 'set-version', + desc: 'Tells cide the package version, otherwise extracted from git', + default: nil + + # AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_REGION need to be passed + # either trough the env or ~/.aws/credentials file + method_option 'aws_bucket', + desc: 'AWS_BUCKET', + default: ENV['AWS_BUCKET'] + + def package + fail 'missing AWS_BUCKET' if options.upload && !options.aws_bucket + + tag = name_to_tag options.name + + build_root = File.expand_path('.package') + guest_export_dir = '/cide/package' + host_export_dir = File.join(build_root, 'package') + + version = options.version || ( + git_branch = `git symbolic-ref --short -q HEAD || echo unknown`.strip + git_rev = `git rev-parse --short HEAD`.strip + "#{git_branch}-#{git_rev}" + ) + + timestamp = Time.now.strftime('%Y-%m-%d_%H%M%S') + tar_name = "#{options.package}.#{timestamp}.#{version}.tar.gz" + tar_path = File.join(build_root, tar_name) + + banner 'Config' + config = ConfigFile.load(Dir.pwd) + say_status :config, config.inspect + + ## Build ## + banner 'Build' + builder = Builder.new(config) + builder.build( + pull: options.pull, + ssh_key: File.expand_path(options.ssh_key), + tag: tag, + ) + + ## Run ## + banner 'Run' + runner = Runner.new( + command: ['script/build', guest_export_dir], + env: config.env, + links: config.links, + tag: tag, + ) + runner.run! + ## Export ## - return unless options.export banner 'Export' - source_export_dir = options.guest_export_dir || build.export_dir - fail 'export flag set but no export_dir given' if source_export_dir.nil? + FileUtils.rm_rf(build_root) - target_export_dir = options.export_dir || source_export_dir + runner.export!( + guest_dir: guest_export_dir, + host_dir: host_export_dir, + ) - target_export_dir = File.dirname(target_export_dir) + # Create archive + puts "Package: #{tar_name}" + system('tar', '-czf', tar_path, '-C', host_export_dir, '.') - guest_export_dir = File.expand_path(source_export_dir, CIDE_SRC_DIR) - host_export_dir = File.expand_path(target_export_dir, Dir.pwd) + ## Upload ## - docker :cp, [id, guest_export_dir].join(':'), host_export_dir + return unless options.upload + banner 'Upload' + + require 'aws-sdk' + s3 = Aws::S3::Client.new + resp = s3.put_object( + bucket: options.aws_bucket, + key: tar_name, + body: File.open(tar_path), + ) + p resp rescue Docker::Error => ex - say_status :status, 'ERROR', :red exit ex.exitstatus + rescue RuntimeError => ex + $stderr.puts ex.to_s + exit 1 ensure - linked_containers = containers - [id] - unless linked_containers.empty? - infos = docker( - :inspect, - *linked_containers, - capture: true, - verbose: false, - ) - JSON.parse(infos).each do |info| - config = info['Config'] - state = info['State'] + FileUtils.rm_rf(build_root) if options.upload - next unless state['Dead'] || state['ExitCode'] > 0 - - $stderr.puts "=== Failed linked container #{info['Id']} ===" - $stderr.puts "Image: #{config['Image']}" - $stderr.puts "State: #{state.inspect}" - docker(:logs, '--tail', 20, info['Id']) - end - end - # Shutdown old containers - unless containers.empty? - docker :rm, '--force', '--volumes', *containers.reverse, - verbose: false, - capture: true - end + runner.cleanup! if runner end desc 'debug', 'Opens a debug console in the last project image' method_option 'name', desc: 'Name of the build', @@ -174,60 +220,37 @@ default: File.basename(Dir.pwd) method_option 'user', desc: 'User to run under', default: 'cide' def debug - containers = [] - setup_docker + tag = name_to_tag options.name + ## Config ## banner 'Config' - build = Build::Config.load - exit 1 if build.nil? - name = CIDE::Docker.id options.name - tag = "cide/#{name}" - say_status :config, build.inspect + config = ConfigFile.load(Dir.pwd) + say_status :config, config.inspect - ## CI ## + ## Run ## banner 'Run' - build.links.each do |link| - args = ['--detach'] - link.env.each_pair do |key, value| - args.push('--env', [key, value].join('=')) - end - args << link.image - args << link.run if link.run - link.id = docker(:run, *args, capture: true).strip - containers << link.id - end + runner = Runner.new( + command: ['bash'], + env: config.env, + links: config.links, + tag: tag, + user: options.user, + ) + runner.run!(interactive: true) - run_options = ['--rm', '-t', '-i'] - - run_options.push '--user', options.user - - build.env.each_pair do |key, value| - run_options.push '--env', [key, value].join('=') - end - - build.links.each do |link| - run_options.push '--link', [link.id, link.name].join(':') - end - - run_options.push tag - run_options.push 'bash' - - docker(:run, *run_options) rescue Docker::Error => ex exit ex.exitstatus + rescue RuntimeError => ex + $stderr.puts ex.to_s + exit 1 ensure - # Shutdown old containers - unless containers.empty? - docker :rm, '--force', '--volumes', *containers.reverse, - verbose: false, - capture: true - end + runner.cleanup! if runner end desc 'clean', 'Removes old containers' method_option 'days', desc: 'Number of days to keep the images', @@ -285,21 +308,19 @@ create_file CONFIG_FILES.first, File.read(DEFAULT_CIDEFILE) end private - def create_tmp_file(destination, *args, &block) - create_file(destination, *args, &block) - # Dockerfile ADD compares content and mtime, we don't want that - File.utime(1_286_701_800, 1_286_701_800, destination) - at_exit do - remove_file(destination, verbose: false) - end + # Prefixes the tag to make it recognizable by the cleaner + # Makes sure it's a valid tag + def name_to_tag(name) + "cide/#{CIDE::Docker.id name}" end - LINE_SIZE = 78.0 + LINE_WIDTH = 78.0 def banner(text) - pad = (LINE_SIZE - text.size - 4) / 2 + pad = (LINE_WIDTH - text.size - 4) / 2 + pad = 0 if pad < 0 puts '=' * pad.floor + "[ #{text} ]" + '=' * pad.ceil end end end