diff --git a/.gitattributes b/.gitattributes
index b844132..cae1733 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -9,6 +9,7 @@
/.gitignore export-ignore
/.gitattributes export-ignore
/.env.dist
+/deployer.phar export-ignore
/docker-compose.yml export-ignore
/infection.json.dist export-ignore
/phive.xml export-ignore
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 098df9a..1d1a937 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -19,4 +19,4 @@ jobs:
ci:
uses: codenamephp/workflows.php/.github/workflows/ci.yml@1
with:
- php-versions: '["8.1","8.2]'
+ php-versions: '["8.1","8.2"]'
diff --git a/.idea/copyright/Apache2.xml b/.idea/copyright/Apache2.xml
new file mode 100644
index 0000000..ddf769e
--- /dev/null
+++ b/.idea/copyright/Apache2.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644
index 0000000..53a32f1
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/.idea/deployer.secrets.iml b/.idea/deployer.secrets.iml
index 5ae24ee..befade1 100644
--- a/.idea/deployer.secrets.iml
+++ b/.idea/deployer.secrets.iml
@@ -5,6 +5,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..a161c77
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/php-test-framework.xml b/.idea/php-test-framework.xml
new file mode 100644
index 0000000..0df0049
--- /dev/null
+++ b/.idea/php-test-framework.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/php.xml b/.idea/php.xml
index e52129d..24f3fba 100644
--- a/.idea/php.xml
+++ b/.idea/php.xml
@@ -13,6 +13,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -29,6 +43,11 @@
+
+
+
+
+
diff --git a/.idea/runConfigurations/All.xml b/.idea/runConfigurations/All.xml
new file mode 100644
index 0000000..52dec9d
--- /dev/null
+++ b/.idea/runConfigurations/All.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/composer.json b/composer.json
index 878f761..5fd3aac 100644
--- a/composer.json
+++ b/composer.json
@@ -10,7 +10,10 @@
}
],
"require": {
- "php": "^8.1"
+ "php": "^8.1",
+ "codenamephp/deployer.base": "^3.0",
+ "codenamephp/platform.secretsmanager.base": "^1.0.1",
+ "deployer/deployer": "^7.2"
},
"autoload": {
"psr-4": {
@@ -34,12 +37,11 @@
"psalm": "tools/psalm --threads=10 --long-progress",
"composer-unused": "tools/composer-unused --no-progress --no-interaction",
"composer-require-checker": "tools/composer-require-checker --no-interaction",
- "infection": "XDEBUG_MODE=coverage tools/infection --min-msi=95 --min-covered-msi=95 --threads=4 --no-progress --show-mutations",
+ "infection": "XDEBUG_MODE=coverage tools/infection --min-msi=100 --min-covered-msi=100 --threads=4 --no-progress --show-mutations",
"ci-all": [
"@phpunit",
"@psalm",
"@composer-unused",
- "@composer-require-checker",
"@infection"
]
},
@@ -50,5 +52,8 @@
"composer-require-checker": "Checks for missing required composer packages",
"infection": "Creates mutation tests to discover missing test coverage",
"ci-all": "Runs all ci tools in sequence"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.5"
}
}
diff --git a/deployer.phar b/deployer.phar
new file mode 120000
index 0000000..20277de
--- /dev/null
+++ b/deployer.phar
@@ -0,0 +1 @@
+vendor/deployer/deployer/dep
\ No newline at end of file
diff --git a/infection.json5.dist b/infection.json5.dist
index c3bbefb..95c3626 100644
--- a/infection.json5.dist
+++ b/infection.json5.dist
@@ -14,5 +14,6 @@
},
"mutators": {
"@default": true
- }
+ },
+ "bootstrap":"./test/bootstrap.php"
}
\ No newline at end of file
diff --git a/psalm.xml b/psalm.xml
index 488dc80..c90ae21 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -24,6 +24,7 @@
cacheDirectory=".cache/psalm"
findUnusedBaselineEntry="true"
findUnusedCode="true"
+ autoloader="./test/bootstrap.php"
>
diff --git a/src/Settings/SettingsInterface.php b/src/Settings/SettingsInterface.php
new file mode 100644
index 0000000..6423f88
--- /dev/null
+++ b/src/Settings/SettingsInterface.php
@@ -0,0 +1,72 @@
+.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace de\codenamephp\deployer\secrets\Settings;
+
+use de\codenamephp\platform\secretsManager\base\Secret\SecretInterface;
+use Deployer\Host\Host;
+
+/**
+ * Interface to set settings either globally or on hosts
+ *
+ * @psalm-api
+ */
+interface SettingsInterface {
+
+ /**
+ * Resolves the given secret and sets it either on all hosts or if no hosts are given as a global setting
+ *
+ * Implementations should make use of the \de\codenamephp\platform\secretsManager\base\Client\ClientInterface to resolve the secret
+ *
+ * @param string $settingsKey The key to set the payload of the secret to
+ * @param SecretInterface $secret The secret to get the payload for
+ * @param Host ...$hosts Hosts to set the secret on. If no hosts are given the secret will be set globally
+ * @return void
+ */
+ public function set(string $settingsKey, SecretInterface $secret, Host ...$hosts) : void;
+
+ /**
+ * An array of settings keys and secrets to set. The array key is used as settings key.
+ *
+ * Resolves the given secrets and sets them either on all hosts or if no hosts are given as global settings
+ *
+ * Implementations should make use of the \de\codenamephp\platform\secretsManager\base\Client\ClientInterface to resolve the secrets
+ *
+ * @param array $secretsToSet The key/secrets mapping
+ * @param Host ...$hosts Hosts to set the secrets on. If no hosts are given the secrets will be set globally
+ * @return void
+ */
+ public function setMultiple(array $secretsToSet, Host ...$hosts) : void;
+
+ /**
+ * Fetches the payload content of the given secret. Implementations should use the \de\codenamephp\platform\secretsManager\base\Client\ClientInterface to
+ * resolve the secret
+ *
+ * @param SecretInterface $secret The secret to fetch the payload for
+ * @return string The payload content
+ */
+ public function fetch(SecretInterface $secret) : string;
+
+ /**
+ * Fetches the payload content of the given secrets. Implementations should use the \de\codenamephp\platform\secretsManager\base\Client\ClientInterface to
+ * resolve the secrets. Implementations MUST preserve the order and array keys of the given array as they may be used to identify the secrets
+ *
+ * @param array $secretsToResolve The secrets to fetch the payload for
+ * @return array The payload content of the secrets with the keys preserved
+ */
+ public function fetchMultiple(array $secretsToResolve) : array;
+}
\ No newline at end of file
diff --git a/src/Settings/WithClient.php b/src/Settings/WithClient.php
new file mode 100644
index 0000000..4176de3
--- /dev/null
+++ b/src/Settings/WithClient.php
@@ -0,0 +1,50 @@
+.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace de\codenamephp\deployer\secrets\Settings;
+
+use de\codenamephp\deployer\base\functions\All;
+use de\codenamephp\deployer\base\functions\iSet;
+use de\codenamephp\platform\secretsManager\base\Client\ClientInterface;
+use de\codenamephp\platform\secretsManager\base\Secret\SecretInterface;
+use Deployer\Host\Host;
+
+/**
+ * Settings implementation that uses a client to fetch the payload of a secret and then sets it on the hosts or as global settings
+ *
+ * @psalm-api
+ */
+final class WithClient implements SettingsInterface {
+
+ public function __construct(public readonly ClientInterface $client, public readonly iSet $deployerFunctions = new All()) {}
+
+ public function fetch(SecretInterface $secret) : string {
+ return $this->client->fetchPayload($secret)->getContent();
+ }
+ public function fetchMultiple(array $secretsToResolve) : array {
+ return array_map(fn(SecretInterface $secret) : string => $this->fetch($secret), $secretsToResolve);
+
+ }
+ public function set(string $settingsKey, SecretInterface $secret, Host ...$hosts) : void {
+ $payload = $this->fetch($secret);
+ $hosts ? array_map(static fn(Host $host) => $host->set($settingsKey, $payload), $hosts) : $this->deployerFunctions->set($settingsKey, $payload);
+ }
+
+ public function setMultiple(array $secretsToSet, Host ...$hosts) : void {
+ foreach($secretsToSet as $settingsKey => $secret) $this->set($settingsKey, $secret, ...$hosts);
+ }
+}
\ No newline at end of file
diff --git a/test/Settings/WithClientTest.php b/test/Settings/WithClientTest.php
new file mode 100644
index 0000000..73df326
--- /dev/null
+++ b/test/Settings/WithClientTest.php
@@ -0,0 +1,180 @@
+.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace de\codenamephp\deployer\secrets\test\Settings;
+
+use de\codenamephp\deployer\base\functions\iSet;
+use de\codenamephp\deployer\secrets\Settings\WithClient;
+use de\codenamephp\platform\secretsManager\base\Client\ClientInterface;
+use de\codenamephp\platform\secretsManager\base\Secret\Payload\PayloadInterface;
+use de\codenamephp\platform\secretsManager\base\Secret\SecretInterface;
+use Deployer\Host\Host;
+use Mockery;
+use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
+use PHPUnit\Framework\TestCase;
+
+final class WithClientTest extends TestCase {
+
+ use MockeryPHPUnitIntegration;
+
+ public function testSetMultiple() : void {
+ $payload1 = $this->createConfiguredMock(PayloadInterface::class, ['getContent' => 'secret1']);
+ $payload2 = $this->createConfiguredMock(PayloadInterface::class, ['getContent' => 'secret2']);
+ $payload3 = $this->createConfiguredMock(PayloadInterface::class, ['getContent' => 'secret3']);
+
+ $secret1 = $this->createMock(SecretInterface::class);
+ $secret2 = $this->createMock(SecretInterface::class);
+ $secret3 = $this->createMock(SecretInterface::class);
+
+ $client = Mockery::mock(ClientInterface::class);
+ $client->allows('fetchPayload')->once()->ordered()->with($secret1)->andReturn($payload1);
+ $client->allows('fetchPayload')->once()->ordered()->with($secret2)->andReturn($payload2);
+ $client->allows('fetchPayload')->once()->ordered()->with($secret3)->andReturn($payload3);
+
+ $deployerFunctions = Mockery::mock(iSet::class);
+ $deployerFunctions->allows('set')->once()->ordered()->with('key1', 'secret1');
+ $deployerFunctions->allows('set')->once()->ordered()->with('key2', 'secret2');
+ $deployerFunctions->allows('set')->once()->ordered()->with('key3', 'secret3');
+
+ $sut = new WithClient($client, $deployerFunctions);
+
+ $sut->setMultiple(['key1' => $secret1, 'key2' => $secret2, 'key3' => $secret3]);
+ }
+
+ public function testSetMultiple_withHosts() : void {
+ $host1 = Mockery::mock(Host::class);
+ $host1->allows('set')->once()->ordered()->with('key1', 'secret1');
+ $host1->allows('set')->once()->ordered()->with('key2', 'secret2');
+ $host1->allows('set')->once()->ordered()->with('key3', 'secret3');
+ $host2 = Mockery::mock(Host::class);
+ $host2->allows('set')->once()->ordered()->with('key1', 'secret1');
+ $host2->allows('set')->once()->ordered()->with('key2', 'secret2');
+ $host2->allows('set')->once()->ordered()->with('key3', 'secret3');
+ $host3 = Mockery::mock(Host::class);
+ $host3->allows('set')->once()->ordered()->with('key1', 'secret1');
+ $host3->allows('set')->once()->ordered()->with('key2', 'secret2');
+ $host3->allows('set')->once()->ordered()->with('key3', 'secret3');
+
+ $payload1 = $this->createConfiguredMock(PayloadInterface::class, ['getContent' => 'secret1']);
+ $payload2 = $this->createConfiguredMock(PayloadInterface::class, ['getContent' => 'secret2']);
+ $payload3 = $this->createConfiguredMock(PayloadInterface::class, ['getContent' => 'secret3']);
+
+ $secret1 = $this->createMock(SecretInterface::class);
+ $secret2 = $this->createMock(SecretInterface::class);
+ $secret3 = $this->createMock(SecretInterface::class);
+
+ $client = Mockery::mock(ClientInterface::class);
+ $client->allows('fetchPayload')->once()->ordered()->with($secret1)->andReturn($payload1);
+ $client->allows('fetchPayload')->once()->ordered()->with($secret2)->andReturn($payload2);
+ $client->allows('fetchPayload')->once()->ordered()->with($secret3)->andReturn($payload3);
+
+ $deployerFunctions = $this->createMock(iSet::class);
+ $deployerFunctions->expects(self::never())->method('set');
+
+ $sut = new WithClient($client, $deployerFunctions);
+
+ $sut->setMultiple(['key1' => $secret1, 'key2' => $secret2, 'key3' => $secret3], $host1, $host2, $host3);
+ }
+
+ public function testSet() : void {
+ $payload = $this->createConfiguredMock(PayloadInterface::class, ['getContent' => 'secret']);
+ $secret = $this->createMock(SecretInterface::class);
+
+ $client = $this->createMock(ClientInterface::class);
+ $client->expects(self::once())->method('fetchPayload')->with($secret)->willReturn($payload);
+
+ $deployerFunctions = $this->createMock(iSet::class);
+ $deployerFunctions->expects(self::once())->method('set')->with('some.key', 'secret');
+
+ $sut = new WithClient($client, $deployerFunctions);
+
+ $sut->set('some.key', $secret);
+ }
+
+ public function testSet_withHosts() : void {
+ $host1 = $this->createMock(Host::class);
+ $host1->expects(self::once())->method('set')->with('some.key', 'secret');
+ $host2 = $this->createMock(Host::class);
+ $host2->expects(self::once())->method('set')->with('some.key', 'secret');
+ $host3 = $this->createMock(Host::class);
+ $host3->expects(self::once())->method('set')->with('some.key', 'secret');
+
+ $payload = $this->createConfiguredMock(PayloadInterface::class, ['getContent' => 'secret']);
+ $secret = $this->createMock(SecretInterface::class);
+
+ $client = $this->createMock(ClientInterface::class);
+ $client->expects(self::once())->method('fetchPayload')->with($secret)->willReturn($payload);
+
+ $deployerFunctions = $this->createMock(iSet::class);
+ $deployerFunctions->expects(self::never())->method('set');
+
+ $sut = new WithClient($client, $deployerFunctions);
+
+ $sut->set('some.key', $secret, $host1, $host2, $host3);
+ }
+
+ public function testFetchMultiple() : void {
+ $payload1 = $this->createConfiguredMock(PayloadInterface::class, ['getContent' => 'secret1']);
+ $payload2 = $this->createConfiguredMock(PayloadInterface::class, ['getContent' => 'secret2']);
+ $payload3 = $this->createConfiguredMock(PayloadInterface::class, ['getContent' => 'secret3']);
+
+ $secret1 = $this->createMock(SecretInterface::class);
+ $secret2 = $this->createMock(SecretInterface::class);
+ $secret3 = $this->createMock(SecretInterface::class);
+
+ $client = Mockery::mock(ClientInterface::class);
+ $client->allows('fetchPayload')->once()->ordered()->with($secret1)->andReturn($payload1);
+ $client->allows('fetchPayload')->once()->ordered()->with($secret2)->andReturn($payload2);
+ $client->allows('fetchPayload')->once()->ordered()->with($secret3)->andReturn($payload3);
+
+ $sut = new WithClient($client);
+
+ self::assertSame(['key1' => 'secret1', 'key2' => 'secret2', 'key3' => 'secret3'], $sut->fetchMultiple(['key1' => $secret1, 'key2' => $secret2, 'key3' => $secret3]));
+ }
+
+ public function test__construct() : void {
+ $client = $this->createMock(ClientInterface::class);
+ $deployerFunctions = $this->createMock(iSet::class);
+
+ $sut = new WithClient($client, $deployerFunctions);
+
+ self::assertSame($client, $sut->client);
+ self::assertSame($deployerFunctions, $sut->deployerFunctions);
+ }
+
+ public function test__construct_withMinimalParameters() : void {
+ $client = $this->createMock(ClientInterface::class);
+
+ $sut = new WithClient($client);
+
+ self::assertSame($client, $sut->client);
+ self::assertInstanceOf(iSet::class, $sut->deployerFunctions);
+ }
+
+ public function testFetch() : void {
+ $secret = $this->createMock(SecretInterface::class);
+ $payload = $this->createMock(PayloadInterface::class);
+ $payload->expects(self::once())->method('getContent')->willReturn('some secret');
+
+ $client = $this->createMock(ClientInterface::class);
+ $client->expects(self::once())->method('fetchPayload')->with($secret)->willReturn($payload);
+
+ $sut = new WithClient($client);
+
+ self::assertSame('some secret', $sut->fetch($secret));
+ }
+}
diff --git a/test/bootstrap.php b/test/bootstrap.php
new file mode 100644
index 0000000..dab1602
--- /dev/null
+++ b/test/bootstrap.php
@@ -0,0 +1,21 @@
+.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+require_once __DIR__ . '/../vendor/autoload.php';
+
+Phar::loadPhar(__DIR__ . '/../deployer.phar');
+require_once 'phar://deployer.phar/vendor/autoload.php';
\ No newline at end of file
diff --git a/test/phpunit.dist.xml b/test/phpunit.dist.xml
index cd63c0a..c0dfd46 100644
--- a/test/phpunit.dist.xml
+++ b/test/phpunit.dist.xml
@@ -1,24 +1,23 @@
-