Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

master states:442 distribute update token, and enable doil to trigger instance updates via url #446

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@
* better salt key handling
* !! Attention: doil needs to be reinstalled !!

## 20240617
## What's Changes
* update of an instance can now be triggered by url

## 20240604
## What's Changed
* CSP Rules per instance

## 20240422
## What's Changed
* fix typo in apache 000-default
Expand Down
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ The following commands are available:
* `doil delete <instance_name>` deletes an instance you do not need anymore
* `doil status` lists the current running doil instances
* `doil exec <instance_name> <cmd>` executes a bash command inside the instance
* `doil sut` (alias for `doil instances:set-update-token`) Sets an update token as an environment variable. More Infos [here](#update-token-handling)

See `doil instances:<command> --help` for more information

Expand Down Expand Up @@ -180,6 +181,8 @@ Following commands come with the `--global` flag:
* `doil instances:path`
* `doil instances:login`
* `doil instances:exec`
* `doil instances:csp`
* `doil instance:set-update-token`

**`doil repo`**
* `doil repo:add`
Expand Down Expand Up @@ -515,4 +518,19 @@ client.ini.php. **doil** offers a state for this.
```bash
doil apply <instance_name> prevent-super-global-replacement
```
As of **doil** version 20241113, **doil** applies this state independently to newly created instances.
As of **doil** version 20241113, **doil** applies this state independently to newly created instances.

### Update Token Handling
Doil instances can be updated via token. There is an entry for this in setup/doil.conf. By default, this entry
is set to 'false' and thus prevents the feature from being activated. To activate the feature once, the value
must be adjusted and then a doil update must be run.
Once the feature has been activated, the token can be updated subsequently using the following command:
```bash
doil sut -t <token>
```
If the feature is active and the token is set up, an update command can be sent via the URL http://doil/<instance>/update.
The currently checked out branch is updated. A 'composer install' and an 'php setup update' are then carried out.
It is important that the Http header contains the field 'Authorization:<token>'. A curl command might look like this.
```bash
curl -H "Authorization:MyToken" http://doil/<instance>/update -L
```
2 changes: 1 addition & 1 deletion app/src/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

class App extends Application
{
const NAME = "Doil Version 20241205 - build 2024-12-05";
const NAME = "Doil Version 20250102 - build 2025-01-02";

public function __construct(Command ...$commands)
{
Expand Down
4 changes: 3 additions & 1 deletion app/src/Commands/Instances/ApplyCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ class ApplyCommand extends Command
"change-roundcube-password",
"nodejs",
"proxy-enable-https",
"keycloak"
"keycloak",
"ilias-update-hook",
"set-update-token"
];

protected static $defaultName = "instances:apply";
Expand Down
18 changes: 18 additions & 0 deletions app/src/Commands/Instances/CreateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ public function execute(InputInterface $input, OutputInterface $output) : int
$keycloak = true;
}

$update_token = explode("=", $this->filesystem->getLineInFile("/etc/doil/doil.conf", "update_token="))[1];

$this->writer->beginBlock($output, "Creating instance " . $options['name']);

if (isset($options["repo_path"]) && ! $this->filesystem->exists($options["repo_path"])) {
Expand Down Expand Up @@ -324,6 +326,10 @@ public function execute(InputInterface $input, OutputInterface $output) : int
sleep(1);
$this->docker->setGrain($instance_salt_name, "cpass", "$cron_password");
sleep(1);
if ($update_token != "false") {
$this->docker->setGrain($instance_salt_name, "update_token", "${$update_token}");
sleep(1);
}
$this->docker->setGrain($instance_salt_name, "doil_domain", $http_scheme . $host . "/" . $options["name"]);
sleep(1);
$this->docker->setGrain($instance_salt_name, "doil_project_name", $options["name"]);
Expand Down Expand Up @@ -388,6 +394,18 @@ public function execute(InputInterface $input, OutputInterface $output) : int
$this->writer->endBlock();
}

if ($update_token != "false") {
// apply set-update-token state
$this->writer->beginBlock($output, "Apply set-update-token state");
$this->docker->applyState($instance_salt_name, "set-update-token");
$this->writer->endBlock();

// apply ilias-update-hook state
$this->writer->beginBlock($output, "Apply ilias-update-hook state");
$this->docker->applyState($instance_salt_name, "ilias-update-hook");
$this->writer->endBlock();
}

// apply access state
$this->writer->beginBlock($output, "Apply access state");
$this->docker->applyState($instance_salt_name, "access");
Expand Down
158 changes: 158 additions & 0 deletions app/src/Commands/Instances/SetUpdateTokenCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<?php declare(strict_types=1);

/* Copyright (c) 2022 - Daniel Weise <[email protected]> - Extended GPL, see LICENSE */

namespace CaT\Doil\Commands\Instances;

use CaT\Doil\Lib\Posix\Posix;
use CaT\Doil\Lib\Docker\Docker;
use CaT\Doil\Lib\ConsoleOutput\Writer;
use CaT\Doil\Lib\FileSystem\Filesystem;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Exception\InvalidArgumentException;

class SetUpdateTokenCommand extends Command
{
protected static $defaultName = "instances:set-update-token";
protected static $defaultDescription =
"<fg=red>!NEEDS SUDO PRIVILEGES!</> This command sets a update token for all instances"
;

protected Docker $docker;
protected Posix $posix;
protected Filesystem $filesystem;
protected Writer $writer;

public function __construct(Docker $docker, Posix $posix, Filesystem $filesystem, Writer $writer)
{
parent::__construct();

$this->docker = $docker;
$this->posix = $posix;
$this->filesystem = $filesystem;
$this->writer = $writer;
}

public function configure() : void
{
$this
->setAliases(["sut"])
->addOption("token", "t", InputOption::VALUE_REQUIRED, "Update token as string")
->addOption("global", "g", InputOption::VALUE_NONE, "Determines if an instance is global or not")
->addOption("autoyes", "a", InputOption::VALUE_NONE, "Auto answer questions with yes")
;
}

public function execute(InputInterface $input, OutputInterface $output) : int
{
if (! $this->posix->isSudo()) {
$this->writer->error(
$output,
"Please execute this script as sudo user!"
);
return Command::FAILURE;
}

$token = $input->getOption("token");

$home_dir = $this->posix->getHomeDirectory($this->posix->getUserId());

$path = "/usr/local/share/doil/instances";
$suffix = "global";
if (! $input->getOption("global")) {
$path = "$home_dir/.doil/instances";
$suffix = "local";
}

$instances = $this->filesystem->getFilesInPath($path);
if (count($instances) == 0) {
$this->writer->error(
$output,
"No instances found!",
"Use <fg=gray>doil instances:ls --help</> for more information."
);
return Command::FAILURE;
}

if (! $input->getOption("autoyes")) {
$question = new ConfirmationQuestion(
"This will also update 'update_token' in your doil config. Want to continue? [yN]: ",
false
);

$helper = $this->getHelper("question");
if (!$helper->ask($input, $output, $question)) {
$output->writeln("Abort by user!");
return Command::FAILURE;
}
}


$this->filesystem->replaceLineInFile("/etc/doil/doil.conf", "/update_token=.*/", "update_token=" . $token);

foreach ($instances as $i) {
$started = $this->startInstance($output, $path, $i);
sleep(3);
$this->applyUpdateToken($output, $i . "." . $suffix, $token);
$this->docker->commit($i . "_" . $suffix);
if ($started) {
$this->stopInstance($output, $path, $i);
}
}
return Command::SUCCESS;
}

protected function startInstance(OutputInterface $output, string $path, string $instance) : bool
{
if (! $this->hasDockerComposeFile($path . "/" . $instance, $output)) {
throw new InvalidArgumentException("Can't find a suitable docker-compose.yml file in $path/$instance");
}

if (! $this->docker->isInstanceUp($path . "/" . $instance)) {
$this->writer->beginBlock($output, "Start instance $instance");
$this->docker->startContainerByDockerCompose($path . "/" . $instance);
$this->writer->endBlock();
return true;
}

return false;
}

protected function stopInstance(OutputInterface $output, string $path, string $instance) : string
{
if ($this->docker->isInstanceUp($path . "/" . $instance)) {
$this->writer->beginBlock($output, "Stop instance $instance");
$this->docker->stopContainerByDockerCompose($path . "/" . $instance);
$this->writer->endBlock();
}

return $instance;
}

protected function hasDockerComposeFile(string $path, OutputInterface $output) : bool
{
if ($this->filesystem->exists($path . "/docker-compose.yml")) {
return true;
}

$output->writeln("<fg=red>Error:</>");
$output->writeln("\tCan't find a suitable docker-compose file in this directory '$path'.");
$output->writeln("\tIs this the right directory?");
$output->writeln("\tSupported filenames: docker-compose.yml");

return false;
}

protected function applyUpdateToken(OutputInterface $output, string $salt_key, string $token): void
{
$this->writer->beginBlock($output, "Apply update token to $salt_key");
$this->docker->setGrain($salt_key, "update_token", $token);
$this->docker->refreshGrains($salt_key);
$this->docker->applyState($salt_key, "set-update-token");
$this->writer->endBlock();
}
}
23 changes: 21 additions & 2 deletions app/src/Commands/Pack/PackCreateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ public function execute(InputInterface $input, OutputInterface $output) : int
if ($this->filesystem->exists(self::KEYCLOAK_PATH)) {
$keycloak = true;
}
$update_token = explode("=", $this->filesystem->getLineInFile("/etc/doil/doil.conf", "update_token="))[1];

$this->writer->beginBlock($output, "Creating instance " . $options['name']);

Expand Down Expand Up @@ -319,9 +320,15 @@ public function execute(InputInterface $input, OutputInterface $output) : int
}

$this->docker->setGrain($instance_salt_name, "mpass", "${mysql_password}");
$host = explode("=", $this->filesystem->getLineInFile("/etc/doil/doil.conf", "host"));

sleep(1);
$this->docker->setGrain($instance_salt_name, "cpass", "${cron_password}");
$this->docker->setGrain($instance_salt_name, "cpass", "$cron_password");
sleep(1);
if ($update_token != "false") {
$this->docker->setGrain($instance_salt_name, "update_token", "${update_token}");
sleep(1);
}
$doil_domain = $http_scheme . $host . "/" . $options["name"];
$this->docker->setGrain($instance_salt_name, "doil_domain", "${doil_domain}");
sleep(1);
Expand Down Expand Up @@ -367,10 +374,22 @@ public function execute(InputInterface $input, OutputInterface $output) : int
$this->writer->endBlock();
}


// apply composer state
$this->writer->beginBlock($output, "Apply composer state");
$this->docker->applyState($instance_salt_name, $this->getComposerVersion($ilias_version));
$this->writer->endBlock();

if ($update_token != "false") {
// apply set-update-token state
$this->writer->beginBlock($output, "Apply set-update-token state");
$this->docker->applyState($instance_salt_name, "set-update-token");
$this->writer->endBlock();

// apply ilias-update-hook state
$this->writer->beginBlock($output, "Apply ilias-update-hook state");
$this->docker->applyState($instance_salt_name, "ilias-update-hook");
$this->writer->endBlock();
}

// apply enable-captainhook state
$this->writer->beginBlock($output, "Apply enable-captainhook state");
Expand Down
10 changes: 10 additions & 0 deletions app/src/cli.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ function buildContainerForApp() : Container
$c["command.instances.login"],
$c["command.instances.path"],
$c["command.instances.restart"],
$c["command.instances.set.update.token"],
$c["command.instances.status"],
$c["command.instances.up"],
$c["command.keycloak.down"],
Expand Down Expand Up @@ -240,6 +241,15 @@ function buildContainerForApp() : Container
);
};

$c["command.instances.set.update.token"] = function($c) {
return new Instances\SetUpdateTokenCommand(
$c["docker.shell"],
$c["posix.shell"],
$c["filesystem.shell"],
$c["command.writer"]
);
};

$c["command.instances.status"] = function($c) {
return new Instances\StatusCommand(
$c["docker.shell"]
Expand Down
10 changes: 7 additions & 3 deletions app/tests/Commands/Instances/CreateCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -335,10 +335,14 @@ public function test_execute() : void
->willReturn(false, true, false, true)
;
$filesystem
->expects($this->exactly(2))
->expects($this->exactly(3))
->method("getLineInFile")
->withConsecutive(["/etc/doil/doil.conf", "host="], ["/etc/doil/doil.conf", "https_proxy="])
->willReturnOnConsecutiveCalls("foo=doil", "foo=false")
->withConsecutive(
["/etc/doil/doil.conf", "host="],
["/etc/doil/doil.conf", "https_proxy="],
["/etc/doil/doil.conf", "update_token="]
)
->willReturnOnConsecutiveCalls("foo=doil", "foo=false", "update_token=false")
;
$filesystem
->expects($this->once())
Expand Down
1 change: 1 addition & 0 deletions setup/conf/doil.conf
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ keycloak_new_admin_password=admin
keycloak_old_admin_password=admin
keycloak_db_username=admin
keycloak_db_password=admin
update_token=DasToken
4 changes: 4 additions & 0 deletions setup/stack/config/master.cnf
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,8 @@ file_roots:
- /srv/salt/states/ilias
keycloak:
- /srv/salt/states/keycloak
ilias-update-hook:
- /srv/salt/states/ilias-update-hook
compile-skins:
- /srv/salt/states/compile-skins
composer:
Expand Down Expand Up @@ -720,6 +722,8 @@ file_roots:
- /srv/salt/states/disable-saml
prevent-super-global-replacement:
- /srv/salt/states/prevent-super-global-replacement
set-update-token:
- /srv/salt/states/set-update-token


# The master_roots setting configures a master-only copy of the file_roots dictionary,
Expand Down
1 change: 1 addition & 0 deletions setup/stack/states/ilias-update-hook/description.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
description = Inject an update hook file into docroot
Loading
Loading