Skip to content

Commit

Permalink
Merge pull request #535 from getwud/feature/#464_trigger_command
Browse files Browse the repository at this point in the history
⭐ [COMMAND] - Add support for Command trigger
  • Loading branch information
fmartinou authored Dec 29, 2024
2 parents 30c3837 + 6cd3b2e commit a0af3ed
Show file tree
Hide file tree
Showing 9 changed files with 261 additions and 2 deletions.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ WORKDIR /home/node/app

RUN mkdir /store

# Add TZDATA to allow easy local time configuration
# Add useful stuff
RUN apt update \
&& apt install -y tzdata openssl curl \
&& apt install -y tzdata openssl curl git jq \
&& rm -rf /var/cache/apt/*

# Dependencies stage
Expand Down
2 changes: 2 additions & 0 deletions app/nodemon.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"WUD_WATCHER_LOCAL_WATCHBYDEFAULT": "false",
"WUD_AUTH_BASIC_JOHN_USER": "john",
"WUD_AUTH_BASIC_JOHN_HASH": "$apr1$aefKbZEa$ZSA5Y3zv9vDQOxr283NGx/",
"WUD_TRIGGER_COMMAND_TMP_CMD": "bash -c ../test/cmd_test.sh",
"WUD_TRIGGER_COMMAND_TMP_ONCE": "false",
"WUD_TRIGGER_NTFY_ONE_TOPIC": "235ef38e-f1db-414a-964f-ce3f2cc8094d",
"WUD_TRIGGER_NTFY_TWO_TOPIC": "6abd7f9d-f140-4543-af3e-749252660d89",
"WUD_TRIGGER_NTFY_THREE_TOPIC": "9f5c9961-adb2-4ab7-a078-0671856948c4",
Expand Down
82 changes: 82 additions & 0 deletions app/triggers/providers/command/Command.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
const util = require('node:util');
const exec = util.promisify(require('node:child_process').exec);
const Trigger = require('../Trigger');

const { flatten } = require('../../../model/container');
/**
* Command Trigger implementation
*/
class Command extends Trigger {
/**
* Get the Trigger configuration schema.
* @returns {*}
*/
getConfigurationSchema() {
return this.joi.object().keys({
cmd: this.joi.string().required(),
shell: this.joi.string().default('/bin/sh'),
timeout: this.joi.number().min(0).default(60000),
});
}

/**
* Run the command with new image version details.
*
* @param container the container
* @returns {Promise<void>}
*/
async trigger(container) {
return this.runCommand({
container_json: JSON.stringify(container),
...flatten(container),
});
}

/**
* Run the command with new image version details.
* @param containers
* @returns {Promise<*>}
*/
async triggerBatch(containers) {
return this.runCommand({
containers_json: JSON.stringify(containers),
});
}

/**
* Run the command.
* @param {*} extraEnvVars
*/
async runCommand(extraEnvVars) {
const commandOptions = {
env: {
...process.env,
...extraEnvVars,
},
shell: this.configuration.shell,
timeout: this.configuration.timeout,
};
try {
const { stdout, stderr } = await exec(
this.configuration.cmd,
commandOptions,
);
if (stdout) {
this.log.info(
`Command ${this.configuration.cmd} \nstdout ${stdout}`,
);
}
if (stderr) {
this.log.warn(
`Command ${this.configuration.cmd} \nstderr ${stderr}`,
);
}
} catch (err) {
this.log.warn(
`Command ${this.configuration.cmd} \nexecution error (${err.message})`,
);
}
}
}

module.exports = Command;
45 changes: 45 additions & 0 deletions app/triggers/providers/command/Command.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const { ValidationError } = require('joi');

const Command = require('./Command');

const command = new Command();

const configurationValid = {
cmd: 'echo "hello"',
timeout: 60000,
shell: '/bin/sh',
threshold: 'all',
mode: 'simple',
once: true,
auto: true,
simpletitle: 'New ${kind} found for container ${name}',
simplebody:
'Container ${name} running with ${kind} ${local} can be updated to ${kind} ${remote}\n${link}',
batchtitle: '${count} updates available',
};

beforeEach(() => {
jest.resetAllMocks();
});

test('validateConfiguration should return validated configuration when valid', () => {
const validatedConfiguration =
command.validateConfiguration(configurationValid);
expect(validatedConfiguration).toStrictEqual(configurationValid);
});

test('validateConfiguration should apply_default_configuration', () => {
const validatedConfiguration = command.validateConfiguration({
cmd: configurationValid.cmd,
});
expect(validatedConfiguration).toStrictEqual(configurationValid);
});

test('validateConfiguration should throw error when invalid', () => {
const configuration = {
command: 123456789,
};
expect(() => {
command.validateConfiguration(configuration);
}).toThrowError(ValidationError);
});
1 change: 1 addition & 0 deletions docs/changelog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- :star: [TRIGGER] - Add support for automatic or manual triggers
- :star: [REGISTRY] - Add support for multiple registries of the same type
- :star: [DOCKER] - Add default healthcheck to the `wud` docker image
- :star: [COMMAND] - Add support for [Command](/configuration/triggers/command/) trigger
- :fire: [TRIGGER] - Fix specific triggers to specific containers association issue
- :lock: Upgrade to node.js 23
- :lock: Add prettier
Expand Down
122 changes: 122 additions & 0 deletions docs/configuration/triggers/command/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Command
![logo](command.png)

The `command` trigger lets you run arbitrary commands upon container update notifications.

### Variables

| Env var | Required | Description | Supported values | Default value when missing |
|----------------------------------------------|:--------------:|-----------------------------|---------------------------------------------|----------------------------|
| `WUD_TRIGGER_COMMAND_{trigger_name}_CMD` | :red_circle: | The command to run | | |
| `WUD_TRIGGER_COMMAND_{trigger_name}_SHELL` | :red_circle: | The shell to use | Any valid installed shell path | `/bin/sh` |
| `WUD_TRIGGER_COMMAND_{trigger_name}_TIMEOUT` | :red_circle: | The command timeout (in ms) | Any positive integer (`0` means no timeout) | `60000` |

?> This trigger also supports the [common configuration variables](configuration/triggers/?id=common-trigger-configuration).

?> Update informations are passed as environment variables (see below).

### Environment variables passed to the executed command
#### In simple mode (execution per container to update)
- display_icon
- display_name
- id
- image_architecture
- image_created
- image_digest_repo
- image_digest_watch
- image_id
- image_name
- image_os
- image_registry_name
- image_registry_url
- image_tag_semver
- image_tag_value
- name
- result_tag
- status
- update_available
- update_kind_kind
- update_kind_local_value
- update_kind_remote_value
- update_kind_semver_diff
- watcher

##### Example
```
display_icon='mdi:docker'
display_name='test-nginx-1'
id='94f9f845de0fc4f8ad17c0ee1aaeaf495669de229edf41cdcd14d2af7157e47e'
image_architecture='amd64'
image_created='2023-06-13T07:15:33.483Z'
image_digest_repo='sha256:b997b0db9c2bc0a2fb803ced5fb9ff3a757e54903a28ada3e50412cc3ab7822f'
image_digest_watch=false
image_id='sha256:7d3c40f240e18f6b440bf06b1dfd8a9c48a49c1dfe3400772c3b378739cbdc47'
image_name='library/nginx'
image_os='linux'
image_registry_name='hub.public'
image_registry_url='https://registry-1.docker.io/v2'
image_tag_semver=true
image_tag_value='1.25.0'
name='test-nginx-1'
result_tag='stable-alpine3.20-slim'
status='running'
update_available=true
update_kind_kind='tag'
update_kind_local_value='1.25.0'
update_kind_remote_value='stable-alpine3.20-slim'
update_kind_semver_diff='major'
watcher='local'
```

?> In addition, a `container_json` environment variable is passed containing the full `container` entity as a JSON string.

#### In batch mode (execution for a batch of containers to update)

?> A `containers_json` environment variable is passed containing the array of all the containers to update as a JSON string.

### Examples

#### Running an arbitrary command

<!-- tabs:start -->
#### **Docker Compose**
```yaml
services:
whatsupdocker:
image: getwud/wud
...
environment:
- WUD_TRIGGER_COMMAND_LOCAL_CMD=echo $${display_name} can be updated to $${update_kind_remote_value}
```
#### **Docker**
```bash
docker run \
-e WUD_TRIGGER_COMMAND_LOCAL_CMD=echo ${display_name} can be updated to ${update_kind_remote_value} \
...
getwud/wud
```
<!-- tabs:end -->

#### Running a custom bash script

<!-- tabs:start -->
#### **Docker Compose**
```yaml
services:
whatsupdocker:
image: getwud/wud
...
environment:
- WUD_TRIGGER_COMMAND_LOCAL_CMD=bash -c /wud/trigger.sh
volumes:
- ${PWD}/wud/trigger.sh:/wud/trigger.sh
```
#### **Docker**
```bash
docker run \
-e WUD_TRIGGER_COMMAND_LOCAL_CMD=WUD_TRIGGER_COMMAND_LOCAL_CMD=bash -c /wud/trigger.sh \
-v ${PWD}/wud/trigger.sh:/wud/trigger.sh
...
getwud/wud
```
<!-- tabs:end -->
Binary file added docs/configuration/triggers/command/command.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/configuration/triggers/sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- [Storage](configuration/storage/)
- [Triggers](configuration/triggers/)
- [Apprise](configuration/triggers/apprise/)
- [Command](configuration/triggers/command/)
- [Docker](configuration/triggers/docker/)
- [Docker Compose](configuration/triggers/docker-compose/)
- [Gotify](configuration/triggers/gotify/)
Expand Down
6 changes: 6 additions & 0 deletions test/cmd_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash

set -e
echo ${name}
echo ${update_kind_local_value}
echo ${update_kind_remote_value}

0 comments on commit a0af3ed

Please sign in to comment.