require 'thor' module Jarl class CLI < Thor # global options # class_option :config, aliases: '-c', type: :string, desc: 'Use specified config file (defaul: ~/.jarl)' class_option :debug # desc 'config', 'Show current configuration' def config Jarl.load(options) puts esc_yellow('config:') + " #{Jarl.config.path}" Jarl.config.params.each do |key, value| puts " #{key}: #{value.inspect}" end puts esc_yellow('jarl files:') Jarl.jarl_files.each do |jarl_file| puts " #{jarl_file.path}" jarl_file.applications.each do |app| puts " #{app.name}" end end end # desc 'status [NAME ...]', '(default) Show applications statuses' def status(*names) Jarl.load(options) ais = Jarl.find_application_instances_by(names) show_jarl_application_instances_status(ais) end default_task :status desc 'up [NAME ...]', 'Start or restart applications' def up(*names) Jarl.load(options) if Jarl.find_application_instances_by(names).any?(&:running?) down(*names) Jarl.reload! end ais = Jarl.find_application_instances_by(names) ais.each do |i| puts esc_green "Starting '#{i.full_name}'..." i.start! end # apps = name ? Jarl.find_applications_by(name) : Jarl.applications # apps.each do |app| # puts esc_green(app.running? ? "Restarting '#{app}'..." : "Starting '#{app}'...") # app.start # end # show_jarl_applications_status(apps) end # desc 'down [NAME ...]', 'Stop applications' def down(*names) Jarl.load(options) ais = Jarl.find_application_instances_by(names).select(&:running?) puts esc_yellow 'No running applications found' unless ais.size > 0 ais.each do |i| puts esc_yellow "Stopping '#{i.full_name}'..." i.stop! end end desc 'execute NAME [COMMAND ...]', 'Run a single command against application image' def execute(name, command = nil, *args) Jarl.load(options) apps = Jarl.find_applications_by(name) if apps.size > 1 puts esc_yellow 'More than one application matches the pattern, using the first one:' puts esc_yellow " #{apps.map(&:full_name).join(', ')}" end app = apps.first abort "Failed to find application by name: '#{name}'" unless app app.execute(command, args) end # desc 'ssh NAME [COMMAND ...]', 'Open an interactive SSH session to a running application or execute a command and exit' def ssh(name, command = nil, *args) Jarl.load(options) ais = Jarl.find_application_instances_by(name).select(&:running?) abort 'No running applications found' unless ais.size > 0 if ais.size > 1 puts esc_yellow 'More than one running application instance matches the pattern, using the first one:' puts esc_yellow " #{ais.map(&:full_name).join(', ')}" end app = ais.first abort "Failed to find application by name: '#{name}'" unless app abort "Application is not running: '#{app.full_name}'" unless app.running? app.ssh([command] + args) end # option :ip, type: :boolean, desc: 'Show only IPs' desc 'inspect [NAME ...]', 'Inspect running application instances' def inspect(*names) Jarl.load(options) ais = Jarl.find_application_instances_by(names).select(&:running?) abort 'No running applications found' unless ais.size > 0 ais.each do |ai| if options[:ip] puts ai.container.ip else show_jarl_application_instance_inspect(ai) end end end desc 'build [NAME ...]', 'Rebuild docker images for Dockerfile based applications' def build(*names) Jarl.load(options) apps = Jarl.find_applications_by(names) apps.select(&:image_is_a_path?).each do |app| puts esc_yellow "Building image for '#{app}'..." app.build end end desc 'pull [NAME ...]', 'Pull docker images for regestry based applications' def pull(*names) Jarl.load(options) apps = Jarl.find_applications_by(names) apps.select(&:image_is_a_registry_path?).each do |app| puts esc_yellow "Pulling image '#{app}'..." app.pull end end option :since, type: :string, desc: 'Show logs from timestamp, like --since 1h' desc 'logs [NAME ...]', 'Show log output for applications' def logs(*names) Jarl.load(options) ais = Jarl.find_application_instances_by(names) threads = [] ais.select(&:running?).flatten.each do |instance| threads << Thread.new { instance.tail_log(options[:since]) } end begin threads.flatten.each(&:join) rescue Interrupt # end puts end desc 'version', 'Show Jarl version' def version puts app_name_version end private def app_name_version "Jarl v#{Jarl::VERSION}" end def show_jarl_application_instances_status(ais) if ais.size == 0 puts esc_yellow 'No applications found' return end full_names = ais.map(&:full_name) max_full_name_len = [full_names.map(&:size).max, 12].max puts esc_format( ['APPLICATION', max_full_name_len], ['RUNNING', 12], ['UPTIME', 12], ['IP', 12], ['PORTS'] ) ais.each do |i| if i.running? puts esc_format( [i.container.name, max_full_name_len, :yellow], [i.container.id, 12, :green], [seconds_to_human(i.container.uptime), 12, :yellow], [i.container.ip, 12, :yellow], [i.container.ports.map { |p| "#{p[:from]}" }.join(', ')] ) else puts esc_format( [i.full_name, max_full_name_len, :yellow], ['no', 12], ['', 12], ['', 12], [''] ) end end end def show_jarl_application_instance_inspect(ai) unless ai.running? puts esc_yellow("#{ai.full_name}: ") + 'off' return end app = ai.application container = ai.container puts esc_yellow "#{container.name}: " + esc_green(container.id) puts " app: #{esc_yellow app.full_name}" puts " image: #{esc_yellow container.image}" puts " full ID: #{esc_yellow container.long_id}" puts " uptime: #{esc_yellow seconds_to_human container.uptime}" puts " IP: #{esc_yellow container.ip}" puts " ports: #{esc_yellow container.ports.map { |p| "#{p[:from]}->#{p[:to]}"}.join(', ')}" puts " volumes: #{esc_yellow container.volumes}" end end # class CLI end # module Jarl