From a9aeae6dba745baaa64e3a7bd34b14559205d71e 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. --- CHANGELOG.md | 5 ++ spec/dependencies/validator.spec.php | 88 ++++++++++++++++++++++++++++ src/Dependencies/Validator.php | 7 ++- src/Modules/Dependencies.php | 7 ++- 4 files changed, 104 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6d67ab..bf49cda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added +- Option to enforce refs when running `whippet deps validate` + ## [2.4.2] - 2024-05-28 ### Changed 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()