Skip to content

Commit

Permalink
Merge pull request #319 from factorial-io/feature/docker-copy
Browse files Browse the repository at this point in the history
Feature/docker copy
  • Loading branch information
mbutkereit authored Nov 8, 2023
2 parents cd6fb34 + 437d122 commit 2e40e6b
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 10 deletions.
34 changes: 24 additions & 10 deletions docs/deploying-artifacts.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,12 @@ hosts:
the default actions for the git-artifact-method will copy all files to the target repo and remove the fabfile.
| property | default value | description |
|-----------------------|---------------|-------------------------------------------|
| `artifact.branch` | `false` | if set to false, the name of the source-branch is used, otherwise the value |
| property | default value | description |
|-----------------------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------|
| `artifact.branch` | `false` | if set to false, the name of the source-branch is used, otherwise the value |
| `artifact.baseBranch` | `master` | If phabalicious needs to create a new branch, because it is missing from the target-repository, then start the branch from `masterBranch` |
| `artifact.repository` | | The url to the target-repository |
| `artifact.actions` | | Actions to perform |
| `artifact.repository` | | The url to the target-repository |
| `artifact.actions` | | Actions to perform |


Phab will use a shallow clone for the target repository to keep resources low. If you need to use a deep copy for the target repository, you can adapt the gitOptions like:
Expand All @@ -155,11 +155,24 @@ You can force the artifact based deployment by adding the `--force`-option, or b

You can customize the list of actions be run when deploying an artifact. Here's a list of available actions

### docker-copy

```yaml
- action: docker-copy
arguments:
image: "%settings.gitlab.imageBaseTag%/builder:%host.branch%"
imageRootPath: /app
from: "*"
to: .
```

This action will copy the listed files from `from` inside the docker image `image` to `to`. If `from` is '*' phabalicious will get the contents of the directory. As the copy is initiated from the container, make sure that the `imageRootPath` points to a valid directory inside the container. The `image`-property supports replacement patterns, in the example case it will get the image name from the gitlab settings and the branch from the host-config. Phab will try to pull the latest image before running the action. The action supports the `excludeFiles.gitSync` settings to skip certain files and folders (it defaults to `.git`).

### copy

```yaml
- action: copy
argumnents:
arguments:
from:
- file1
- folder2
Expand All @@ -169,6 +182,7 @@ You can customize the list of actions be run when deploying an artifact. Here's

This will copy the three mentioned files and folders into the subfolder `targetsubfolder` of the target folder. Please be aware, that you might need to create subdirectories beforehand manually via the `script`-method. Also be aware that copy action deletes existing files and folders from target before doing the copy, if you want to combine files from multiple sources it is better to also use the `script`-method for that.

The action supports the `excludeFiles.gitSync` settings to skip certain files and folders (it defaults to `.git`). If `from` is `*` phabalicious will get the contents of the directory.
### delete

```yaml
Expand Down Expand Up @@ -224,10 +238,10 @@ This action comes handy when degugging the build process, as it will stop the ex

The `script`-action will run the script from the arguments section line by line. You can use the usual replacement patterns as for other scripts. Most helpful are:

| Pattern | Description |
|---------|-------------|
| `%context.data.installDir%` | The installation dir, where the app got installed into |
| `%context.data.targetDir%` | The targetdir, where the app got copied to, which gets committed or synced |
| Pattern | Description |
|-----------------------------|----------------------------------------------------------------------------|
| `%context.data.installDir%` | The installation dir, where the app got installed into |
| `%context.data.targetDir%` | The targetdir, where the app got copied to, which gets committed or synced |

If `arguments` contains a name, then this named script will be executed. It should be available under the global `scripts`-section or on the hosts' scripts section.

Expand Down
3 changes: 3 additions & 0 deletions src/Artifact/Actions/ActionBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public function validateConfig($host_config, array $action_config, ValidationErr
$service->isArray('arguments', 'arguments need to be an array!');
}

if (!isset($action_config['arguments'])) {
$action_config['arguments'] = [];
}
if (!empty($action_config['action']) && is_array($action_config['arguments'])) {
$service = new ValidationService(
$action_config['arguments'],
Expand Down
103 changes: 103 additions & 0 deletions src/Artifact/Actions/Base/DockerCopyAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php


namespace Phabalicious\Artifact\Actions\Base;

use Phabalicious\Artifact\Actions\ActionBase;
use Phabalicious\Configuration\HostConfig;
use Phabalicious\Method\TaskContextInterface;
use Phabalicious\ShellProvider\ShellProviderInterface;
use Phabalicious\Utilities\Utilities;
use Phabalicious\Validation\ValidationService;

class DockerCopyAction extends ActionBase
{
protected $dockerImageName = false;

protected function validateArgumentsConfig(array $action_arguments, ValidationService $validation)
{
$validation->hasKey('imageRootPath', 'action needs an image root path which is inside the images file system');
$validation->hasKey('image', 'action needs a docker image argument');
$validation->hasKey('to', 'action needs a to argument');
$validation->hasKey('from', 'action needs a from argument');
}

public function run(HostConfig $host_config, TaskContextInterface $context)
{
$this->getDockerImage($host_config, $context);
parent::run($host_config, $context); // TODO: Change the autogenerated stub
}

protected function getDockerImage(HostConfig $host_config, TaskContextInterface $context)
{
$variables = Utilities::buildVariablesFrom($host_config, $context);
$replacements = Utilities::expandVariables($variables);

$this->dockerImageName = Utilities::expandString($this->getArgument('image'), $replacements);
}

protected function getDirectoryContents(ShellProviderInterface $shell, string $path)
{
$result = $shell->run(sprintf(
'docker run --entrypoint /bin/ls --rm %s -1a %s',
$this->dockerImageName,
$path
), true);

if ($result->failed()) {
$result->throwException('Could not get directory contents from docker image!');
}
return $result->getOutput();
}

protected function runImplementation(
HostConfig $host_config,
TaskContextInterface $context,
ShellProviderInterface $shell,
string $install_dir,
string $target_dir
) {
$shell->pushWorkingDir($install_dir);

if ($this->getArgument('imagePull', true)) {
$shell->run(sprintf('docker pull -q %s', $this->dockerImageName));
}

$image_root_path = $this->getArgument('imageRootPath', $install_dir);
$files_to_copy = $this->getArgument('from');

if (!is_array($files_to_copy)) {
if ($files_to_copy === '*') {
$files_to_copy = array_filter(
$this->getDirectoryContents($shell, $image_root_path),
static function ($e) {
return !in_array($e, ['.', '..']);
}
);
} else {
$files_to_copy = [$files_to_copy];
}
}

$files_to_skip = $context->getConfigurationService()->getSetting('excludeFiles.gitSync', []);

// Make sure that git-related files are skipped.
$files_to_skip[] = ".git";
$to = $target_dir . '/' . $this->getArgument('to');

// Make sure the target directory exists before copying.
$shell->run(sprintf('mkdir -p %s', $to));

$docker_container = Utilities::getTempNamePrefixFromString('phab-docker-copy');
$shell->run(sprintf('docker create --name %s %s', $docker_container, $this->dockerImageName), false, true);
foreach ($files_to_copy as $file) {
if (!in_array($file, $files_to_skip)) {
$shell->run(sprintf('rm -rf %s', $to . '/' . basename($file)));
$shell->run(sprintf('docker cp -a %s:%s/%s %s', $docker_container, $image_root_path, $file, $to));
}
}
$shell->run(sprintf('docker rm %s', $docker_container), false, true);

$shell->popWorkingDir();
}
}
2 changes: 2 additions & 0 deletions src/Method/ArtifactsBaseMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Phabalicious\Artifact\Actions\Base\ConfirmAction;
use Phabalicious\Artifact\Actions\Base\CopyAction;
use Phabalicious\Artifact\Actions\Base\DeleteAction;
use Phabalicious\Artifact\Actions\Base\DockerCopyAction;
use Phabalicious\Artifact\Actions\Base\InstallScriptAction;
use Phabalicious\Artifact\Actions\Base\LogAction;
use Phabalicious\Artifact\Actions\Base\MessageAction;
Expand Down Expand Up @@ -34,6 +35,7 @@ public function __construct(LoggerInterface $logger)
parent::__construct($logger);

ActionFactory::register('base', 'copy', CopyAction::class);
ActionFactory::register('base', 'docker-copy', DockerCopyAction::class);
ActionFactory::register('base', 'delete', DeleteAction::class);
ActionFactory::register('base', 'confirm', ConfirmAction::class);
ActionFactory::register('base', 'script', ScriptAction::class);
Expand Down
3 changes: 3 additions & 0 deletions src/Method/RunCommandBaseMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Phabalicious\Configuration\Storage\Node;
use Phabalicious\ConfigurationService\DeprecatedValueMapping;
use Phabalicious\ShellProvider\ShellProviderInterface;
use Phabalicious\Utilities\Utilities;
use Phabalicious\Validation\ValidationErrorBagInterface;
use Phabalicious\Validation\ValidationService;

Expand Down Expand Up @@ -200,8 +201,10 @@ protected function runCommand(

$script_context->setShell($shell);

$variables = Utilities::buildVariablesFrom($host_config, $context);
$bag = new ScriptDataBag();
$bag->setContext($script_context)
->setVariables($variables)
->setCommands($commands)
->setRootFolder($this->getConfig($host_config, self::ROOT_FOLDER_KEY));

Expand Down

0 comments on commit 2e40e6b

Please sign in to comment.