/** * @param $name */ public function dockerInspect($name) { $containerName = $this->getContainerName($name); $command = ['docker', 'inspect', $containerName]; echo 'command | '; echo \Peanut\Console\Color::text(implode(' ', $command), 'white') . PHP_EOL . PHP_EOL; echo $this->process($command, ['print' => false]); }
/** * @param $name * @param $cmd */ public function dockerSsh($name, $cmd = '') { $containerName = $this->getContainerName($name); if (!$cmd) { $cmd = '/bin/bash'; } $command = ['docker', 'exec', '-it', $containerName, $cmd]; echo 'command | '; echo \Peanut\Console\Color::text(implode(' ', $command), 'white') . PHP_EOL . PHP_EOL; $this->process($command, ['print' => false, 'tty' => true]); }
/** * @param $name * @param $force */ public function dockerRm($name, $force = false) { $containerName = $this->getContainerName($name); $command = ['docker', 'rm']; if ($force) { $command[] = '-f'; } $command[] = $containerName; echo 'command | '; echo \Peanut\Console\Color::text(implode(' ', $command), 'white') . PHP_EOL . PHP_EOL; $this->process($command, ['print' => true]); }
/** * @param $name */ public function dockerLog($name, $isFollow = false) { $containerName = $this->getContainerName($name); $command = ['docker', 'logs']; if ($isFollow) { $command[] = '-f'; } $command[] = $containerName; echo 'command | '; echo \Peanut\Console\Color::text(implode(' ', $command), 'white') . PHP_EOL . PHP_EOL; //echo shell_exec(implode(' ', $command)); $this->process($command, ['print' => true]); }
/** * @param \Peanut\Console\Application $app * @param array $config */ public function exec(\Peanut\Console\Application $app, array $config) { $this->process('sudo -v', ['print' => false]); $mode = $app->getOption('attach') ? 'attach' : 'detach'; $ispull = $app->getOption('pull') ? true : false; if ('attach' == $mode) { if (false === function_exists('pcntl_fork')) { $mode = 'detach'; echo \Peanut\Console\Color::text('attach mode is not support, start detach mode', 'red', '') . PHP_EOL; } $this->loop = \React\EventLoop\Factory::create(); try { $this->run(); $pcntl = new \MKraemer\ReactPCNTL\PCNTL($this->loop); $pcntl->on(SIGTERM, function () { // kill echo 'SIGTERM' . PHP_EOL; exit; }); $pcntl->on(SIGHUP, function () { // logout echo 'SIGHUP' . PHP_EOL; exit; }); $pcntl->on(SIGINT, function () { // ctrl+c echo 'Terminated by console' . PHP_EOL; posix_kill(posix_getpid(), SIGUSR1); echo $this->process('docker rm -f $(docker ps -q)'); exit; }); echo 'Started as PID ' . getmypid() . PHP_EOL; $this->loop->run(); } catch (\Exception $e) { throw new \Peanut\Console\Exception($e); } } else { $this->run($mode, $ispull); echo PHP_EOL; $this->dockerLs(); } }
/** * @param $task * @param $action */ public function dockerTask($task, $action) { $containerName = $this->getContainerName($task['container']); $command = ['docker', 'exec']; if (true === isset($task['user'])) { $command[] = '--user='******'user']; } $command[] = '-it'; $command[] = $containerName; $command[] = 'sh -c'; $command[] = '"'; if (true === isset($task['working_dir'])) { $command[] = 'cd ' . $task['working_dir']; $command[] = '&&'; } $command[] = $task['cmd']; if ($action) { $command[] = $action; } $command[] = '"'; echo 'command | '; echo \Peanut\Console\Color::text(implode(' ', $command), 'white') . PHP_EOL . PHP_EOL; $this->process($command, ['print' => true, 'tty' => true]); }
/** * @param $config * TODO : Run before container creation */ public function setCert() { $stageName = $this->getStageName(); $serviceList = []; $stageService = isset($this->config['stages'][$stageName]['services']) ? $this->config['stages'][$stageName]['services'] : []; foreach ($this->config['services'] + $stageService as $key => $value) { $serviceList[] = $this->getContainerName($key); } $command = ['docker', 'inspect', '--format="name={{.Name}}&ip={{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}&service={{index .Config.Labels \\"com.docker.bootapp.service\\"}}&id={{.Id}}&env={{json .Config.Env}}"', '$(docker ps -q)']; $inspectList = $this->process($command, ['print' => false])->toArray(); $machineName = $this->getMachineName(); $projectName = $this->getProjectName(); $id = $machineName . '_' . $projectName; $domainList = []; foreach ($inspectList as $str) { parse_str($str, $b); $envs = json_decode($b['env'], true); $name = ltrim($b['name'], '/'); $env = []; if (true === in_array($name, $serviceList)) { foreach ($envs as $envstring) { list($key, $value) = explode('=', $envstring, 2); $env[$key] = $value; } if (isset($env['DOMAIN']) && isset($env['USE_SSL'])) { $domainList[$b['ip']] = $env['DOMAIN']; } } } if ($domainList) { $SSL_DIR = getcwd() . '/var/certs'; if (false === is_dir(getcwd() . '/var')) { mkdir(getcwd() . '/var'); } if (false === is_dir(getcwd() . '/var/certs')) { mkdir(getcwd() . '/var/certs'); } shell_exec('mkdir -p ' . $SSL_DIR); $dump = $this->process('sudo security dump-trust-settings -d', ['print' => false]); $certList = []; if (preg_match_all('#Cert (?P<key>[0-9]+): (?P<domain>[^\\n]+)#', $dump, $matches)) { $certList = $matches['domain']; } else { } foreach ($domainList as $ip => $domain) { $sslname = $SSL_DIR . '/' . $domain; $this->message(\Peanut\Console\Color::text('cert | ', 'white') . 'domain ' . $domain); //$this->message(\Peanut\Console\Color::text(' | ', 'white').'key ./var/certs/'.$domain.'.key'); $certfile = './var/certs/' . $domain . '.cert'; if (false === file_exists($certfile)) { $command = ['openssl', 'genrsa', '-out', $sslname . '.key', '2048']; $this->process($command, ['print' => false]); $command = ['openssl', 'req', '-new', '-x509', '-key', $sslname . '.key', '-out', $sslname . '.cert', '-days', '3650', '-subj', '/CN=' . $domain]; $this->process($command, ['print' => false]); } else { } if (true === file_exists($certfile) && false === in_array($domain, $certList)) { $this->process('sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ' . $certfile, ['print' => false]); $this->message(\Peanut\Console\Color::text(' | ', 'white') . 'trusted ./var/certs/' . $domain . '.cert'); } else { $this->message(\Peanut\Console\Color::text(' | ', 'white') . 'cert ./var/certs/' . $domain . '.cert'); } } $this->message(); $this->message('# Run this command to configure your ssl certificate:'); $this->message(\Peanut\Console\Color::text('open /Applications/Utilities/Keychain\\ Access.app', 'white')); } }
/** * @param $name * @param $command */ public function childProcess($name, $command) { $process = new \React\ChildProcess\Process($command); $process->on('exit', function ($exitCode, $termSignal) { // ... $this->message($exitCode, 'exit'); $this->message($termSignal, 'exit'); }); if (!$this->color) { $tmp = \Peanut\Console\Color::$foregroundColors; unset($tmp['black'], $tmp['default'], $tmp['white'], $tmp['light_gray']); $this->color = array_keys($tmp); } $color = array_shift($this->color); $this->loop->addTimer(0.001, function ($timer) use($process, $name, $color) { $process->start($timer->getLoop()); $callback = function ($output) use($name, $color) { $lines = explode(PHP_EOL, $output); $i = 0; foreach ($lines as $line) { if ($line) { $tmp = ''; if ($name) { $tmp .= \Peanut\Console\Color::text(str_pad($name, 16, ' ', STR_PAD_RIGHT) . ' | ', $color); } $tmp .= \Peanut\Console\Color::text($line, 'light_gray'); $this->message($tmp); } $i++; } }; $process->stdout->on('data', $callback); $process->stderr->on('data', $callback); }); }
/** * @param $stageName */ public function Containers($mode, $isPull) { $machineName = $this->getMachineName(); $projectName = $this->getProjectName(); if (!$projectName) { echo PHP_EOL; while (true) { $inputProjectName = $this->ask('Please project a name : '); $command = ['docker', 'ps', '-aq', '--filter="label=com.docker.bootapp.project=' . $inputProjectName . '"']; $existsCount = count($this->process($command, ['print' => false])->toArray()); if (0 == $existsCount) { break; } else { echo 'Name invalid. '; } } $this->config = ['project_name' => $inputProjectName] + $this->config; \App\Helpers\Yaml::dumpFile($this->configFileName, $this->config); $projectName = $this->getProjectName(); } $stageName = $this->getStageName(); $machineName = $this->getMachineName(); $compose = \App\Helpers\Yaml::parseFile(getcwd() . '/docker-compose.' . $stageName . '.yml'); echo \Peanut\Console\Color::text('machine | ', 'white') . $machineName . PHP_EOL; echo \Peanut\Console\Color::text('project | ', 'white') . $projectName . PHP_EOL; echo \Peanut\Console\Color::text('stage | ', 'white') . $stageName . PHP_EOL; $checkBuild = false; $checkPull = false; foreach ($compose['services'] as $serviceName => &$service) { if (true === isset($service['name'])) { $name = $service['name']; } else { $name = $serviceName; } $service['org_name'] = $name; $service['name'] = $this->getContainerName($name); if (true === isset($service['build'])) { $checkBuild = true; } elseif (true === isset($service['image'])) { $checkPull = true; } } // break the reference with the last element unset($service); if ($checkPull && $isPull) { echo \Peanut\Console\Color::text('pull | ', 'white'); foreach ($compose['services'] as $serviceName => $service) { if (true === isset($service['image'])) { $command = ['docker', 'pull', $service['image']]; //$this->message('build '.$service['name']); echo $service['org_name'] . ' '; $this->process($command, ['print' => false]); } } echo PHP_EOL; } if ($checkBuild) { echo \Peanut\Console\Color::text('build | ', 'white'); foreach ($compose['services'] as $serviceName => $service) { if (true === isset($service['build'])) { $buildOpts = []; $buildOpts[] = 'docker'; $buildOpts[] = 'build'; $buildOpts[] = '--tag=' . $service['name']; if (true === is_array($service['build'])) { if (true === isset($service['build']['args'])) { foreach ($service['build']['args'] as $argKey => $argValue) { $buildOpts[] = '--build-arg ' . $argKey . '=' . escapeshellarg($argValue); } } if (true === isset($service['build']['context'])) { $buildOpts[] = $service['build']['context']; } else { throw new \Console\Exception('build context not found'); } } else { $buildOpts[] = $service['build']; } //$this->message('build '.$service['name']); echo $service['org_name'] . ' '; $this->process($buildOpts, ['print' => true]); } } echo PHP_EOL; } echo \Peanut\Console\Color::text('remove | ', 'white'); foreach ($compose['services'] as $serviceName => $service) { echo $service['org_name'] . ' '; $rmCommand = ['docker', 'rm', '-f', $service['name'], '2>&1']; $this->process($rmCommand, ['print' => false]); } echo PHP_EOL; if (true === isset($compose['networks'])) { } else { // default network setting $compose['networks'] = ['default' => ['ipam' => ['config' => [['subnet']]]]]; } if (true === isset($compose['networks'])) { $defaultName = $projectName; foreach ($compose['networks'] as $networkName => $network) { $dockerNetworks = $this->getNetworkList(); if ('default' == $networkName) { $networkName = 'default[' . $defaultName . ']'; } //$this->networkName[] = $networkName; // --net 은 배열이 아니다. $this->networkName = [$networkName]; if (true === isset($dockerNetworks[$networkName])) { $networkRmcommand = ['docker', 'network', 'rm', $networkName, '2>&1']; $this->process($networkRmcommand, ['print' => false]); unset($dockerNetworks[$networkName]); } foreach ($dockerNetworks as $dockerNetworkName => $dockerNetworkSubnet) { foreach ($network['ipam']['config'] as $configSubnet) { if (true === isset($configSubnet['subnet']) && $configSubnet['subnet'] == $dockerNetworkSubnet) { $this->message(\Peanut\Console\Color::text($networkName . ' conflicts with network ' . $dockerNetworkName . ', subnet ' . $dockerNetworkSubnet, 'red')); echo 'delete? [y/N]: '; $handle = fopen('php://stdin', 'r'); $line = fgets($handle); fclose($handle); if (false === in_array(trim($line), ['y', 'Y'])) { throw new \Peanut\Console\Exception($networkName . ' conflicts with network ' . $dockerNetworkName . ', subnet ' . $dockerNetworkSubnet); } else { $networkRmCommand = ['docker', 'network', 'rm', $dockerNetworkName]; $this->process($networkRmCommand, ['print' => false]); } } } } $subnet = []; foreach ($network['ipam']['config'] as $configSubnet) { if (true === isset($configSubnet['subnet']) && $configSubnet['subnet']) { $subnet[] = $configSubnet['subnet']; } } $networkCreateCommand = ['docker', 'network', 'create', '--driver=bridge']; if ($subnet) { $networkCreateCommand[] = '--subnet=' . implode(' --subnet=', $subnet); } else { $subnetFile = $this->process('echo $HOME', ['print' => false])->toString() . '/.docker/docker-machine-subnet.yaml'; if (false === is_file($subnetFile)) { $this->process('touch ' . $subnetFile, ['print' => false]); } $subnets = \App\Helpers\Yaml::parseFile($subnetFile); if (false === is_array($subnets)) { $subnets = []; } if (true === isset($subnets[$machineName][$projectName])) { $subnet = $subnets[$machineName][$projectName]; } else { $bridge = $this->process("docker network inspect --format='{{range .IPAM.Config}}{{.Subnet}}{{end}}' bridge", ['print' => false])->toString(); $subnetIps = []; foreach ($subnets as $machines) { if (true === is_array($machines)) { foreach ($machines as $projectIp) { $subnetIps[] = $projectIp; } } } while (1) { $subnet = '172.' . rand(0, 255) . '.0.0/16'; if ($subnet == $bridge) { continue; } if (false == in_array($subnet, $subnetIps)) { break; } } $subnets[$machineName][$projectName] = $subnet; \App\Helpers\Yaml::dumpFile($subnetFile, $subnets); } $networkCreateCommand[] = '--subnet=' . $subnet; } $networkCreateCommand[] = $networkName; $networkCreateCommand[] = '2>&1'; $this->process($networkCreateCommand, ['print' => false]); $networkInspectCommand = ['docker', 'network', 'inspect', '--format="{{range .IPAM.Config}}{{.Subnet}}{{end}}"', $networkName, '2>&1']; $subnet = $this->process($networkInspectCommand, ['print' => false])->toArray(); if (!$subnet) { throw new \Peanut\Console\Exception('network ' . $networkName . ' not found'); } echo \Peanut\Console\Color::text('network | ', 'white') . 'recreate ' . $networkName . ', subnet ' . implode(' ', $subnet) . PHP_EOL; } } if ('attach' == $mode) { echo \Peanut\Console\Color::text('create | ', 'white'); } else { echo \Peanut\Console\Color::text('run | ', 'white'); } $runCommands = []; foreach ($compose['services'] as $serviceName => $service) { $command = []; if ('attach' == $mode) { $command[] = 'docker create'; $command[] = '-a STDIN'; $command[] = '-a STDOUT'; $command[] = '-a STDERR'; $command[] = '-i'; } else { $command[] = 'docker run'; $command[] = '-d'; $command[] = '-i'; if (true === isset($service['tty'])) { $command[] = '--tty'; } } if (true === isset($service['privileged'])) { $command[] = '--privileged'; } if ($this->networkName) { foreach ($this->networkName as $networkName) { $command[] = '--net=' . $networkName; } } if (true === isset($service['networks'])) { foreach ($service['networks'] as $name => $conf) { if (true === isset($conf['ipv4_address'])) { $command[] = '--ip=' . $conf['ipv4_address']; } if (true === isset($conf['ipv6_address'])) { $command[] = '--ip6=' . $conf['ipv6_address']; } } } if (true === isset($service['dns'])) { foreach ($service['dns'] as $value) { $command[] = '--dns=' . $value; } } if (true === isset($service['env-file'])) { foreach ($service['env-file'] as $value) { $command[] = '--env-file=' . $value; } } $command[] = '-e TERM=xterm'; if (true === isset($service['environment'])) { foreach ($service['environment'] as $key => $value) { if (true === is_array($value)) { $value = "'" . json_encode($value, JSON_UNESCAPED_SLASHES) . "'"; } $command[] = '-e ' . $key . '=' . escapeshellarg($value); } } if (true === isset($service['expose'])) { foreach ($service['expose'] as $key => $value) { $command[] = '--expose=' . $value; } } if (true === isset($service['user'])) { $command[] = '--user='******'user']; } if (true === isset($service['hostname'])) { $command[] = '--hostname=' . $service['hostname']; } $addHost = []; if (true === isset($service['links'])) { foreach ($service['links'] as $key => $value) { foreach ($this->networkName as $networkName) { $inspectCommand = ['docker', 'inspect', '--format="service={{index .Config.Labels \\"com.docker.bootapp.service\\"}}&&ip={{with index .NetworkSettings.Networks \\"' . $networkName . '\\"}}{{.IPAddress}}{{end}}"', $this->getContainerName($value)]; $str = $this->process($inspectCommand, ['print' => false])->toString(); parse_str($str, $out); if (true === isset($out['ip']) && true === isset($out['service'])) { $ip = $out['ip']; $serviceName = $out['service']; $addHost[$ip] = $serviceName . ' ' . $this->getContainerName($value); } } $command[] = '--link=' . $this->getContainerName($value); } } foreach ($addHost as $ip => $host) { $command[] = '--add-host="' . $host . '":' . $ip; } if (true === isset($service['net'])) { $command[] = '--net=' . $service['net']; } if (true === isset($service['working_dir'])) { $command[] = '--workdir=' . $service['working_dir']; } if (true === isset($service['ports'])) { foreach ($service['ports'] as $value) { $command[] = '--publish=' . $value; } } else { //$command[] = '-P'; } if (true === isset($service['restart'])) { $command[] = '--restart=' . $service['restart']; } if (true === isset($service['volumes'])) { foreach ($service['volumes'] as $value) { $command[] = '-v ' . $this->volumeRealPath($value); } } if (true === isset($service['volumes_from'])) { foreach ($service['volumes_from'] as $value) { $command[] = '--volumes-from=' . ($projectName ? $projectName . '-' . $value : $value); } } if (true === isset($service['entrypoint'])) { $command[] = '--entrypoint=' . $service['entrypoint']; } $command[] = '--name=' . $service['name']; if (true === isset($service['labels'])) { foreach ($service['labels'] as $labelName => $labelValue) { $command[] = '--label ' . $labelName . '="' . $labelValue . '"'; } } if (true === isset($service['environment']['DOMAIN'])) { if (false === strpos($service['environment']['DOMAIN'], ' ')) { $command[] = '--label com.docker.bootapp.domain="' . $service['environment']['DOMAIN'] . '"'; } else { throw new \Peanut\Console\Exception('domain name not valid'); } } if (true === isset($service['image'])) { $command[] = $service['image']; } if (true === isset($service['build'])) { $command[] = $service['name']; } if (true === isset($service['command'])) { $command[] = $service['command']; } $runCommands[] = implode(' ', $command); $this->process($command, ['print' => false]); // create echo $service['org_name'] . ' '; } echo PHP_EOL; if ('attach' == $mode) { echo \Peanut\Console\Color::text('start | ', 'white'); foreach ($compose['services'] as $serviceName => $service) { echo $service['org_name'] . ' '; $command = ['docker', 'start', $service['name']]; $this->process($command, ['print' => false]); } echo PHP_EOL; echo 'attach | '; foreach ($compose['services'] as $serviceName => $service) { echo $service['org_name'] . ' '; $command = ['docker', 'logs', $service['name'], '&&', 'docker', 'attach', '--sig-proxy=true', $service['name']]; $this->childProcess($service['name'], implode(' ', $command)); } echo PHP_EOL; } else { } }