From 136546a37a4a4bf06dfd55da00c4c0315ba8dcb5 Mon Sep 17 00:00:00 2001 From: Rob Skilling Date: Wed, 17 Jul 2024 11:23:07 +0100 Subject: [PATCH] Add option to enforce refs on `whippet deps validate` This commit adds an `--enforce-refs` option that can be passed into `whippet deps validate` that will cause it to fail if a dependency in whippet.json is missing a "ref" property. This will facilitate us in enabling automatic plugin updates, we we'll be able to update our shared whippet validation workflow to fail if references are missing, thereby ensuring that all dependencies have a reference and therefore can't be accidentally updated beyond e.g. a major version tag, or a specific pinned version. Note that like the other checks in `whippet deps validate`, this does not check whether the reference provided exists and is valid, it just checks one is provided. An implicit check on the validity of the reference itself happens when `whippet deps update` or `whippet deps install` is run. --- spec/dependencies/validator.spec.php | 88 ++++++++++++++++++++++++++++ src/Dependencies/Validator.php | 7 ++- src/Modules/Dependencies.php | 7 ++- 3 files changed, 99 insertions(+), 3 deletions(-) diff --git a/spec/dependencies/validator.spec.php b/spec/dependencies/validator.spec.php index a4001b1..608c39b 100644 --- a/spec/dependencies/validator.spec.php +++ b/spec/dependencies/validator.spec.php @@ -181,6 +181,94 @@ expect($result->getErr())->toEqual('Missing revision property in whippet.lock for plugins: advanced-custom-fields-pro'); }); }); + context('but an entry in .json is missing a ref when --enforce-refs is set', function () { + it('returns an error', function () { + $whippetContents = '{ + "src": { + "plugins": "git@git.govpress.com:wordpress-plugins/" + }, + "plugins": [ + {"name": "akismet", "ref": "v1"}, + {"name": "advanced-custom-fields-pro"} + ] + }'; + allow('file_get_contents')->toBeCalled()->andReturn($whippetContents); + allow('sha1')->toBeCalled()->andReturn('a_matching_sha'); + expect('sha1')->toBeCalled()->once()->with($whippetContents); + allow($this->whippetLock)->toReceive('getHash')->andReturn('a_matching_sha'); + allow($this->whippetJson)->toReceive('getDependencies')->andReturn([], [ + [ + 'name' => 'akismet', + 'ref' => 'v1' + ], + [ + 'name' => 'advanced-custom-fields-pro' + ] + ]); + allow($this->whippetLock)->toReceive('getDependencies')->andReturn([], [ + [ + 'name' => 'akismet', + 'src' => 'a_src_1', + 'revision' => 'a_revision_1' + ], + [ + 'name' => 'advanced-custom-fields-pro', + 'src' => 'a_src_2', + 'revision' => 'a_revision_2' + ] + ]); + ob_start(); + $result = $this->validator->validate(true); + $output = ob_get_clean(); + expect($result->getErr())->toEqual("Missing reference in whippet.json for plugins: advanced-custom-fields-pro"); + expect($result)->toBeAnInstanceOf(\Result\Result::class); + expect($result->isErr())->toEqual(true); + }); + }); + context('but an entry in whippet.json is missing a ref when --enforce-refs is not set', function () { + it('returns an ok result', function () { + $whippetContents = '{ + "src": { + "plugins": "git@git.govpress.com:wordpress-plugins/" + }, + "plugins": [ + {"name": "akismet", "ref": "v1"}, + {"name": "advanced-custom-fields-pro"} + ] + }'; + allow('file_get_contents')->toBeCalled()->andReturn($whippetContents); + allow('sha1')->toBeCalled()->andReturn('a_matching_sha'); + expect('sha1')->toBeCalled()->once()->with($whippetContents); + allow($this->whippetLock)->toReceive('getHash')->andReturn('a_matching_sha'); + allow($this->whippetJson)->toReceive('getDependencies')->andReturn([], [ + [ + 'name' => 'akismet', + 'ref' => 'v1' + ], + [ + 'name' => 'advanced-custom-fields-pro' + ] + ]); + allow($this->whippetLock)->toReceive('getDependencies')->andReturn([], [ + [ + 'name' => 'akismet', + 'src' => 'a_src_1', + 'revision' => 'a_revision_1' + ], + [ + 'name' => 'advanced-custom-fields-pro', + 'src' => 'a_src_2', + 'revision' => 'a_revision_2' + ] + ]); + ob_start(); + $result = $this->validator->validate(); + $output = ob_get_clean(); + expect($output)->toEqual("Valid whippet.json and whippet.lock \n"); + expect($result)->toBeAnInstanceOf(\Result\Result::class); + expect($result->isErr())->toEqual(false); + }); + }); context('and everything is good', function () { it('returns an ok result', function () { $whippetContents = '{ diff --git a/src/Dependencies/Validator.php b/src/Dependencies/Validator.php index 5867ee4..17ab56d 100644 --- a/src/Dependencies/Validator.php +++ b/src/Dependencies/Validator.php @@ -15,7 +15,7 @@ public function __construct( $this->dir = $dir; } - public function validate() + public function validate(bool $enforceRefs = false) { $whippetLock = $this->loadWhippetLock(); if ($whippetLock->isErr()) { @@ -57,6 +57,11 @@ public function validate() if (!$this->lockMatchFoundForDependency($whippetJsonDependency, $whippetLockDependencies)) { return \Result\Result::err(sprintf('No entry found in whippet.lock for %s: %s', $type, $whippetJsonDependency["name"])); } + if ($enforceRefs) { + if (!array_key_exists('ref', $whippetJsonDependency)) { + return \Result\Result::err(sprintf("Missing reference in whippet.json for %s: %s", $type, $whippetJsonDependency["name"])); + } + } } foreach ($whippetLockDependencies as $whippetLockDependency) { diff --git a/src/Modules/Dependencies.php b/src/Modules/Dependencies.php index 02a3329..7a0f5d5 100644 --- a/src/Modules/Dependencies.php +++ b/src/Modules/Dependencies.php @@ -32,7 +32,9 @@ public function commands() }; $this->command('install', 'Installs dependencies', $inspections_host_option); $this->command('update', 'Updates dependencies to their latest versions. Use deps update [type]/[name] to update a specific dependency', $inspections_host_option); - $this->command('validate', 'Validate whippet.json and whippet.lock files'); + $this->command('validate', 'Validate whippet.json and whippet.lock files', function ($option_parser) { + $option_parser->addRule('r|enforce-refs', "Enforce refs for all whippet dependencies"); + }); $this->command('describe', 'List dependencies and their versions'); } @@ -78,7 +80,8 @@ public function validate() { $dir = $this->getDirectory(); $validator = new \Dxw\Whippet\Dependencies\Validator($this->factory, $dir); - $this->exitIfError(($validator->validate())); + $enforceRefs = isset($this->options->{'enforce-refs'}) ? true : false; + $this->exitIfError(($validator->validate($enforceRefs))); } public function describe()