diff --git a/app/Commands/AwsSsmStartSession.php b/app/Commands/AwsSsmStartSession.php index 5526660..60ad613 100644 --- a/app/Commands/AwsSsmStartSession.php +++ b/app/Commands/AwsSsmStartSession.php @@ -2,11 +2,9 @@ namespace App\Commands; -use App\Exceptions\ProcessFailed; use App\Helpers\Helpers; -use Symfony\Component\Process\Process; +use App\Exceptions\ProcessFailed; use LaravelZero\Framework\Commands\Command; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; class AwsSsmStartSession extends Command @@ -101,7 +99,6 @@ private function generateTempSshKey() '-f', $keyName, '-C', "netsells-cli-ssm-ssh-session" ]) - ->echoLineByLineOutput(false) ->run(); } catch (ProcessFailed $e) { $this->error("Unable to generate temp ssh key."); diff --git a/app/Commands/DockerBuildCommand.php b/app/Commands/DockerBuildCommand.php index f302205..1bb008d 100644 --- a/app/Commands/DockerBuildCommand.php +++ b/app/Commands/DockerBuildCommand.php @@ -2,6 +2,7 @@ namespace App\Commands; +use App\Exceptions\ProcessFailed; use App\Helpers\Helpers; use App\Helpers\NetsellsFile; use Symfony\Component\Process\Process; @@ -93,24 +94,18 @@ public function handle() protected function callBuild(string $tag, string $service = null): bool { - $process = new Process([ - 'docker-compose', - '-f', 'docker-compose.yml', - '-f', 'docker-compose.prod.yml', - 'build', '--no-cache', $service - ], null, [ - 'TAG' => $tag, - ], null, 1200); // 20min timeout - - $process->start(); - - foreach ($process as $data) { - echo $data; - } - - $process->wait(); - - if ($process->getExitCode() !== 0) { + try { + $this->helpers->process()->withCommand([ + 'docker-compose', + '-f', 'docker-compose.yml', + '-f', 'docker-compose.prod.yml', + 'build', '--no-cache', $service + ]) + ->withEnvironmentVars(['TAG' => $tag]) + ->withTimeout(1200) // 20mins + ->echoLineByLineOutput(true) + ->run(); + } catch (ProcessFailed $e) { $this->error("Unable to build all images, check the above output for reasons why."); return false; } diff --git a/app/Commands/DockerPushCommand.php b/app/Commands/DockerPushCommand.php index 2bfa65b..3f8733c 100644 --- a/app/Commands/DockerPushCommand.php +++ b/app/Commands/DockerPushCommand.php @@ -4,6 +4,7 @@ use App\Helpers\Helpers; use App\Helpers\NetsellsFile; +use App\Exceptions\ProcessFailed; use Symfony\Component\Process\Process; use LaravelZero\Framework\Commands\Command; use Symfony\Component\Console\Input\InputOption; @@ -93,24 +94,18 @@ public function handle() protected function callPush(string $tag, string $service = null): bool { - $process = new Process([ - 'docker-compose', - '-f', 'docker-compose.yml', - '-f', 'docker-compose.prod.yml', - 'push', $service - ], null, [ - 'TAG' => $tag, - ], null, 1200); // 20min timeout - - $process->start(); - - foreach ($process as $data) { - echo $data; - } - - $process->wait(); - - if ($process->getExitCode() !== 0) { + try { + $this->helpers->process()->withCommand([ + 'docker-compose', + '-f', 'docker-compose.yml', + '-f', 'docker-compose.prod.yml', + 'push', $service + ]) + ->withEnvironmentVars(['TAG' => $tag]) + ->withTimeout(1200) // 20mins + ->echoLineByLineOutput(true) + ->run(); + } catch (ProcessFailed $e) { $this->error("Unable to push all items to AWS, check the above output for reasons why."); return false; } diff --git a/app/Helpers/Aws/Ec2.php b/app/Helpers/Aws/Ec2.php index 70c96aa..0ed5bb5 100644 --- a/app/Helpers/Aws/Ec2.php +++ b/app/Helpers/Aws/Ec2.php @@ -32,7 +32,6 @@ public function listInstances(Command $command, $query): ?Collection try { $processOutput = $this->aws->newProcess($command, $commandOptions) - ->echoLineByLineOutput(false) ->run(); } catch (ProcessFailed $e) { $command->error("Unable to list ec2 instances"); diff --git a/app/Helpers/Aws/Ecs.php b/app/Helpers/Aws/Ecs.php index 069e9be..d0cbc29 100644 --- a/app/Helpers/Aws/Ecs.php +++ b/app/Helpers/Aws/Ecs.php @@ -27,7 +27,6 @@ public function authenticateDocker(Command $command): bool $processOutput = $this->aws->newProcess($command, [ 'ecr', 'get-login-password', ]) - ->echoLineByLineOutput(false) ->run(); } catch (ProcessFailed $e) { $command->error("Unable to get docker password from AWS."); @@ -43,7 +42,6 @@ public function authenticateDocker(Command $command): bool "--password={$password}", "{$awsAccountId}.dkr.ecr.{$awsRegion}.amazonaws.com" ]) - ->echoLineByLineOutput(false) ->run(); } catch (ProcessFailed $e) { $command->error("Unable to login to docker."); @@ -59,7 +57,6 @@ public function getTaskDefinition(Command $command, $name): ?array $processOutput = $this->aws->newProcess($command, [ 'ecs', 'describe-task-definition', "--task-definition={$name}", ]) - ->echoLineByLineOutput(false) ->run(); } catch (ProcessFailed $e) { $command->error("Unable to get task definition [{$name}] from AWS."); @@ -75,7 +72,6 @@ public function registerTaskDefinition(Command $command, string $taskDefinitionJ $processOutput = $this->aws->newProcess($command, [ 'ecs', 'register-task-definition', "--cli-input-json", $taskDefinitionJson, ]) - ->echoLineByLineOutput(false) ->run(); } catch (ProcessFailed $e) { $command->error("Unable to register task definition in AWS."); @@ -94,7 +90,6 @@ public function updateService(Command $command, string $clusterName, string $ser "--service={$serviceName}", "--task-definition={$taskDefinition}", ]) - ->echoLineByLineOutput(false) ->run(); } catch (ProcessFailed $e) { $command->error("Unable to update service in AWS."); @@ -123,7 +118,6 @@ public function runTaskWithCommand(Command $command, string $clusterName, string "--overrides={$overrides}", "--task-definition={$taskDefinition}", ]) - ->echoLineByLineOutput(false) ->run(); } catch (ProcessFailed $e) { $command->error("Unable to start migration task in AWS."); diff --git a/app/Helpers/Aws/Ssm.php b/app/Helpers/Aws/Ssm.php index 11a912b..4573481 100644 --- a/app/Helpers/Aws/Ssm.php +++ b/app/Helpers/Aws/Ssm.php @@ -27,7 +27,6 @@ public function sendRemoteCommand(Command $command, string $instanceId, string $ '--parameters', "commands=\"{$remoteCommand}\"", '--comment', '"Temporary SSM SSH Access via Netsells CLI"' ]) - ->echoLineByLineOutput(false) ->run(); return true; diff --git a/app/Helpers/Helpers.php b/app/Helpers/Helpers.php index 00ce23a..79621a0 100644 --- a/app/Helpers/Helpers.php +++ b/app/Helpers/Helpers.php @@ -26,7 +26,7 @@ public function aws(): Aws public function process(): Process { - return new Process($this); + return app(Process::class); } public function netsellsFile(): NetsellsFile diff --git a/app/Helpers/Process.php b/app/Helpers/Process.php index 5df1253..7b61bdb 100644 --- a/app/Helpers/Process.php +++ b/app/Helpers/Process.php @@ -8,7 +8,7 @@ class Process { protected $echoOnFailure = true; - protected $echoLineByLineOutput = true; + protected $echoLineByLineOutput = false; protected $timeout = 60; protected $environmentVars = []; @@ -19,12 +19,12 @@ public function withCommand(array $arguments) { $this->arguments = $arguments; - $this->process = new SymfonyProcess($this->arguments, null, $this->environmentVars, null, $this->timeout); return $this; } public function run() { + $this->process = new SymfonyProcess($this->arguments, null, $this->environmentVars, null, $this->timeout); $this->process->start(); if ($this->echoLineByLineOutput) { @@ -36,7 +36,8 @@ public function run() $this->process->wait(); if ($this->process->getExitCode() !== 0) { - if ($this->echoOnFailure) { + // If we're already echo'ing line by line, there's no point echoing it again + if ($this->echoOnFailure && !$this->echoLineByLineOutput) { foreach ($this->process as $data) { echo $data; } diff --git a/composer.json b/composer.json index 022a877..6a2ba89 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.3", "laminas/laminas-text": "^2.7", "laravel-zero/framework": "^7.0", "nunomaduro/laravel-console-menu": "^3.0", @@ -19,6 +19,7 @@ "symfony/yaml": "^5.0" }, "require-dev": { + "fzaninotto/faker": "^1.9", "mockery/mockery": "^1.3.1", "phpunit/phpunit": "^8.5" }, diff --git a/composer.lock b/composer.lock index 54ea1ca..5331a4c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e5c0846f5060e94dcf8b16de9c97163f", + "content-hash": "a5f748b793ea3465f9abb719777bdd46", "packages": [ { "name": "beberlei/assert", @@ -3426,6 +3426,56 @@ ], "time": "2019-10-21T16:45:58+00:00" }, + { + "name": "fzaninotto/faker", + "version": "v1.9.1", + "source": { + "type": "git", + "url": "https://github.com/fzaninotto/Faker.git", + "reference": "fc10d778e4b84d5bd315dad194661e091d307c6f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/fc10d778e4b84d5bd315dad194661e091d307c6f", + "reference": "fc10d778e4b84d5bd315dad194661e091d307c6f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "ext-intl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7", + "squizlabs/php_codesniffer": "^2.9.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "time": "2019-12-12T13:22:17+00:00" + }, { "name": "hamcrest/hamcrest-php", "version": "v2.0.0", diff --git a/tests/Feature/AwsEc2ListCommandTest.php b/tests/Feature/AwsEc2ListCommandTest.php new file mode 100755 index 0000000..80476a3 --- /dev/null +++ b/tests/Feature/AwsEc2ListCommandTest.php @@ -0,0 +1,64 @@ +mock(Process::class, function ($mock) { + $mock->shouldReceive('withCommand')->once()->andReturnSelf(); + $mock->shouldReceive('run')->once()->andThrow(ProcessFailed::class, "Something bad happened", 1); + }); + + $this->artisan('aws:ec2:list') + ->assertExitCode(1); + } + + public function testHandlesNoInstances() + { + $this->mock(Process::class, function ($mock) { + $mock->shouldReceive('withCommand')->once()->andReturnSelf(); + $mock->shouldReceive('run')->once()->andReturn(null); + }); + + $this->artisan('aws:ec2:list') + ->assertExitCode(0); + } + + public function testHandlesInstances() + { + $instance = [ + 'InstanceId' => $this->faker->iban, + 'Name' => $this->faker->company, + 'PrivateIpAddress' => $this->faker->localIpv4, + 'InstanceType' => $this->faker->creditCardType, + ]; + + $this->mock(Process::class, function ($mock) use ($instance) { + $mock->shouldReceive('withCommand')->once()->andReturnSelf(); + $mock->shouldReceive('run')->once()->andReturn(json_encode([ + [ + [ + 'InstanceId' => $instance['InstanceId'], + 'Name' => $instance['Name'], + 'PrivateIpAddress' => $instance['PrivateIpAddress'], + 'InstanceType' => $instance['InstanceType'], + ] + ] + ])); + }); + + // TODO: Learn how to read output and assert, laravel's implementation is crap + $this->artisan('aws:ec2:list') + ->assertExitCode(0); + } +} diff --git a/tests/Feature/InspiringCommandTest.php b/tests/Feature/InspiringCommandTest.php deleted file mode 100755 index 358e77b..0000000 --- a/tests/Feature/InspiringCommandTest.php +++ /dev/null @@ -1,20 +0,0 @@ -artisan('inspiring') - ->expectsOutput('Simplicity is the ultimate sophistication.') - ->assertExitCode(0); - } -} diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php deleted file mode 100644 index 06ece2c..0000000 --- a/tests/Unit/ExampleTest.php +++ /dev/null @@ -1,18 +0,0 @@ -assertTrue(true); - } -}