class Kaiser::Cli
The commandline
Public Class Methods
all_subcommands_usage()
click to toggle source
# File lib/kaiser/cli.rb, line 80 def self.all_subcommands_usage output = '' @subcommands.each do |name, klass| name_s = name.to_s output += name_s + "\n" output += name_s.gsub(/./, '-') output += "\n" output += klass.usage output += "\n\n" end output end
register(name, klass)
click to toggle source
# File lib/kaiser/cli.rb, line 43 def self.register(name, klass) @subcommands ||= {} @subcommands[name] = klass.new end
run_command(name, global_opts)
click to toggle source
# File lib/kaiser/cli.rb, line 48 def self.run_command(name, global_opts) cmd = @subcommands[name] opts = cmd.define_options(global_opts + cmd.class.options) # The define_options method has stripped all arguments from the cli so now # all that we're left with in ARGV are the subcommand to be run and possibly # its own subcommands. We remove the subcommand here so each subcommand can # easily use ARGV.shift to access its own subcommands. ARGV.shift Kaiser::Config.load(Dir.pwd) # We do all this work in here instead of the exe/kaiser file because we # want -h options to output before we check if a Kaiserfile exists. # If we do it in exe/kaiser, people won't be able to check help messages # unless they create a Kaiserfile firest. if opts[:quiet] Config.out = File.open(File::NULL, 'w') Config.info_out = File.open(File::NULL, 'w') elsif opts[:verbose] || Config.always_verbose? Config.out = $stderr Config.info_out = Kaiser::AfterDotter.new(dotter: Kaiser::Dotter.new) else Config.out = Kaiser::Dotter.new Config.info_out = Kaiser::AfterDotter.new(dotter: Config.out) end cmd.set_config cmd.execute(opts) end
Public Instance Methods
define_options(global_opts = [])
click to toggle source
At first I did this in the constructor but the problem with that is Optimist will parse the entire commandline for the first Cli
command registered. That means no matter what you call -h or –help on, it will always return the help for the first subcommand. Fixed this by only running define_options
when a command is run. We can't just run the constructor at that point because we need each Cli
class to be constructed in the beginning so we can add their usage text to the output of `kaiser -h`.
# File lib/kaiser/cli.rb, line 31 def define_options(global_opts = []) # We can't just call usage within the options block because that actually shifts # the scope to Optimist::Parser. We can still reference variables but we can't # call instance methods of a Kaiser::Cli class. u = usage Optimist.options do banner u global_opts.each { |o| opt *o } end end
set_config()
click to toggle source
# File lib/kaiser/cli.rb, line 10 def set_config # This is here for backwards compatibility since it can be used in Kaiserfiles. # It would be a good idea to deprecate this and make it more abstract. @work_dir = Config.work_dir @config_dir = Config.work_dir @config_file = Config.config_file @kaiserfile = Config.kaiserfile @config = Config.config @out = Config.out @info_out = Config.info_out @kaiserfile.validate! end
start_services()
click to toggle source
# File lib/kaiser/cli.rb, line 102 def start_services services.each do |service| Config.info_out.puts "Starting service: #{service.name}" run_if_dead( service.shared_name, "docker run -d --name #{service.shared_name} --network #{Config.config[:networkname]} #{service.image}" ) end end
stop_app()
click to toggle source
# File lib/kaiser/cli.rb, line 96 def stop_app Config.info_out.puts 'Stopping application' killrm app_container_name stop_services end
stop_services()
click to toggle source
# File lib/kaiser/cli.rb, line 115 def stop_services services.each do |service| Config.info_out.puts "Stopping service: #{service.name}" killrm service.shared_name end end
Private Instance Methods
app_container_name()
click to toggle source
# File lib/kaiser/cli.rb, line 445 def app_container_name "#{envname}-app" end
app_expose()
click to toggle source
# File lib/kaiser/cli.rb, line 437 def app_expose Config.kaiserfile.port end
app_params()
click to toggle source
# File lib/kaiser/cli.rb, line 421 def app_params eval_template Config.kaiserfile.params end
app_port()
click to toggle source
# File lib/kaiser/cli.rb, line 433 def app_port Config.config[:envs][envname][:app_port] end
attach_app()
click to toggle source
# File lib/kaiser/cli.rb, line 232 def attach_app cmd = (ARGV || []).join(' ') killrm app_container_name attach_mounts = Config.kaiserfile.attach_mounts volumes = attach_mounts.map { |from, to| "-v #{`pwd`.chomp}/#{from}:#{to}" }.join(' ') system "docker run -ti --name #{app_container_name} --network #{network_name} --dns #{ip_of_container(Config.config[:shared_names][:dns])} --dns-search #{http_suffix} -p #{app_port}:#{app_expose} -e DEV_APPLICATION_HOST=#{envname}.#{http_suffix} -e VIRTUAL_HOST=#{envname}.#{http_suffix} -e VIRTUAL_PORT=#{app_expose} #{volumes} #{app_params} kaiser:#{envname}-#{current_branch} #{cmd}".tr("\n", ' ') Config.out.puts 'Cleaning up...' end
check_db_image_exists(name)
click to toggle source
# File lib/kaiser/cli.rb, line 166 def check_db_image_exists(name) return if File.exist?(db_image_path(name)) Optimist.die 'No saved state exists with that name' end
container_dead?(container)
click to toggle source
# File lib/kaiser/cli.rb, line 553 def container_dead?(container) x = JSON.parse(`docker inspect #{container} 2>/dev/null`) return true if x.length.zero? || x[0]['State']['Running'] == false end
copy_keyfile(file)
click to toggle source
# File lib/kaiser/cli.rb, line 467 def copy_keyfile(file) if Config.config[:cert_source][:folder] CommandRunner.run! Config.out, "docker run --rm -v #{Config.config[:shared_names][:certs]}:/certs -v #{Config.config[:cert_source][:folder]}:/cert_source alpine cp /cert_source/#{file} /certs/#{file}" elsif Config.config[:cert_source][:url] CommandRunner.run! Config.out, "docker run --rm -v #{Config.config[:shared_names][:certs]}:/certs alpine wget #{Config.config[:cert_source][:url]}/#{file} -O /certs/#{file}" end end
create_if_network_not_exist(net)
click to toggle source
# File lib/kaiser/cli.rb, line 571 def create_if_network_not_exist(net) x = JSON.parse(`docker inspect #{net} 2>/dev/null`) return unless x.length.zero? CommandRunner.run! Config.out, "docker network create #{net}" end
create_if_volume_not_exist(vol)
click to toggle source
# File lib/kaiser/cli.rb, line 564 def create_if_volume_not_exist(vol) x = JSON.parse(`docker volume inspect #{vol} 2>/dev/null`) return unless x.length.zero? CommandRunner.run! Config.out, "docker volume create #{vol}" end
current_branch()
click to toggle source
# File lib/kaiser/cli.rb, line 453 def current_branch `git branch | grep \\* | cut -d ' ' -f2`.chomp.gsub(/[^\-_0-9a-z]+/, '-') end
current_branch_db_image_dir()
click to toggle source
# File lib/kaiser/cli.rb, line 214 def current_branch_db_image_dir "#{Config.config_dir}/databases/#{envname}/#{current_branch}" end
db_commands()
click to toggle source
# File lib/kaiser/cli.rb, line 393 def db_commands eval_template Config.kaiserfile.database[:commands] end
db_container_name()
click to toggle source
# File lib/kaiser/cli.rb, line 449 def db_container_name "#{envname}-db" end
db_data_directory()
click to toggle source
# File lib/kaiser/cli.rb, line 397 def db_data_directory Config.kaiserfile.database[:data_dir] end
db_expose()
click to toggle source
# File lib/kaiser/cli.rb, line 381 def db_expose Config.kaiserfile.database[:port] end
db_image()
click to toggle source
# File lib/kaiser/cli.rb, line 389 def db_image Config.kaiserfile.database[:image] end
db_image_path(name)
click to toggle source
# File lib/kaiser/cli.rb, line 218 def db_image_path(name) if name.start_with?('./') path = "#{`pwd`.chomp}/#{name.sub('./', '')}" Config.info_out.puts "Database image path is: #{path}" return path end FileUtils.mkdir_p current_branch_db_image_dir "#{current_branch_db_image_dir}/#{name}.tar.bz" end
db_params()
click to toggle source
# File lib/kaiser/cli.rb, line 385 def db_params eval_template Config.kaiserfile.database[:params] end
db_port()
click to toggle source
# File lib/kaiser/cli.rb, line 377 def db_port Config.config[:envs][envname][:db_port] end
db_reset_command()
click to toggle source
# File lib/kaiser/cli.rb, line 425 def db_reset_command eval_template Config.kaiserfile.database_reset_command end
db_volume_name()
click to toggle source
# File lib/kaiser/cli.rb, line 441 def db_volume_name "#{envname}-database" end
db_waitscript()
click to toggle source
# File lib/kaiser/cli.rb, line 405 def db_waitscript eval_template Config.kaiserfile.database[:waitscript] end
db_waitscript_params()
click to toggle source
# File lib/kaiser/cli.rb, line 409 def db_waitscript_params eval_template Config.kaiserfile.database[:waitscript_params] end
default_db_image()
click to toggle source
# File lib/kaiser/cli.rb, line 228 def default_db_image db_image_path('.default') end
delete_db_volume()
click to toggle source
# File lib/kaiser/cli.rb, line 210 def delete_db_volume CommandRunner.run Config.out, "docker volume rm #{db_volume_name}" end
docker_build_args()
click to toggle source
# File lib/kaiser/cli.rb, line 417 def docker_build_args Config.kaiserfile.docker_build_args end
docker_file_contents()
click to toggle source
# File lib/kaiser/cli.rb, line 413 def docker_file_contents eval_template Config.kaiserfile.docker_file_contents end
ensure_db_volume()
click to toggle source
# File lib/kaiser/cli.rb, line 124 def ensure_db_volume create_if_volume_not_exist db_volume_name end
ensure_env()
click to toggle source
# File lib/kaiser/cli.rb, line 457 def ensure_env return unless envname.nil? Optimist.die('No environment? Please use kaiser init <name>') end
ensure_setup()
click to toggle source
# File lib/kaiser/cli.rb, line 495 def ensure_setup ensure_env setup if network.nil? create_if_network_not_exist Config.config[:networkname] if_container_dead Config.config[:shared_names][:nginx] do prepare_cert_volume! end run_if_dead( Config.config[:shared_names][:redis], "docker run -d --name #{Config.config[:shared_names][:redis]} --network #{Config.config[:networkname]} redis:alpine" ) run_if_dead( Config.config[:shared_names][:chrome], "docker run -d -p 5900:5900 --name #{Config.config[:shared_names][:chrome]} --network #{Config.config[:networkname]} selenium/standalone-chrome-debug" ) run_if_dead( Config.config[:shared_names][:nginx], "docker run -d -p 80:80 -p 443:443 -v #{Config.config[:shared_names][:certs]}:/etc/nginx/certs -v /var/run/docker.sock:/tmp/docker.sock:ro --privileged --name #{Config.config[:shared_names][:nginx]} --network #{Config.config[:networkname]} jwilder/nginx-proxy" ) run_if_dead( Config.config[:shared_names][:dns], "docker run -d --name #{Config.config[:shared_names][:dns]} --network #{Config.config[:networkname]} --privileged -v /var/run/docker.sock:/docker.sock:ro davidsiaw/docker-dns --domain #{http_suffix} --record :#{ip_of_container(Config.config[:shared_names][:nginx])}" ) end
envname()
click to toggle source
# File lib/kaiser/cli.rb, line 586 def envname Config.config[:envnames][Config.work_dir] end
eval_template(value)
click to toggle source
# File lib/kaiser/cli.rb, line 429 def eval_template(value) ERB.new(value).result(binding) end
http_suffix()
click to toggle source
# File lib/kaiser/cli.rb, line 463 def http_suffix Config.config[:http_suffix] || 'lvh.me' end
if_container_dead(container) { || ... }
click to toggle source
# File lib/kaiser/cli.rb, line 558 def if_container_dead(container) return unless container_dead?(container) yield if block_given? end
ip_of_container(containername)
click to toggle source
# File lib/kaiser/cli.rb, line 544 def ip_of_container(containername) networkname = ".NetworkSettings.Networks.#{Config.config[:networkname]}.IPAddress" `docker inspect -f '{{#{networkname}}}' #{containername}`.chomp end
killrm(container)
click to toggle source
# File lib/kaiser/cli.rb, line 594 def killrm(container) x = JSON.parse(`docker inspect #{container} 2>/dev/null`) return if x.length.zero? CommandRunner.run Config.out, "docker kill #{container}" if x[0]['State'] && x[0]['State']['Running'] == true CommandRunner.run Config.out, "docker rm #{container}" if x[0]['State'] end
load_db(name)
click to toggle source
# File lib/kaiser/cli.rb, line 156 def load_db(name) check_db_image_exists(name) killrm db_container_name CommandRunner.run Config.out, "docker volume rm #{db_volume_name}" delete_db_volume create_if_volume_not_exist db_volume_name load_db_state_from file: db_image_path(name), to_container: db_volume_name start_db end
load_db_state_from(file:, to_container:)
click to toggle source
# File lib/kaiser/cli.rb, line 182 def load_db_state_from(file:, to_container:) Config.info_out.puts 'Loading database state' CommandRunner.run Config.out, "docker run --rm -v #{to_container}:#{db_data_directory} -v #{file}:#{file} ruby:alpine tar xvjf #{file} -C #{db_data_directory} --strip #{db_data_directory.scan(%r{\/}).count}" end
network()
click to toggle source
# File lib/kaiser/cli.rb, line 549 def network `docker network inspect #{Config.config[:networkname]} 2>/dev/null` end
network_name()
click to toggle source
# File lib/kaiser/cli.rb, line 369 def network_name Config.config[:networkname] end
prepare_cert_volume!()
click to toggle source
# File lib/kaiser/cli.rb, line 482 def prepare_cert_volume! create_if_volume_not_exist Config.config[:shared_names][:certs] return unless Config.config[:cert_source] %w[ chain.pem crt key ].each do |file_ext| copy_keyfile("#{http_suffix}.#{file_ext}") end end
run_blocking_script(image, params, script, &block)
click to toggle source
# File lib/kaiser/cli.rb, line 294 def run_blocking_script(image, params, script, &block) killrm tmp_db_waiter killrm tmp_file_container create_if_volume_not_exist tmp_file_volume CommandRunner.run! Config.out, "docker create -v #{tmp_file_volume}:/tmpvol --name #{tmp_file_container} alpine" File.write(tmp_waitscript_name, script) CommandRunner.run! Config.out, "docker cp #{tmp_waitscript_name} #{tmp_file_container}:/tmpvol/wait.sh" CommandRunner.run!( Config.out, "docker run --rm -ti --name #{tmp_db_waiter} --network #{network_name} -v #{tmp_file_volume}:/tmpvol #{params} #{image} sh /tmpvol/wait.sh", &block ) ensure killrm tmp_file_container FileUtils.rm(tmp_waitscript_name) end
run_if_dead(container, command)
click to toggle source
# File lib/kaiser/cli.rb, line 578 def run_if_dead(container, command) if_container_dead container do Config.info_out.puts "Starting up #{container}" killrm container CommandRunner.run Config.out, command end end
save_config()
click to toggle source
# File lib/kaiser/cli.rb, line 590 def save_config File.write(Config.config_file, Config.config.to_yaml) end
save_db(name)
click to toggle source
# File lib/kaiser/cli.rb, line 150 def save_db(name) killrm db_container_name save_db_state_from container: db_volume_name, to_file: db_image_path(name) start_db end
save_db_state_from(container:, to_file:)
click to toggle source
# File lib/kaiser/cli.rb, line 172 def save_db_state_from(container:, to_file:) Config.info_out.puts 'Saving database state' File.write(to_file, '') CommandRunner.run Config.out, "docker run --rm -v #{container}:#{db_data_directory} -v #{to_file}:#{to_file} ruby:alpine tar cvjf #{to_file} #{db_data_directory}" end
server_type()
click to toggle source
# File lib/kaiser/cli.rb, line 401 def server_type Config.kaiserfile.server_type end
services()
click to toggle source
# File lib/kaiser/cli.rb, line 373 def services @services ||= Config.kaiserfile.services.map { |name, info| Service.new(envname, name, info) } end
setup_db()
click to toggle source
# File lib/kaiser/cli.rb, line 128 def setup_db ensure_db_volume start_db return if File.exist?(default_db_image) # Some databases keep state around, best to clean it. stop_db delete_db_volume start_db Config.info_out.puts 'Provisioning database' killrm "#{envname}-apptemp" CommandRunner.run! Config.out, "docker run -ti --rm --name #{envname}-apptemp --network #{Config.config[:networkname]} #{app_params} kaiser:#{envname}-#{current_branch} #{db_reset_command}" save_db('.default') end
start_app()
click to toggle source
# File lib/kaiser/cli.rb, line 255 def start_app start_services Config.info_out.puts 'Starting up application' killrm app_container_name CommandRunner.run! Config.out, "docker run -d --name #{app_container_name} --network #{network_name} --dns #{ip_of_container(Config.config[:shared_names][:dns])} --dns-search #{http_suffix} -p #{app_port}:#{app_expose} -e DEV_APPLICATION_HOST=#{envname}.#{http_suffix} -e VIRTUAL_HOST=#{envname}.#{http_suffix} -e VIRTUAL_PORT=#{app_expose} #{app_params} kaiser:#{envname}-#{current_branch}" wait_for_app end
start_db()
click to toggle source
# File lib/kaiser/cli.rb, line 197 def start_db Config.info_out.puts 'Starting up database' run_if_dead db_container_name, "docker run -d -p #{db_port}:#{db_expose} -v #{db_volume_name}:#{db_data_directory} --name #{db_container_name} --network #{network_name} #{db_params} #{db_image} #{db_commands}" wait_for_db unless db_waitscript.nil? end
stop_db()
click to toggle source
# File lib/kaiser/cli.rb, line 192 def stop_db Config.info_out.puts 'Stopping database' killrm db_container_name end
tmp_db_waiter()
click to toggle source
# File lib/kaiser/cli.rb, line 282 def tmp_db_waiter "#{envname}-dbwait" end
tmp_dockerfile_name()
click to toggle source
# File lib/kaiser/cli.rb, line 278 def tmp_dockerfile_name "#{Config.config_dir}/.#{envname}-dockerfile" end
tmp_file_container()
click to toggle source
# File lib/kaiser/cli.rb, line 286 def tmp_file_container "#{envname}-tmpfiles" end
tmp_file_volume()
click to toggle source
# File lib/kaiser/cli.rb, line 290 def tmp_file_volume "#{envname}-tmpfiles-vol" end
tmp_waitscript_name()
click to toggle source
# File lib/kaiser/cli.rb, line 274 def tmp_waitscript_name "#{Config.config_dir}/.#{envname}-dbwaitscript" end
wait_for_app()
click to toggle source
# File lib/kaiser/cli.rb, line 325 def wait_for_app return unless server_type == :http Config.info_out.puts 'Waiting for server to start...' http_code_extractor = "curl -s -o /dev/null -I -w \"\%{http_code}\" http://#{app_container_name}:#{app_expose}" unreachable_test = "#{http_code_extractor} | grep -q 000" # This waitscript runs until curl returns a non-unreachable status code # and then checks to see if its 200. If its not, it will raise an error. wait_script = <<-SCRIPT apk update apk add curl while #{unreachable_test}; do echo 'o' sleep 1 done echo '#{http_code_extractor}' echo $(#{http_code_extractor}) if [ "$(#{http_code_extractor})" != "200" ]; then echo $(#{http_code_extractor}) else echo '!' fi SCRIPT run_blocking_script('alpine', '', wait_script) do |line| # This script gets run every line that gets output. # The '!' exclamation mark means success # Three numbers means a status code has been returned # If curl returns an error status the script will cut out and # the app container died error will be displayed. raise Kaiser::Error, "Failed with HTTP status: #{line}" if line =~ /^[0-9]{3}$/ && line != '200' raise Kaiser::Error, 'App container died. Run `kaiser logs` to see why.' if line != '!' && container_dead?(app_container_name) end Config.info_out.puts 'Started successfully!' end
wait_for_db()
click to toggle source
# File lib/kaiser/cli.rb, line 363 def wait_for_db Config.info_out.puts 'Waiting for database to start...' run_blocking_script(db_image, db_waitscript_params, db_waitscript) Config.info_out.puts 'Started.' end