#!/usr/bin/env ruby require "dctl" require "thor" require "pty" require "rainbow" require "config" module Dctl class Cli < Thor class_option :env, default: "dev", type: :string desc "build", "Build images. Pass image name to build a specific one; otherwise builds all" def build(*images) dctl = Dctl::Main.new env: options[:env] images = dctl.expand_images *images env = options[:env] puts "Generating build script for #{images.join(", ")}" commands = [] images.each do |image| tag = dctl.image_tag(image) dockerfile = dctl.image_dockerfile(image) commands << "#{sudo}docker #{docker_opts} build -f #{dockerfile} -t #{tag} ." end stream_output commands.join(" && "), exec: true end desc "push", "Upload locally built images to the remote store" def push(*images) dctl = Dctl::Main.new env: options[:env] images = dctl.expand_images *images push_cmds = [] images.each do |image| tag = dctl.image_tag(image) push_cmds << "#{sudo}docker push #{tag}" end push_cmd = push_cmds.join " && " stream_output push_cmd, exec: true end desc "pull", "Pull the latest remote images to your local machine" def pull(*images) dctl = Dctl::Main.new env: options[:env] images = dctl.expand_images *images pull_cmds = [] images.each do |image| tag = dctl.image_tag(image) pull_cmds << "#{sudo}docker pull #{tag}" end pull_cmd = pull_cmds.join " && " stream_output pull_cmd, exec: true end desc "up", "Start your dockerized app server" def up dctl = Dctl::Main.new env: options[:env] pidfile = "tmp/pids/server.pid" FileUtils.rm pidfile if File.exist? pidfile compose_opts = %w(--remove-orphans) stream_output "#{sudo}docker-compose -f #{dctl.compose_file_path} up #{compose_opts.join(" ")}", exec: true end desc "down", "Stop one or many containers" def down(*images) dctl = Dctl::Main.new env: options[:env] stream_output "#{sudo}docker-compose -f #{dctl.compose_file_path} down #{images.join(" ")}", exec: true end desc "rm", "Remove one or many containers" option :force, type: :boolean, default: false, aliases: %w(f) def rm(*images) dctl = Dctl::Main.new env: options[:env] opts = " --force" if options[:force] stream_output "#{sudo}docker-compose -f #{dctl.compose_file_path} rm#{opts} #{images.join(" ")}", exec: true end desc "start", "Start one or many containers" def start(*images) dctl = Dctl::Main.new env: options[:env] stream_output "#{sudo}docker-compose -f #{dctl.compose_file_path} start #{images.join(" ")}", exec: true end desc "stop", "Stop one or many containers" def stop(*images) dctl = Dctl::Main.new env: options[:env] stream_output "#{sudo}docker-compose -f #{dctl.compose_file_path} stop #{images.join(" ")}", exec: true end desc "restart", "Restart one or many containers" def restart(*images) dctl = Dctl::Main.new env: options[:env] stream_output "#{sudo}docker-compose -f #{dctl.compose_file_path} restart #{images.join(" ")}", exec: true end desc "create", "Create one or many containers" def create(*images) dctl = Dctl::Main.new env: options[:env] stream_output "#{sudo}docker-compose -f #{dctl.compose_file_path} create #{images.join(" ")}", exec: true end desc "recreate", "Stop, remove, build, create, and start a container" option :build, type: :boolean, default: true, aliases: %w(b) def recreate(container) operations = ["stop", "rm -f", "build", "create", "start"] operations.delete("build") unless options[:build] commands = operations.map { |op| "dctl #{op} #{container}" } stream_output "#{commands.join(" && ")}", exec: true end desc "ps", "List running containers for this environment" def ps dctl = Dctl::Main.new env: options[:env] stream_output "#{sudo}docker-compose -f #{dctl.compose_file_path} ps", exec: true end desc "release IMAGE", "Increase the version number for the given image" def release(image) dctl = Dctl::Main.new env: options[:env] dctl.release(image) end desc "initdb", "Setup initial postgres database" def initdb dctl = Dctl::Main.new env: options[:env] env = options[:env] local_data_dir = File.expand_path "../tmp/psql-#{env}", __FILE__ `#{sudo}rm -r #{local_data_dir}` if File.exists? local_data_dir # todo prompt cmd = "#{sudo}docker-compose -f #{dtcl.compose_file_path} run --rm psql /bin/bash -c /etc/initdb.sh" stream_output cmd, exec: true end desc "cleanup", "cleans up dangling docker images" def cleanup dangling = `#{sudo}docker images --filter dangling=true -q`.split("\n") if dangling.none? puts "No images to cleanup. Yay!" exit 0 end stream_output "#{sudo}docker rmi -f #{dangling.join(" ")}", exec: true end desc "bash CONTAINER", "Create a new instance of the given image with a bash prompt" def bash(container = "app") dctl = Dctl::Main.new env: options[:env] cmd = "#{sudo}docker-compose -f #{dctl.compose_file_path} run --rm #{container} /bin/bash" stream_output cmd, exec: true end desc "connect CONTAINER", "Connect to a running container." def connect(image = "app") dctl = Dctl::Main.new env: options[:env] stream_output "#{sudo}docker-compose -f #{dctl.compose_file_path} exec #{image} /bin/bash", exec: true end desc "attach CONTAINER", "Connect to a running container." option :env, type: :string, default: "dev" def attach(image = "app") dctl = Dctl::Main.new env: options[:env] tag = dctl.image_tag(image) cmd = "#{sudo}docker ps --filter ancestor=#{image} -aq | head -n1" puts cmd container = `#{cmd}`.chomp if container.empty? puts Rainbow("No running containers for image #{image}").red exit 1 end stream_output "#{sudo}docker attach #{container}", exec: true end desc "version", "Print version" def version puts Dctl::VERSION end no_commands do def stream_output(string, print_command: true, exec: false) puts string if print_command if exec exec string else PTY.spawn string do |stdout, stdin, pid| stdout.each { |line| puts line } end end end def sudo `uname`.chomp == "Darwin" ? "" : "sudo " # use sudo on linux hosts end def docker_opts return "" unless ENV["JENKINS"] opts = "--tls" if host = ENV["DOCKER_HOST_IP"] opts += " --host tcp://#{host}" end opts end end end end Dctl::Cli.start ARGV