Skip to content

Commit 188b32c

Browse files
Added media tasks
Signed-off-by: Bastian Schwarz <bastian@codename-php.de>
1 parent 654020c commit 188b32c

11 files changed

Lines changed: 605 additions & 7 deletions

File tree

.idea/inspectionProfiles/Project_Default.xml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/functions/All.php

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
namespace de\codenamephp\deployer\base\functions;
1919

2020
use Closure;
21+
use de\codenamephp\deployer\base\MissingConfigurationException;
2122
use Deployer\Deployer;
2223
use Deployer\Host\Host;
2324
use Deployer\Host\Localhost;
@@ -64,21 +65,28 @@ public function get(string $name, mixed $default = null) : mixed {
6465
}
6566

6667
public function host(string ...$hostname) : Host|array {
67-
$host = \Deployer\host(...$hostname);
68-
if($host instanceof ObjectProxy) {
68+
$hostOrHosts = \Deployer\host(...$hostname);
69+
if($hostOrHosts instanceof ObjectProxy) {
6970
/**
7071
* We need to hack a little since ObjectProxy doesn't expose the contained objects
7172
*
7273
* @psalm-suppress PossiblyInvalidFunctionCall,InaccessibleProperty
7374
*/
74-
$host = array_values(
75+
$hostOrHosts = array_values(
7576
array_filter(
76-
(array) Closure::bind(static fn(ObjectProxy $objectProxy) : array => $objectProxy->objects, null, $host)($host),
77-
static fn(mixed $host) : bool => $host instanceof Localhost
77+
(array) Closure::bind(static fn(ObjectProxy $objectProxy) : array => $objectProxy->objects, null, $hostOrHosts)($hostOrHosts),
78+
static fn(mixed $host) : bool => $host instanceof Host
7879
)
7980
);
8081
}
81-
return $host;
82+
return $hostOrHosts;
83+
}
84+
85+
public function firstHost(string ...$hostname) : Host {
86+
$hostOrHosts = $this->host(...$hostname);
87+
if($hostOrHosts === []) throw new MissingConfigurationException(sprintf('No hosts were found for the given hostnames: [%s]', implode(',', $hostname)));
88+
89+
return is_array($hostOrHosts) ? array_values($hostOrHosts)[0] : $hostOrHosts;
8290
}
8391

8492
public function getOption(string $name, mixed $default = null) : mixed {
@@ -126,7 +134,7 @@ public function parse(string $value) : string {
126134
public function parseOnHost(Host $host, string $value) : string {
127135
$finalValue = '';
128136
$this->on($host, function() use (&$finalValue, $value) { $finalValue = $this->parse($value); });
129-
return $finalValue;
137+
return (string) $finalValue;
130138
}
131139

132140
public function run(string $command, ?array $options = [], ?int $timeout = null, ?int $idle_timeout = null, ?string $secret = null, ?array $env = null, ?bool $real_time_output = false, ?bool $no_throw = false) : string {

src/functions/iHost.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
namespace de\codenamephp\deployer\base\functions;
1919

20+
use de\codenamephp\deployer\base\MissingConfigurationException;
2021
use Deployer\Host\Host;
2122

2223
/**
@@ -42,4 +43,13 @@ interface iHost {
4243
* @return Host|Host[]
4344
*/
4445
public function host(string ...$hostname) : Host|array;
46+
47+
/**
48+
* Same as host but only returns the first host that was found. If no host was found a MissingConfigurationException must be thrown
49+
*
50+
* @param string ...$hostname
51+
* @return Host
52+
* @throws MissingConfigurationException
53+
*/
54+
public function firstHost(string ...$hostname) : Host;
4555
}

src/ssh/client/StaticProxy.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php declare(strict_types=1);
2+
/*
3+
* Copyright 2022 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\base\ssh\client;
19+
20+
use Deployer\Component\Ssh\Client;
21+
use Deployer\Host\Host;
22+
23+
/**
24+
* The \Deployer\Component\Ssh\Client has some static methods I want to use but still be able to test. Just don't write static methods folks ...
25+
*/
26+
final class StaticProxy implements iClient {
27+
28+
public function connectionOptionsString(Host $host) : string {
29+
return Client::connectionOptionsString($host);
30+
}
31+
}

src/ssh/client/iClient.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php declare(strict_types=1);
2+
/*
3+
* Copyright 2022 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\base\ssh\client;
19+
20+
use Deployer\Host\Host;
21+
22+
/**
23+
* Interface for proxying the Deployer ssh client
24+
*/
25+
interface iClient {
26+
27+
/**
28+
* Gets the connection options for an ssh call for the given host
29+
*
30+
* @param Host $host The host to get the connection options for
31+
* @return string
32+
*/
33+
public function connectionOptionsString(Host $host) : string;
34+
}

src/task/media/Copy.php

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<?php declare(strict_types=1);
2+
/*
3+
* Copyright 2022 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\base\task\media;
19+
20+
use de\codenamephp\deployer\base\functions\All;
21+
use de\codenamephp\deployer\base\functions\iAll;
22+
use de\codenamephp\deployer\base\functions\iInput;
23+
use de\codenamephp\deployer\base\hostCheck\DoNotRunOnProduction;
24+
use de\codenamephp\deployer\base\hostCheck\iHostCheck;
25+
use de\codenamephp\deployer\base\iConfigurationKeys;
26+
use de\codenamephp\deployer\base\MissingConfigurationException;
27+
use de\codenamephp\deployer\base\ssh\client\iClient;
28+
use de\codenamephp\deployer\base\ssh\client\StaticProxy;
29+
use de\codenamephp\deployer\base\task\iTask;
30+
use de\codenamephp\deployer\base\transferable\iTransferable;
31+
use de\codenamephp\deployer\base\UnsafeOperationException;
32+
use Deployer\Exception\RunException;
33+
34+
/**
35+
* Task to copy media between two remotes. The copy uses rsync over a ssh tunnel with agent forwarding so the remotes do not need each others credentials but
36+
* the local credentials are used.
37+
*
38+
* @psalm-suppress PropertyNotSetInConstructor see https://github.com/vimeo/psalm/issues/4393
39+
*/
40+
final class Copy implements iTask {
41+
42+
public const OPTION_SOURCE_HOST = 'cpd:media:copy:sourceHost';
43+
44+
/**
45+
* @var array<iTransferable>
46+
*/
47+
private array $transferables;
48+
49+
/**
50+
* @param array<iTransferable> $transferables Array of transferables to copy between two remotes
51+
* @param iAll $deployerFunctions Deployer function to set options and run the copy
52+
* @param iHostCheck $hostCheck HostCheck to prevent accidental execution
53+
*/
54+
public function __construct(array $transferables,
55+
public iAll $deployerFunctions = new All(),
56+
public iHostCheck $hostCheck = new DoNotRunOnProduction(),
57+
public iClient $sshClient = new StaticProxy()) {
58+
$this->setTransferables(...$transferables);
59+
$deployerFunctions->option(
60+
self::OPTION_SOURCE_HOST,
61+
null,
62+
iInput::OPTION_VALUE_REQUIRED,
63+
'The source host to copy the media from',
64+
iConfigurationKeys::PRODUCTION
65+
);
66+
}
67+
68+
/**
69+
* @return iTransferable[]
70+
*/
71+
public function getTransferables() : array {
72+
return $this->transferables;
73+
}
74+
75+
public function setTransferables(iTransferable ...$transferables) : Copy {
76+
$this->transferables = $transferables;
77+
return $this;
78+
}
79+
80+
/**
81+
* Uses the option for the source host to get the source host, the target host is the host for the current stage and uses
82+
* those hosts to build an ssh command that connects to the source host and an rsync command that will be executed on the source host
83+
* and syncs to the target host.
84+
*
85+
* The idea is that ssh agent forwarding is enabled so the hosts can talk to each other using the local key.
86+
*
87+
* The base command is then used with all transferables so all folders are synced.
88+
*
89+
* The command cannot be run on the production stage
90+
*
91+
* @return void
92+
* @throws UnsafeOperationException if the stage is production (more precisely: if the stage check fails)
93+
* @throws MissingConfigurationException if one of the target or source host could not be found
94+
* @throws RunException
95+
*/
96+
public function __invoke() : void {
97+
$this->hostCheck->check();
98+
99+
$sourceHost = $this->deployerFunctions->firstHost((string) $this->deployerFunctions->getOption(self::OPTION_SOURCE_HOST));
100+
$targetHost = $this->deployerFunctions->currentHost();
101+
102+
$sshCommand = "ssh {$this->sshClient->connectionOptionsString($sourceHost)} {$sourceHost->getConnectionString()}";
103+
$rsyncCommand = "rsync -e 'ssh -o StrictHostKeyChecking=no {$this->sshClient->connectionOptionsString($targetHost)}' -azP %s %s {$targetHost->getConnectionString()}:%s";
104+
105+
foreach($this->getTransferables() as $transferable) {
106+
$sourcePath = $this->deployerFunctions->parseOnHost($sourceHost, $transferable->getLocalPath());
107+
$targetPath = $this->deployerFunctions->parseOnHost($targetHost, $transferable->getRemotePath());
108+
109+
$this->deployerFunctions->runLocally(sprintf('%s "%s"', $sshCommand, sprintf($rsyncCommand, implode(' ', $transferable->getConfig()['options'] ?? []), $sourcePath, $targetPath)));
110+
}
111+
}
112+
}

src/task/media/Pull.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php declare(strict_types=1);
2+
/*
3+
* Copyright 2022 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\base\task\media;
19+
20+
use de\codenamephp\deployer\base\functions\All;
21+
use de\codenamephp\deployer\base\functions\iDownload;
22+
use de\codenamephp\deployer\base\task\iTask;
23+
use de\codenamephp\deployer\base\transferable\iTransferable;
24+
use Deployer\Exception\RunException;
25+
26+
/**
27+
* Pulls uploaded media (images, files, ...) from remote to local
28+
*
29+
* @psalm-suppress PropertyNotSetInConstructor see https://github.com/vimeo/psalm/issues/4393
30+
*/
31+
final class Pull implements iTask {
32+
33+
public iDownload $deployerFunctions;
34+
35+
/**
36+
* @var array<iTransferable>
37+
*/
38+
private array $transferables;
39+
40+
public function __construct(iTransferable ...$transferables) {
41+
$this->transferables = $transferables;
42+
$this->deployerFunctions = new All();
43+
}
44+
45+
/**
46+
* @return iTransferable[]
47+
*/
48+
public function getTransferables() : array {
49+
return $this->transferables;
50+
}
51+
52+
public function setTransferables(iTransferable ...$transferables) : Pull {
53+
$this->transferables = $transferables;
54+
return $this;
55+
}
56+
57+
/**
58+
* Iterates over the transferables and passes the localPath, remotePath and config on the download function
59+
*
60+
* @return void
61+
* @throws RunException
62+
*/
63+
public function __invoke() : void {
64+
foreach($this->getTransferables() as $transferable) {
65+
$this->deployerFunctions->download($transferable->getLocalPath(), $transferable->getRemotePath(), $transferable->getConfig());
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)