require 'yaml' require 'openssl' module AppRb class Cli def initialize(args) @args = args Thread.abort_on_exception = true end def run if @args.count < 2 usage exit end if File.exists?(@args[0]) config_path = @args.shift end if @args[0] == "--vault" && File.exists?(@args[1]) @args.shift @vault_file = @args.shift elsif File.exists?(File.join(Dir.pwd, "vault.key")) @vault_file = File.join(Dir.pwd, "vault.key") else @vault_file = nil end command = @args.shift config = Config.new(read_yaml(config_path)) if config_path if config && AppRb::Util.compare_versions(config.tool_version, AppRb::VERSION) > 0 puts "FATAL: need at least '#{config.tool_version}' tool version but current version is '#{AppRb::VERSION}'" exit -1 end if %w[deploy d].index(command) Command.new(config).deploy(@args[2]) elsif %w[status s].index(command) Command.new(config).status elsif command == "redeploy" Command.new(config).redeploy elsif command == "clean" Command.new(config).clean elsif command == "stop" Command.new(config).stop elsif %w[run r].index(command) Command.new(config).run(@args[2..-1].join(" ")) elsif command == "cd" Command.new(config).cd elsif %w[encrypt en e].index(command) puts encrypt(@args.shift) elsif %w[decrypt de].index(command) puts decrypt(@args.shift) else puts "FATAL: unknown command '#{command}'" exit -1 end end private MARKER = "__VAULT:" def read_yaml(file) unvault = proc { |o| if o.is_a?(Array) o.map { |i| unvault.call(i) } elsif o.is_a?(Hash) o.map { |k, v| [k, unvault.call(v)] }.to_h elsif o.is_a?(String) && o.index(MARKER) == 0 decrypt(o) else o end } unvault.call(YAML.load(File.read(file))) end def die(msg = nil) if msg puts "FATAL: #{msg}" else puts "exit with status code -1" end exit -1 end def bin_to_hex(s) s.unpack('H*').first end def hex_to_bin(s) s.scan(/../).map { |x| x.hex }.pack('c*') end def encrypt(string) die "missed vault file" unless @vault_file die "string encrypted yet" if string.index(MARKER) == 0 cipher = OpenSSL::Cipher::AES256.new :CBC cipher.encrypt iv = cipher.random_iv cipher.key = Digest::SHA256.digest(File.read(@vault_file).strip) MARKER + bin_to_hex(cipher.update(string) + cipher.final) + ":" + bin_to_hex(iv) end def decrypt(string) die "missed vault file" unless @vault_file die "string not encrypted" unless string.index(MARKER) == 0 cipher = OpenSSL::Cipher::AES256.new :CBC cipher.decrypt cipher.iv = hex_to_bin(string.sub(MARKER, "").split(":")[1]) cipher.key = Digest::SHA256.digest(File.read(@vault_file).strip) cipher.update(hex_to_bin(string.sub(MARKER, "").split(":")[0])) + cipher.final end def usage puts "Just deploy your apps with docker and consul. Nothing else." puts "Version: #{AppRb::VERSION}" puts "" puts " app-rb [options] " puts "" puts "Options:" puts " --vault - vault file (/vault.key by default)" puts "" puts "Commands:" puts " deploy [hash] - deploy new version of app" puts " status - status of app" puts " stop - stop app completely" puts " run [args] - one time command" puts " cd - go to run node" puts " encrypt - encrypt script using vault-file" puts " decrypt - decrypt script using vault-file" puts "" puts "Advanced commands:" puts " redeploy - redeploy app" puts " clean - stop and remove not current containers" end end end