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