Skip to content

Commit

Permalink
Dev: added support for docker by port
Browse files Browse the repository at this point in the history
  • Loading branch information
andrey18106 committed Jul 12, 2023
1 parent e0a7147 commit 8d3f45b
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 33 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@
.vscode-upload.json
.*.sw*
node_modules
.venv
vendor
20 changes: 18 additions & 2 deletions docs/deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ The second step is to deploy ExApp on registered daemon. This is done using `occ
### CLI

```
app_ecosystem_v2:app:deploy [--info-xml INFO-XML] [-e|--env ENV] [--] <appid> <daemon-config-id>
app_ecosystem_v2:app:deploy [--info-xml INFO-XML] [--ssl_key SSL_KEY] [--ssl_password SSL_PASSWORD] [--ssl_cert SSL_CERT] [--ssl_cert_password SSL_CERT_PASSWORD] [-e|--env ENV] [--] <appid> <daemon-config-id>
```

arguments:
Expand All @@ -82,6 +82,10 @@ arguments:
options:

- `info-xml` - `[required]` path to info.xml (see [info.xml schema](#exapp-infoxml-schema)) file (url or local absolute path)
- `ssl_key` - `[optional]` path to SSL key file (local absolute path), may be required in some cases (e.g. remote docker daemon with TLS enabled, see [docker daemon TLS](#docker-daemon-port)
- `ssl_key_password` - `[optional]` SSL key password
- `ssl_cert` - `[optional]` path to SSL cert file (local absolute path)
- `ssl_cert_password` - `[optional]` SSL cert password
- `env` - `[required]` environment variables to pass to the docker container (list of required env variables is defined below [deploy env variables](#deploy-env-variables))

Successful deployment will return the following JSON output which is used then in ExApp registration:
Expand All @@ -95,7 +99,7 @@ Successful deployment will return the following JSON output which is used then i
"secret":"***generated-secret***",
"host":"app_python_skeleton",
"port":"9001",
"system_app": false
"system_app": true
}
```

Expand All @@ -120,6 +124,18 @@ Let's say we want to deploy ExApp with appid `app_python_skeleton` and version `
php occ app_ecosystem_v2:app:deploy app_python_skeleton 1 --info-xml https://raw.githubusercontent.com/cloud-py-api/py_app_v2-skeleton/main/appinfo/info.xml
```

# Docker daemon port

If you want to connect to remote docker daemon with TLS enabled, you need to provide SSL key and cert by provided options.
Important: before deploy you need to import ca.pem file using occ command:

```
php occ security:certificates:import /path/to/ca.pem
```

The daemon must be configured with `protocol=net`, `host=https://dockerapihost`, `port=8443`.
More info about how to configure daemon will be added soon.

## ExApp registration

The third step is to register ExApp. This is done using `occ` CLI tool or in AppEcosystemV2 UI (`to be implemented`).
Expand Down
13 changes: 12 additions & 1 deletion lib/Command/ExApp/Deploy.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ protected function configure() {
$this->addArgument('daemon-config-id', InputArgument::REQUIRED);

$this->addOption('info-xml', null, InputOption::VALUE_REQUIRED, '[required] Path to ExApp info.xml file (url or local absolute path)');
$this->addOption('ssl_key', null, InputOption::VALUE_REQUIRED, 'SSL key for daemon connection (local absolute path)');
$this->addOption('ssl_key_password', null, InputOption::VALUE_REQUIRED, 'SSL key password for daemon connection');
$this->addOption('ssl_cert', null, InputOption::VALUE_REQUIRED, 'SSL cert for daemon connection (local absolute path)');
$this->addOption('ssl_cert_password', null, InputOption::VALUE_REQUIRED, 'SSL cert password for daemon connection');
$this->addOption('env', 'e', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Docker container environment variables', []);
}

Expand Down Expand Up @@ -136,7 +140,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int
], $envParams, $deployConfig);
$containerParams['env'] = $envs;

[$pullResult, $createResult, $startResult] = $this->dockerActions->deployExApp($daemonConfig, $imageParams, $containerParams);
$sslParams = [
'ssl_key' => $input->getOption('ssl_key'),
'ssl_key_password' => $input->getOption('ssl_key_password'),
'ssl_cert' => $input->getOption('ssl_cert'),
'ssl_cert_password' => $input->getOption('ssl_cert_password'),
];

[$pullResult, $createResult, $startResult] = $this->dockerActions->deployExApp($daemonConfig, $imageParams, $containerParams, $sslParams);

if (isset($pullResult['error'])) {
$output->writeln(sprintf('ExApp %s deployment failed. Error: %s', $appId, $pullResult['error']));
Expand Down
97 changes: 67 additions & 30 deletions lib/Docker/DockerActions.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,28 @@
namespace OCA\AppEcosystemV2\Docker;


use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use OCA\AppEcosystemV2\Db\DaemonConfig;
use OCP\ICertificateManager;
use OCP\IConfig;
use Psr\Log\LoggerInterface;

class DockerActions {
public const DOCKER_API_VERSION = 'v1.41';
private LoggerInterface $logger;
private \GuzzleHttp\Client $guzzleClient;

public function __construct(LoggerInterface $logger) {
private Client $guzzleClient;
private ICertificateManager $certificateManager;
private IConfig $config;

public function __construct(
LoggerInterface $logger,
IConfig $config,
ICertificateManager $certificateManager
) {
$this->logger = $logger;
$this->guzzleClient = new \GuzzleHttp\Client(
[
'curl' => [
CURLOPT_UNIX_SOCKET_PATH => '/var/run/docker.sock', // default docker socket path
],
]
);
$this->certificateManager = $certificateManager;
$this->config = $config;
}

/**
Expand All @@ -58,50 +62,56 @@ public function __construct(LoggerInterface $logger) {
* @param DaemonConfig $daemonConfig
* @param array $imageParams
* @param array $containerParams
* @param array $sslParams
*
* @return array
*/
public function deployExApp(
DaemonConfig $daemonConfig,
array $imageParams,
array $containerParams,
array $sslParams,
): array {
if ($daemonConfig->getAcceptsDeployId() !== 'docker-install') {
return ['error' => 'Only docker-install is supported for now.'];
}
$dockerUrl = 'http://localhost';
$guzzleParams = [];
if ($daemonConfig->getProtocol() === 'unix-socket') {
$this->guzzleClient = new \GuzzleHttp\Client(
[
'curl' => [
CURLOPT_UNIX_SOCKET_PATH => $daemonConfig->getHost(),
],
]
);
$guzzleParams = [
'curl' => [
CURLOPT_UNIX_SOCKET_PATH => $daemonConfig->getHost(),
],
];
} else if ($daemonConfig->getProtocol() === 'net') {
$dockerUrl = $daemonConfig->getHost() . ':' . $daemonConfig->getPort();
}
$guzzleParams = $this->setupCerts($guzzleParams, $sslParams);
$this->guzzleClient = new Client($guzzleParams);

$pullResult = $this->pullContainer($imageParams);
$pullResult = $this->pullContainer($dockerUrl, $imageParams);
if (isset($pullResult['error'])) {
return [$pullResult, null, null];
}

$createResult = $this->createContainer($imageParams, $containerParams);
$createResult = $this->createContainer($dockerUrl, $imageParams, $containerParams);
if (isset($createResult['error'])) {
return [null, $createResult, null];
}

$startResult = $this->startContainer($createResult['Id']);
$startResult = $this->startContainer($dockerUrl, $createResult['Id']);
return [$pullResult, $createResult, $startResult];
}

public function buildApiUrl(string $url): string {
return sprintf('http://localhost/%s/%s', self::DOCKER_API_VERSION, $url);
public function buildApiUrl(string $dockerUrl, string $route): string {
return sprintf('%s/%s/%s', $dockerUrl, self::DOCKER_API_VERSION, $route);
}

public function buildImageName(array $imageParams): string {
return $imageParams['image_src'] . '/' . $imageParams['image_name'] . ':' . $imageParams['image_tag'];
}

public function createContainer(array $imageParams, array $params = []): array {
public function createContainer(string $dockerUrl, array $imageParams, array $params = []): array {
$containerParams = [
'Image' => $this->buildImageName($imageParams),
'Hostname' => $params['hostname'],
Expand All @@ -124,7 +134,7 @@ public function createContainer(array $imageParams, array $params = []): array {
$containerParams['NetworkingConfig'] = $networkingConfig;
}

$url = $this->buildApiUrl(sprintf('containers/create?name=%s', urlencode($params['name'])));
$url = $this->buildApiUrl($dockerUrl, sprintf('containers/create?name=%s', urlencode($params['name'])));
try {
$options['json'] = $containerParams;
$response = $this->guzzleClient->post($url, $options);
Expand All @@ -136,8 +146,8 @@ public function createContainer(array $imageParams, array $params = []): array {
}
}

public function startContainer(string $containerId): array {
$url = $this->buildApiUrl(sprintf('containers/%s/start', $containerId));
public function startContainer(string $dockerUrl, string $containerId): array {
$url = $this->buildApiUrl($dockerUrl, sprintf('containers/%s/start', $containerId));
try {
$response = $this->guzzleClient->post($url);
return ['success' => $response->getStatusCode() === 204];
Expand All @@ -148,8 +158,8 @@ public function startContainer(string $containerId): array {
}
}

public function pullContainer(array $params): array {
$url = $this->buildApiUrl(sprintf('images/create?fromImage=%s', $this->buildImageName($params)));
public function pullContainer(string $dockerUrl, array $params): array {
$url = $this->buildApiUrl($dockerUrl, sprintf('images/create?fromImage=%s', $this->buildImageName($params)));
try {
$xRegistryAuth = json_encode([
'https://' . $params['image_src'] => []
Expand All @@ -167,8 +177,8 @@ public function pullContainer(array $params): array {
}
}

public function inspectContainer(string $containerId): array {
$url = $this->buildApiUrl(sprintf('containers/%s/json', $containerId));
public function inspectContainer(string $dockerUrl, string $containerId): array {
$url = $this->buildApiUrl($dockerUrl, sprintf('containers/%s/json', $containerId));
try {
$response = $this->guzzleClient->get($url);
return json_decode((string) $response->getBody(), true);
Expand All @@ -178,4 +188,31 @@ public function inspectContainer(string $containerId): array {
return ['error' => 'Failed to inspect container'];
}
}

/**
* @param array $guzzleParams
* @param array $sslParams ['ssl_key', 'ssl_password', 'ssl_cert', 'ssl_cert_password']
*
* @return array
*/
private function setupCerts(array $guzzleParams, array $sslParams): array {
if (!$this->config->getSystemValueBool('installed', false)) {
$certs = \OC::$SERVERROOT . '/resources/config/ca-bundle.crt';
} else {
$certs = $this->certificateManager->getAbsoluteBundlePath();
}

$guzzleParams['verify'] = $certs;
if (isset($sslParams['ssl_key'])) {
$guzzleParams['ssl_key'] = !isset($sslParams['ssl_key_password'])
? $sslParams['ssl_key']
: [$sslParams['ssl_key'], $sslParams['ssl_key_password']];
}
if (isset($sslParams['ssl_cert'])) {
$guzzleParams['cert'] = !isset($sslParams['ssl_cert_password'])
? $sslParams['ssl_cert']
: [$sslParams['ssl_cert'], $sslParams['ssl_cert_password']];
}
return $guzzleParams;
}
}

0 comments on commit 8d3f45b

Please sign in to comment.