diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 4ae65c4..5f2506a 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -12,13 +12,15 @@ jobs: strategy: matrix: - php: - - "8.0" - - "8.1" - - "8.2" include: - - php-version: "8.0" - phpunit: "9.5" + - php: "8.1" + phpunit: "10" + phpunit-config: "phpunit-10.xml.dist" + - php: "8.2" + phpunit: "11" + phpunit-config: "phpunit.xml.dist" + - php: "8.3" + phpunit: "11" phpunit-config: "phpunit.xml.dist" steps: @@ -28,12 +30,12 @@ jobs: php-version: "${{ matrix.php }}" extensions: redis, apcu, ctype, dom, iconv, gd, mbstring, fileinfo, intl, json, mysql, bcmath, zip coverage: none # disable xdebug, pcov - ini-values: post_max_size=256M memory + #ini-values: post_max_size=256M memory tools: cs2pr, pecl, php-cs-fixer, vimeo/psalm, phpstan, phpcs - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - uses: "ramsey/composer-install@v2" + - uses: "ramsey/composer-install@v3" with: composer-options: "--no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist -q" @@ -41,8 +43,10 @@ jobs: uses: php-actions/phpunit@v3 with: version: "${{ matrix.phpunit }}" + php_version: "${{matrix.php}}" configuration: "${{ matrix.phpunit-config }}" memory_limit: "256M" + test_suffix: .php - name: Run phpstan run: phpstan analyse --error-format=checkstyle -c "phpstan.neon" | cs2pr diff --git a/Asm/Ansible/Command/AbstractAnsibleCommand.php b/Asm/Ansible/Command/AbstractAnsibleCommand.php index d3e6829..41cbefa 100644 --- a/Asm/Ansible/Command/AbstractAnsibleCommand.php +++ b/Asm/Ansible/Command/AbstractAnsibleCommand.php @@ -34,6 +34,8 @@ abstract class AbstractAnsibleCommand private array $baseOptions; + private bool $useStdoutForError; + /** * @param ProcessBuilderInterface $processBuilder * @param LoggerInterface|null $logger @@ -44,6 +46,7 @@ public function __construct(ProcessBuilderInterface $processBuilder, LoggerInter $this->options = []; $this->parameters = []; $this->baseOptions = []; + $this->useStdoutForError = false; $this->setLogger($logger ?? new NullLogger()); } @@ -183,7 +186,7 @@ protected function runProcess(?callable $callback): int|string // if no callback is set, and the process is not successful, we return the output if (false === $process->isSuccessful()) { - return $process->getErrorOutput(); + return $this->useStdoutForError ? $process->getOutput() : $process->getErrorOutput(); } // if no callback is set, and the process is successful, we return the output @@ -210,4 +213,14 @@ protected function getProcessCommandline(Process $process): string return sprintf('%s %s', implode(' ', $vars), $commandline); } + + /** + * in case ansible explicitly is in json mode, this will be set to be able to get error outputs + * + * @return void + */ + protected function useStdoutForError(): void + { + $this->useStdoutForError = true; + } } diff --git a/Asm/Ansible/Command/AnsiblePlaybook.php b/Asm/Ansible/Command/AnsiblePlaybook.php index f68535d..a1c8c92 100644 --- a/Asm/Ansible/Command/AnsiblePlaybook.php +++ b/Asm/Ansible/Command/AnsiblePlaybook.php @@ -408,6 +408,7 @@ public function colors(bool $colors = true): AnsiblePlaybookInterface public function json(): AnsiblePlaybookInterface { $this->processBuilder->setEnv('ANSIBLE_STDOUT_CALLBACK', 'json'); + $this->useStdoutForError(); return $this; } @@ -728,12 +729,12 @@ public function hostKeyChecking(bool $enable = true): AnsiblePlaybookInterface } /** - * Ansible SSH pipelining option - * https://docs.ansible.com/ansible/latest/reference_appendices/config.html#ansible-pipelining - * - * @param bool $enable - * @return AnsiblePlaybookInterface - **/ + * Ansible SSH pipelining option + * https://docs.ansible.com/ansible/latest/reference_appendices/config.html#ansible-pipelining + * + * @param bool $enable + * @return AnsiblePlaybookInterface + **/ public function sshPipelining(bool $enable = false): AnsiblePlaybookInterface { $enable ? diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b6aff1b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +FROM php:8.3-cli + +WORKDIR /app + +ENV ANSIBLE_VERSION 2.9.17 + +# composer +RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \ + && php -r "if (hash_file('sha384', 'composer-setup.php') === 'dac665fdc30fdd8ec78b38b9800061b4150413ff2e3b6f88543c636f7cd84f6db9189d43a81e5503cda447da73c7e5b6') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \ + && php composer-setup.php \ + && php -r "unlink('composer-setup.php');" \ + && mv composer.phar /usr/bin/composer \ + && chmod +x /usr/bin/composer + +# python, pipx & ansible +RUN apt-get update \ + && apt-get install -y gcc python3 git zip 7zip unzip pipx \ + && apt-get clean all; \ + pipx install --upgrade pip; \ + pipx install "ansible==${ANSIBLE_VERSION}"; \ + pipx install ansible; + +# keep container running +CMD [ "bash", "-c", "echo 'running'; tail -f /dev/null" ] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ad88589 --- /dev/null +++ b/Makefile @@ -0,0 +1,66 @@ +# Executables (local) +DOCKER_COMP = docker compose + +# Docker containers +PHP_CONT = $(DOCKER_COMP) exec php-ansible + +# Executables +PHP = $(PHP_CONT) php +COMPOSER = $(PHP_CONT) composer + +# Misc +.DEFAULT_GOAL = help + +## —— php-ansible Makefile 🎵 —————————————————————————————————— +help: ## Outputs this help screen + @grep -E '(^[a-zA-Z0-9\./_-]+:.*?##.*$$)|(^##)' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}{printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}' | sed -e 's/\[32m##/[33m/' + +## —— Docker 🐳 ———————————————————————————————————————————————————————————————— +build: ## Builds the Docker images + @$(DOCKER_COMP) build --pull --no-cache + +up: ## Start the docker hub in detached mode (no logs) + @$(DOCKER_COMP) up --detach + +start: build up ## Build and start the containers + +down: ## Stop the docker hub + @$(DOCKER_COMP) down --remove-orphans + +logs: ## Show live logs + @$(DOCKER_COMP) logs --tail=0 --follow + +sh: ## Connect to the php-fpm container + @$(PHP_CONT) sh + +bash: ## Connect to the php-fpm container via bash so up and down arrows go to previous commands + @$(PHP_CONT) bash + +test: ## Start tests with phpunit, pass the parameter "c=" to add options to phpunit, example: make test c="--group e2e --stop-on-failure" + @$(eval c ?=) + @$(PHP_CONT) vendor/bin/phpunit -c phpunit.xml.dist $(c) + +analyze: ## Start analysis with phpstan, pass the parameter "c=" to add options to phpstan. Default config ist always used, example: make analyze c="--group e2e" + @$(eval c ?=) + @$(PHP_CONT) vendor/bin/phpstan --configuration=phpstan.neon $(c) + +codestyle: ## Start codestyle analysis with phpcs, pass the parameter "c=" to add options to phpcs. Default config ist always used. Example: make codestyle c="--parallel=2" + @$(eval c ?=) + @$(PHP_CONT) vendor/bin/phpcs --standard=phpcs.xml.dist $(c) + +codestyle-fix: ## Start codestyle analysis with phpcbf, pass the parameter "c=" to add options to phpcbf. Default config ist always used. Example: make codestyle c="--parallel=2" + @$(eval c ?=) + @$(PHP_CONT) vendor/bin/phpcbf --standard=phpcs.xml.dist $(c) + +psalm: ## Start code analysis with psalm, pass the parameter "c=" to add options to psalm. Default config ist always used. Example: make codestyle c="--level=2" + @$(eval c ?=) + @$(DOCKER_COMP) exec -e APP_ENV=dev php vendor/bin/psalm --config=psalm.xml $(c) + +## —— Composer 🧙 —————————————————————————————————————————————————————————————— +composer: ## Run composer, pass the parameter "c=" to run a given command, example: make composer c='req phpstan/phpstan' + @$(eval c ?=) + @$(COMPOSER) $(c) + +vendor: ## Install vendors according to the current composer.lock file +vendor: c=install --prefer-dist --no-dev --no-progress --no-scripts --no-interaction +vendor: composer \ No newline at end of file diff --git a/README.md b/README.md index 98db526..44d619d 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,12 @@ $ansible … ``` +## Development +You can use the provided docker image with ```make build``` which uses a default php-cli docker image and ansible 2.x. See the ```Dockerfile``` for more info. +Start the container with ```make up```. +Composer install: ```make vendor``` +You can run code or the tests within the container: ```make test c="--testdox"``` ## Thank you for your contributions! @@ -178,7 +183,6 @@ The Next steps for implementation are: - improve type handling and structure, due to overall complexity of the playbook at the moment - scalar typehints all over the place - provide docker support for development -- move to php8.0 exclusively for the next major release - wrapping the library into a bundle -> maybe - provide commandline-capabilities -> maybe diff --git a/Tests/Asm/Ansible/AnsibleTest.php b/Tests/Asm/Ansible/AnsibleTest.php index 0ae6627..9d29772 100644 --- a/Tests/Asm/Ansible/AnsibleTest.php +++ b/Tests/Asm/Ansible/AnsibleTest.php @@ -1,26 +1,24 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ + +declare(strict_types=1); namespace Asm\Ansible; use Asm\Ansible\Exception\CommandException; use Asm\Ansible\Testing\AnsibleTestCase; use org\bovigo\vfs\vfsStream; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversFunction; +#[CoversClass(\Asm\Ansible\Ansible::class)] +#[CoversFunction('playbook')] +#[CoversFunction('createProcess')] +#[CoversFunction('checkCommand')] +#[CoversFunction('checkDir')] +#[CoversFunction('__construct')] class AnsibleTest extends AnsibleTestCase { - /** - * @covers \Asm\Ansible\Ansible::checkCommand - * @covers \Asm\Ansible\Ansible::checkDir - * @covers \Asm\Ansible\Ansible::__construct - */ + public function testInstance() { $ansible = new Ansible( @@ -31,11 +29,6 @@ public function testInstance() $this->assertInstanceOf('\Asm\Ansible\Ansible', $ansible, 'Instantiation with given paths'); } - /** - * @covers \Asm\Ansible\Ansible::checkCommand - * @covers \Asm\Ansible\Ansible::checkDir - * @covers \Asm\Ansible\Ansible::__construct - */ public function testAnsibleProjectPathNotFoundException() { $this->expectException(CommandException::class); @@ -46,11 +39,6 @@ public function testAnsibleProjectPathNotFoundException() ); } - /** - * @covers \Asm\Ansible\Ansible::checkCommand - * @covers \Asm\Ansible\Ansible::checkDir - * @covers \Asm\Ansible\Ansible::__construct - */ public function testAnsibleCommandNotFoundException() { $this->expectException(CommandException::class); @@ -61,11 +49,6 @@ public function testAnsibleCommandNotFoundException() ); } - /** - * @covers \Asm\Ansible\Ansible::checkCommand - * @covers \Asm\Ansible\Ansible::checkDir - * @covers \Asm\Ansible\Ansible::__construct - */ public function testAnsibleNoCommandGivenException() { // TODO: Not sure why the following command should give an error. @@ -75,11 +58,6 @@ public function testAnsibleNoCommandGivenException() // ); } - /** - * @covers \Asm\Ansible\Ansible::checkCommand - * @covers \Asm\Ansible\Ansible::checkDir - * @covers \Asm\Ansible\Ansible::__construct - */ public function testAnsibleCommandNotExecutableException() { $this->expectException(CommandException::class); @@ -94,13 +72,6 @@ public function testAnsibleCommandNotExecutableException() ); } - /** - * @covers \Asm\Ansible\Ansible::playbook - * @covers \Asm\Ansible\Ansible::createProcess - * @covers \Asm\Ansible\Ansible::checkCommand - * @covers \Asm\Ansible\Ansible::checkDir - * @covers \Asm\Ansible\Ansible::__construct - */ public function testPlaybookCommandInstance() { $ansible = new Ansible( @@ -114,13 +85,6 @@ public function testPlaybookCommandInstance() $this->assertInstanceOf('\Asm\Ansible\Command\AnsiblePlaybook', $playbook); } - /** - * @covers \Asm\Ansible\Ansible::galaxy - * @covers \Asm\Ansible\Ansible::createProcess - * @covers \Asm\Ansible\Ansible::checkCommand - * @covers \Asm\Ansible\Ansible::checkDir - * @covers \Asm\Ansible\Ansible::__construct - */ public function testGalaxyCommandInstance() { $ansible = new Ansible( diff --git a/Tests/Asm/Ansible/Command/AnsibleGalaxyTest.php b/Tests/Asm/Ansible/Command/AnsibleGalaxyTest.php index d8d1308..98d1821 100644 --- a/Tests/Asm/Ansible/Command/AnsibleGalaxyTest.php +++ b/Tests/Asm/Ansible/Command/AnsibleGalaxyTest.php @@ -23,11 +23,7 @@ public function testCreateInstance(): AnsibleGalaxyInterface return $ansibleGalaxy; } - /** - * @depends testCreateInstance - * @param AnsibleGalaxyInterface $command - */ - public function testExecute(AnsibleGalaxyInterface $command): void + public function testExecute(): void { // Skipped on Windows if (Env::isWindows()) { @@ -35,6 +31,7 @@ public function testExecute(AnsibleGalaxyInterface $command): void return; } + $command = $this->testCreateInstance(); $command->execute(); // if command executes without exception diff --git a/Tests/Asm/Ansible/Command/AnsiblePlaybookTest.php b/Tests/Asm/Ansible/Command/AnsiblePlaybookTest.php index 3c50ada..32cc499 100644 --- a/Tests/Asm/Ansible/Command/AnsiblePlaybookTest.php +++ b/Tests/Asm/Ansible/Command/AnsiblePlaybookTest.php @@ -30,13 +30,11 @@ public function testCreateInstance(): AnsiblePlaybookInterface } /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface * @throws Exception */ - public function testDefaultDeployment(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + public function testDefaultDeployment(): AnsiblePlaybookInterface { + $command = $this->testCreateInstance(); $today = new DateTime(); $command @@ -52,81 +50,56 @@ public function testDefaultDeployment(AnsiblePlaybookInterface $command): Ansibl return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testAskPassArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + public function testAskPassArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->askPass(); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--ask-pass', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testAskSuPassArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + public function testAskSuPassArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->askSuPass(); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--ask-su-pass', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testAskBecomePassArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testAskBecomePassArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->askBecomePass(); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--ask-become-pass', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testAskVaultPassArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testAskVaultPassArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->askVaultPass(); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--ask-vault-pass', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testConnectionArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testConnectionArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->connection(); @@ -139,51 +112,36 @@ public function testConnectionArgumentPresent(AnsiblePlaybookInterface $command) $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--connection=test', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testDiffArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testDiffArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->diff(); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--diff', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testForceHandlersArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testForceHandlersArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->forceHandlers(); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--force-handlers', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testForksArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testForksArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->forks(); @@ -196,34 +154,24 @@ public function testForksArgumentPresent(AnsiblePlaybookInterface $command): Ans $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--forks=10', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testHelpArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testHelpArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->help(); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--help', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testLimitArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testLimitArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->limit('test'); @@ -242,51 +190,36 @@ public function testLimitArgumentPresent(AnsiblePlaybookInterface $command): Ans $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--limit=test,more,some', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testlistHostsArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testlistHostsArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->listHosts(); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--list-hosts', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testListTasksArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testListTasksArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->listTasks(); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--list-tasks', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testModulePathArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testModulePathArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->modulePath(); @@ -299,34 +232,24 @@ public function testModulePathArgumentPresent(AnsiblePlaybookInterface $command) $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--module-path=/test,/narf', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testPrivateKeyArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testPrivateKeyArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->privateKey('/path/to/private/key'); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--private-key=/path/to/private/key', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testSkipTagsArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testSkipTagsArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->skipTags('test'); @@ -345,68 +268,48 @@ public function testSkipTagsArgumentPresent(AnsiblePlaybookInterface $command): $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--skip-tags=test,another', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testStartAtTaskArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testStartAtTaskArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->startAtTask('test'); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--start-at-task=test', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testStepArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testStepArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->step(); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--step', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testSuArgumentPresent(AnsiblePlaybookInterface $command) + + public function testSuArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->su(); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--su', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testSuUserArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testSuUserArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->suUser(); @@ -419,34 +322,24 @@ public function testSuUserArgumentPresent(AnsiblePlaybookInterface $command): An $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--su-user=maschmann', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testBecomeArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testBecomeArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->become(); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--become', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testBecomeUserArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testBecomeUserArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->becomeUser(); @@ -459,34 +352,24 @@ public function testBecomeUserArgumentPresent(AnsiblePlaybookInterface $command) $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--become-user=maschmann', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testSyntaxCheckArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testSyntaxCheckArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->syntaxCheck(); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--syntax-check', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testTagsArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testTagsArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->tags('oneTag'); @@ -505,17 +388,12 @@ public function testTagsArgumentPresent(AnsiblePlaybookInterface $command): Ansi $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--tags=oneTag,anotherTag', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testTimeoutArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testTimeoutArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->timeout(); @@ -528,51 +406,36 @@ public function testTimeoutArgumentPresent(AnsiblePlaybookInterface $command): A $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--timeout=115', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testUserArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testUserArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->user('maschmann'); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--user=maschmann', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testVaultPasswordFileArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testVaultPasswordFileArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->vaultPasswordFile('/path/to/vault'); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--vault-password-file=/path/to/vault', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testVerboseArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testVerboseArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->verbose(); @@ -585,185 +448,129 @@ public function testVerboseArgumentPresent(AnsiblePlaybookInterface $command): A $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('-vvv', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testVersionArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testVersionArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->version(); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--version', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testFlushCacheParameterPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testFlushCacheParameterPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->flushCache(); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--flush-cache', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testNewVaultIdArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testNewVaultIdArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->newVaultId('someId'); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--new-vault-id=someId', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testNewVaultPasswordFileArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testNewVaultPasswordFileArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->newVaultPasswordFile('/path/to/vault'); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--new-vault-password-file=/path/to/vault', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testScpExtraArgsArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testScpExtraArgsArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->scpExtraArgs('SomeExtraArgs'); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--scp-extra-args=SomeExtraArgs', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testSftpExtraArgsArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testSftpExtraArgsArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->sftpExtraArgs('SftExtraArgs'); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--sftp-extra-args=SftExtraArgs', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testSshCommonArgsArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testSshCommonArgsArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->sshCommonArgs('SshCommonArgs'); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--ssh-common-args=SshCommonArgs', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testSshExtraArgsArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testSshExtraArgsArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->sshExtraArgs('SshExtraArgs'); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--ssh-extra-args=SshExtraArgs', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testVaultIdArgumentPresent(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testVaultIdArgumentPresent() { + $command = $this->testCreateInstance(); $command ->play($this->getPlayUri()) ->vaultId('VaultId'); $arguments = array_flip($command->getCommandlineArguments()); $this->assertArrayHasKey('--vault-id=VaultId', $arguments); - - return $command; } - /** - * @depends testCreateInstance - * @param AnsiblePlaybookInterface $command - * @return AnsiblePlaybookInterface - */ - public function testGetCommandlineArguments(AnsiblePlaybookInterface $command): AnsiblePlaybookInterface + + public function testGetCommandlineArguments() { + $command = $this->testCreateInstance(); $arguments = $command ->play($this->getPlayUri()) ->getCommandlineArguments(); $this->assertTrue(is_array($arguments)); $this->assertTrue(is_string($command->getCommandlineArguments(false))); - - return $command; } - /** - * @depends testDefaultDeployment - * @param AnsiblePlaybookInterface $command - */ - public function testExecuteWithCallback(AnsiblePlaybookInterface $command): void + public function testExecuteWithCallback(): void { // Skipped on Windows if (Env::isWindows()) { @@ -771,6 +578,15 @@ public function testExecuteWithCallback(AnsiblePlaybookInterface $command): void return; } + $command = $this->testCreateInstance(); + + $command + ->play($this->getPlayUri()) + ->user('maschmann') + ->extraVars(['project_release=' . (new DateTime())->getTimestamp()]) + ->limit('test') + ->check(); + $exitCode = $command ->execute(function (string $type, string $buffer) { if (Process::ERR === $type) { @@ -785,13 +601,15 @@ public function testExecuteWithCallback(AnsiblePlaybookInterface $command): void $this->assertTrue(is_integer($exitCode)); } - /** - * @depends testDefaultDeployment - * @param AnsiblePlaybookInterface $command - */ - public function textExecuteWithTextOutput(AnsiblePlaybookInterface $command): void + public function textExecuteWithTextOutput(): void { + $command = $this->testCreateInstance(); $result = $command + ->play($this->getPlayUri()) + ->user('maschmann') + ->extraVars(['project_release=' . (new DateTime())->getTimestamp()]) + ->limit('test') + ->check() ->execute(null); $this->assertTrue(is_string($result)); @@ -1070,6 +888,38 @@ public function testReturnsErrorOutputIfProcessWasNotSuccessful(): void self::assertEquals('error output', $playbook->execute()); } + public function testReturnsErrorOutputIfProcessWasNotSuccessfulInJsonMode(): void + { + $builder = $this->createMock(ProcessBuilderInterface::class); + $builder + ->expects(self::once()) + ->method('setArguments') + ->willReturnSelf(); + $builder + ->expects(self::once()) + ->method('getProcess') + ->willReturn($process = $this->createMock(Process::class)); + $process + ->expects(self::once()) + ->method('run'); + $process + ->expects(self::once()) + ->method('isSuccessful') + ->willReturn(false); + $process + ->expects(self::once()) + ->method('getOutput') + ->willReturn('{ "error": "error output" }'); + $process + ->expects(self::never()) + ->method('getErrorOutput'); + + $playbook = new AnsiblePlaybook($builder); + $playbook->json(); + + self::assertEquals('{ "error": "error output" }', $playbook->execute()); + } + public function testReturnsNormalOutputIfProcessWasSuccessful(): void { $builder = $this->createMock(ProcessBuilderInterface::class); @@ -1101,7 +951,7 @@ public function testReturnsNormalOutputIfProcessWasSuccessful(): void self::assertEquals('success', $playbook->execute()); } - public function testReturnsExitCodeIfCallbackwasPassed(): void + public function testReturnsExitCodeIfCallbackWasPassed(): void { $builder = $this->createMock(ProcessBuilderInterface::class); $builder diff --git a/Tests/Asm/Ansible/Process/ProcessBuilderTest.php b/Tests/Asm/Ansible/Process/ProcessBuilderTest.php index ceb9388..b89de15 100644 --- a/Tests/Asm/Ansible/Process/ProcessBuilderTest.php +++ b/Tests/Asm/Ansible/Process/ProcessBuilderTest.php @@ -21,12 +21,10 @@ public function testCreateInstance(): ProcessBuilderInterface return $processBuilder; } - /** - * @param ProcessBuilderInterface $processBuilder - * @depends testCreateInstance - */ - public function testGetProcess(ProcessBuilderInterface $processBuilder): void + public function testGetProcess(): void { + $processBuilder = $this->testCreateInstance(); + $process = $processBuilder ->setArguments(['more_args']) ->setEnv('SOME', 'value') diff --git a/Tests/Asm/Ansible/Testing/AnsibleTestCase.php b/Tests/Asm/Ansible/Testing/AnsibleTestCase.php index 52c92a0..1e3e694 100644 --- a/Tests/Asm/Ansible/Testing/AnsibleTestCase.php +++ b/Tests/Asm/Ansible/Testing/AnsibleTestCase.php @@ -1,12 +1,6 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ + +declare(strict_types=1); namespace Asm\Ansible\Testing; @@ -57,7 +51,14 @@ protected function createProjectStructure(): vfsStreamDirectory protected function getProjectUri(): string { - return $this->project->url() . '/ansible-project'; + /** + * @todo using a virtual file system with an URI here, breaks proc_open for PHP 8.3.x due to change in + * the way it works underneath when not finding an executable. Providing an existing path here, does not + * break behavior of tests and has no real impact. + * @see https://github.com/php/php-src/issues/13743 + */ + //return $this->project->url() . '/ansible-project'; + return $this->getAssetsBinPath(); } protected function getPlayUri(): string diff --git a/Tests/Asm/Ansible/Utils/StrTest.php b/Tests/Asm/Ansible/Utils/StrTest.php index 2f311e0..a5bdf8e 100644 --- a/Tests/Asm/Ansible/Utils/StrTest.php +++ b/Tests/Asm/Ansible/Utils/StrTest.php @@ -5,10 +5,9 @@ namespace Asm\Ansible\Utils; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\Group; -/** - * @group utils - */ +#[Group('utils')] class StrTest extends TestCase { /** diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..2f0ea52 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,6 @@ +services: + php-ansible: + build: . + container_name: php-ansible + volumes: + - ./:/app \ No newline at end of file diff --git a/composer.json b/composer.json index 2f6a2dd..af82267 100644 --- a/composer.json +++ b/composer.json @@ -15,13 +15,14 @@ } ], "require": { - "php": "^8.0.0|^8.1.0|^8.2.0", - "psr/log": "^1.1|^2.0|^3.0", - "symfony/process": "^5.3|^6.0" + "php": "^8.1.0|^8.2.0|^8.3.0", + "psr/log": "^2.0|^3.0", + "symfony/process": "^5.3|^6.0|^7.0" }, "require-dev": { - "phpunit/phpunit": "^9.5|^10.0 ", - "mikey179/vfsstream": "^1.6" + "phpunit/phpunit": "^10.0|^11.0", + "mikey179/vfsstream": "^1.6", + "phpstan/phpstan": "^1.12" }, "autoload": { "psr-4": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d25af5c..bc9cb59 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,6 +1,11 @@ - - + + + + ./Tests + + + . @@ -8,10 +13,5 @@ ./Tests ./vendor - - - - ./Tests - - + diff --git a/psalm.xml b/psalm.xml index 8b0757c..b220368 100644 --- a/psalm.xml +++ b/psalm.xml @@ -2,7 +2,7 @@