Skip to content

Commit bd6eac4

Browse files
author
Bastian Schwarz
committed
Initial implementation
1 parent 83fcac0 commit bd6eac4

3 files changed

Lines changed: 302 additions & 0 deletions

File tree

src/Settings/SettingsInterface.php

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php declare(strict_types=1);
2+
/*
3+
* Copyright 2023 Bastian Schwarz <bastian@codename-php.de>.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
namespace de\codenamephp\deployer\secrets\Settings;
19+
20+
use de\codenamephp\platform\secretsManager\base\Secret\SecretInterface;
21+
use Deployer\Host\Host;
22+
23+
/**
24+
* Interface to set settings either globally or on hosts
25+
*
26+
* @psalm-api
27+
*/
28+
interface SettingsInterface {
29+
30+
/**
31+
* Resolves the given secret and sets it either on all hosts or if no hosts are given as a global setting
32+
*
33+
* Implementations should make use of the \de\codenamephp\platform\secretsManager\base\Client\ClientInterface to resolve the secret
34+
*
35+
* @param string $settingsKey The key to set the payload of the secret to
36+
* @param SecretInterface $secret The secret to get the payload for
37+
* @param Host ...$hosts Hosts to set the secret on. If no hosts are given the secret will be set globally
38+
* @return void
39+
*/
40+
public function set(string $settingsKey, SecretInterface $secret, Host ...$hosts) : void;
41+
42+
/**
43+
* An array of settings keys and secrets to set. The array key is used as settings key.
44+
*
45+
* Resolves the given secrets and sets them either on all hosts or if no hosts are given as global settings
46+
*
47+
* Implementations should make use of the \de\codenamephp\platform\secretsManager\base\Client\ClientInterface to resolve the secrets
48+
*
49+
* @param array<string, SecretInterface> $secretsToSet The key/secrets mapping
50+
* @param Host ...$hosts Hosts to set the secrets on. If no hosts are given the secrets will be set globally
51+
* @return void
52+
*/
53+
public function setMultiple(array $secretsToSet, Host ...$hosts) : void;
54+
55+
/**
56+
* Fetches the payload content of the given secret. Implementations should use the \de\codenamephp\platform\secretsManager\base\Client\ClientInterface to
57+
* resolve the secret
58+
*
59+
* @param SecretInterface $secret The secret to fetch the payload for
60+
* @return string The payload content
61+
*/
62+
public function fetch(SecretInterface $secret) : string;
63+
64+
/**
65+
* Fetches the payload content of the given secrets. Implementations should use the \de\codenamephp\platform\secretsManager\base\Client\ClientInterface to
66+
* resolve the secrets. Implementations MUST preserve the order and array keys of the given array as they may be used to identify the secrets
67+
*
68+
* @param array<SecretInterface> $secretsToResolve The secrets to fetch the payload for
69+
* @return array<string> The payload content of the secrets with the keys preserved
70+
*/
71+
public function fetchMultiple(array $secretsToResolve) : array;
72+
}

src/Settings/WithClient.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php declare(strict_types=1);
2+
/*
3+
* Copyright 2023 Bastian Schwarz <bastian@codename-php.de>.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
namespace de\codenamephp\deployer\secrets\Settings;
19+
20+
use de\codenamephp\deployer\base\functions\All;
21+
use de\codenamephp\deployer\base\functions\iSet;
22+
use de\codenamephp\platform\secretsManager\base\Client\ClientInterface;
23+
use de\codenamephp\platform\secretsManager\base\Secret\SecretInterface;
24+
use Deployer\Host\Host;
25+
26+
/**
27+
* Settings implementation that uses a client to fetch the payload of a secret and then sets it on the hosts or as global settings
28+
*
29+
* @psalm-api
30+
*/
31+
final class WithClient implements SettingsInterface {
32+
33+
public function __construct(public readonly ClientInterface $client, public readonly iSet $deployerFunctions = new All()) {}
34+
35+
public function fetch(SecretInterface $secret) : string {
36+
return $this->client->fetchPayload($secret)->getContent();
37+
}
38+
public function fetchMultiple(array $secretsToResolve) : array {
39+
return array_map(fn(SecretInterface $secret) : string => $this->fetch($secret), $secretsToResolve);
40+
41+
}
42+
public function set(string $settingsKey, SecretInterface $secret, Host ...$hosts) : void {
43+
$payload = $this->fetch($secret);
44+
$hosts ? array_map(static fn(Host $host) => $host->set($settingsKey, $payload), $hosts) : $this->deployerFunctions->set($settingsKey, $payload);
45+
}
46+
47+
public function setMultiple(array $secretsToSet, Host ...$hosts) : void {
48+
foreach($secretsToSet as $settingsKey => $secret) $this->set($settingsKey, $secret, ...$hosts);
49+
}
50+
}

test/Settings/WithClientTest.php

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
<?php declare(strict_types=1);
2+
/*
3+
* Copyright 2023 Bastian Schwarz <bastian@codename-php.de>.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
namespace de\codenamephp\deployer\secrets\test\Settings;
19+
20+
use de\codenamephp\deployer\base\functions\iSet;
21+
use de\codenamephp\deployer\secrets\Settings\WithClient;
22+
use de\codenamephp\platform\secretsManager\base\Client\ClientInterface;
23+
use de\codenamephp\platform\secretsManager\base\Secret\Payload\PayloadInterface;
24+
use de\codenamephp\platform\secretsManager\base\Secret\SecretInterface;
25+
use Deployer\Host\Host;
26+
use Mockery;
27+
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
28+
use PHPUnit\Framework\TestCase;
29+
30+
final class WithClientTest extends TestCase {
31+
32+
use MockeryPHPUnitIntegration;
33+
34+
public function testSetMultiple() : void {
35+
$payload1 = $this->createConfiguredMock(PayloadInterface::class, ['getContent' => 'secret1']);
36+
$payload2 = $this->createConfiguredMock(PayloadInterface::class, ['getContent' => 'secret2']);
37+
$payload3 = $this->createConfiguredMock(PayloadInterface::class, ['getContent' => 'secret3']);
38+
39+
$secret1 = $this->createMock(SecretInterface::class);
40+
$secret2 = $this->createMock(SecretInterface::class);
41+
$secret3 = $this->createMock(SecretInterface::class);
42+
43+
$client = Mockery::mock(ClientInterface::class);
44+
$client->allows('fetchPayload')->once()->ordered()->with($secret1)->andReturn($payload1);
45+
$client->allows('fetchPayload')->once()->ordered()->with($secret2)->andReturn($payload2);
46+
$client->allows('fetchPayload')->once()->ordered()->with($secret3)->andReturn($payload3);
47+
48+
$deployerFunctions = Mockery::mock(iSet::class);
49+
$deployerFunctions->allows('set')->once()->ordered()->with('key1', 'secret1');
50+
$deployerFunctions->allows('set')->once()->ordered()->with('key2', 'secret2');
51+
$deployerFunctions->allows('set')->once()->ordered()->with('key3', 'secret3');
52+
53+
$sut = new WithClient($client, $deployerFunctions);
54+
55+
$sut->setMultiple(['key1' => $secret1, 'key2' => $secret2, 'key3' => $secret3]);
56+
}
57+
58+
public function testSetMultiple_withHosts() : void {
59+
$host1 = Mockery::mock(Host::class);
60+
$host1->allows('set')->once()->ordered()->with('key1', 'secret1');
61+
$host1->allows('set')->once()->ordered()->with('key2', 'secret2');
62+
$host1->allows('set')->once()->ordered()->with('key3', 'secret3');
63+
$host2 = Mockery::mock(Host::class);
64+
$host2->allows('set')->once()->ordered()->with('key1', 'secret1');
65+
$host2->allows('set')->once()->ordered()->with('key2', 'secret2');
66+
$host2->allows('set')->once()->ordered()->with('key3', 'secret3');
67+
$host3 = Mockery::mock(Host::class);
68+
$host3->allows('set')->once()->ordered()->with('key1', 'secret1');
69+
$host3->allows('set')->once()->ordered()->with('key2', 'secret2');
70+
$host3->allows('set')->once()->ordered()->with('key3', 'secret3');
71+
72+
$payload1 = $this->createConfiguredMock(PayloadInterface::class, ['getContent' => 'secret1']);
73+
$payload2 = $this->createConfiguredMock(PayloadInterface::class, ['getContent' => 'secret2']);
74+
$payload3 = $this->createConfiguredMock(PayloadInterface::class, ['getContent' => 'secret3']);
75+
76+
$secret1 = $this->createMock(SecretInterface::class);
77+
$secret2 = $this->createMock(SecretInterface::class);
78+
$secret3 = $this->createMock(SecretInterface::class);
79+
80+
$client = Mockery::mock(ClientInterface::class);
81+
$client->allows('fetchPayload')->once()->ordered()->with($secret1)->andReturn($payload1);
82+
$client->allows('fetchPayload')->once()->ordered()->with($secret2)->andReturn($payload2);
83+
$client->allows('fetchPayload')->once()->ordered()->with($secret3)->andReturn($payload3);
84+
85+
$deployerFunctions = $this->createMock(iSet::class);
86+
$deployerFunctions->expects(self::never())->method('set');
87+
88+
$sut = new WithClient($client, $deployerFunctions);
89+
90+
$sut->setMultiple(['key1' => $secret1, 'key2' => $secret2, 'key3' => $secret3], $host1, $host2, $host3);
91+
}
92+
93+
public function testSet() : void {
94+
$payload = $this->createConfiguredMock(PayloadInterface::class, ['getContent' => 'secret']);
95+
$secret = $this->createMock(SecretInterface::class);
96+
97+
$client = $this->createMock(ClientInterface::class);
98+
$client->expects(self::once())->method('fetchPayload')->with($secret)->willReturn($payload);
99+
100+
$deployerFunctions = $this->createMock(iSet::class);
101+
$deployerFunctions->expects(self::once())->method('set')->with('some.key', 'secret');
102+
103+
$sut = new WithClient($client, $deployerFunctions);
104+
105+
$sut->set('some.key', $secret);
106+
}
107+
108+
public function testSet_withHosts() : void {
109+
$host1 = $this->createMock(Host::class);
110+
$host1->expects(self::once())->method('set')->with('some.key', 'secret');
111+
$host2 = $this->createMock(Host::class);
112+
$host2->expects(self::once())->method('set')->with('some.key', 'secret');
113+
$host3 = $this->createMock(Host::class);
114+
$host3->expects(self::once())->method('set')->with('some.key', 'secret');
115+
116+
$payload = $this->createConfiguredMock(PayloadInterface::class, ['getContent' => 'secret']);
117+
$secret = $this->createMock(SecretInterface::class);
118+
119+
$client = $this->createMock(ClientInterface::class);
120+
$client->expects(self::once())->method('fetchPayload')->with($secret)->willReturn($payload);
121+
122+
$deployerFunctions = $this->createMock(iSet::class);
123+
$deployerFunctions->expects(self::never())->method('set');
124+
125+
$sut = new WithClient($client, $deployerFunctions);
126+
127+
$sut->set('some.key', $secret, $host1, $host2, $host3);
128+
}
129+
130+
public function testFetchMultiple() : void {
131+
$payload1 = $this->createConfiguredMock(PayloadInterface::class, ['getContent' => 'secret1']);
132+
$payload2 = $this->createConfiguredMock(PayloadInterface::class, ['getContent' => 'secret2']);
133+
$payload3 = $this->createConfiguredMock(PayloadInterface::class, ['getContent' => 'secret3']);
134+
135+
$secret1 = $this->createMock(SecretInterface::class);
136+
$secret2 = $this->createMock(SecretInterface::class);
137+
$secret3 = $this->createMock(SecretInterface::class);
138+
139+
$client = Mockery::mock(ClientInterface::class);
140+
$client->allows('fetchPayload')->once()->ordered()->with($secret1)->andReturn($payload1);
141+
$client->allows('fetchPayload')->once()->ordered()->with($secret2)->andReturn($payload2);
142+
$client->allows('fetchPayload')->once()->ordered()->with($secret3)->andReturn($payload3);
143+
144+
$sut = new WithClient($client);
145+
146+
self::assertSame(['key1' => 'secret1', 'key2' => 'secret2', 'key3' => 'secret3'], $sut->fetchMultiple(['key1' => $secret1, 'key2' => $secret2, 'key3' => $secret3]));
147+
}
148+
149+
public function test__construct() : void {
150+
$client = $this->createMock(ClientInterface::class);
151+
$deployerFunctions = $this->createMock(iSet::class);
152+
153+
$sut = new WithClient($client, $deployerFunctions);
154+
155+
self::assertSame($client, $sut->client);
156+
self::assertSame($deployerFunctions, $sut->deployerFunctions);
157+
}
158+
159+
public function test__construct_withMinimalParameters() : void {
160+
$client = $this->createMock(ClientInterface::class);
161+
162+
$sut = new WithClient($client);
163+
164+
self::assertSame($client, $sut->client);
165+
self::assertInstanceOf(iSet::class, $sut->deployerFunctions);
166+
}
167+
168+
public function testFetch() : void {
169+
$secret = $this->createMock(SecretInterface::class);
170+
$payload = $this->createMock(PayloadInterface::class);
171+
$payload->expects(self::once())->method('getContent')->willReturn('some secret');
172+
173+
$client = $this->createMock(ClientInterface::class);
174+
$client->expects(self::once())->method('fetchPayload')->with($secret)->willReturn($payload);
175+
176+
$sut = new WithClient($client);
177+
178+
self::assertSame('some secret', $sut->fetch($secret));
179+
}
180+
}

0 commit comments

Comments
 (0)