foreman_openbolt 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +190 -19
- data/Rakefile +17 -93
- data/app/controllers/foreman_openbolt/task_controller.rb +61 -49
- data/app/lib/actions/foreman_openbolt/cleanup_proxy_artifacts.rb +11 -10
- data/app/lib/actions/foreman_openbolt/poll_task_status.rb +70 -60
- data/app/models/foreman_openbolt/task_job.rb +16 -17
- data/config/routes.rb +0 -1
- data/lib/foreman_openbolt/engine.rb +11 -11
- data/lib/foreman_openbolt/version.rb +1 -1
- data/lib/proxy_api/openbolt.rb +25 -9
- data/lib/tasks/foreman_openbolt_tasks.rake +1 -22
- data/locale/gemspec.rb +1 -1
- data/package.json +11 -15
- data/test/acceptance/acceptance_helper.rb +146 -0
- data/test/acceptance/docker/docker-compose.yml +69 -0
- data/test/acceptance/docker/foreman/Dockerfile +45 -0
- data/test/acceptance/docker/foreman/entrypoint.sh +26 -0
- data/test/acceptance/docker/target/Dockerfile +29 -0
- data/test/acceptance/docker/target/entrypoint.sh +11 -0
- data/test/acceptance/fixtures/modules/acceptance/tasks/complex_params.json +30 -0
- data/test/acceptance/fixtures/modules/acceptance/tasks/complex_params.sh +16 -0
- data/test/acceptance/fixtures/modules/acceptance/tasks/echo.json +13 -0
- data/test/acceptance/fixtures/modules/acceptance/tasks/echo.sh +3 -0
- data/test/acceptance/fixtures/modules/acceptance/tasks/failing_task.json +8 -0
- data/test/acceptance/fixtures/modules/acceptance/tasks/failing_task.sh +3 -0
- data/test/acceptance/fixtures/modules/acceptance/tasks/noop_task.json +8 -0
- data/test/acceptance/fixtures/modules/acceptance/tasks/noop_task.sh +2 -0
- data/test/acceptance/fixtures/modules/acceptance/tasks/slow_task.json +14 -0
- data/test/acceptance/fixtures/modules/acceptance/tasks/slow_task.sh +3 -0
- data/test/acceptance/fixtures/modules/acceptance/tasks/target_conditional.json +13 -0
- data/test/acceptance/fixtures/modules/acceptance/tasks/target_conditional.sh +9 -0
- data/test/acceptance/fixtures/openbolt.yml +7 -0
- data/test/acceptance/tests/error_handling_test.rb +40 -0
- data/test/acceptance/tests/host_selector_test.rb +31 -0
- data/test/acceptance/tests/launch_task_test.rb +96 -0
- data/test/acceptance/tests/parameter_table_test.rb +61 -0
- data/test/acceptance/tests/settings_test.rb +95 -0
- data/test/acceptance/tests/ssh_options_test.rb +77 -0
- data/test/acceptance/tests/task_execution_test.rb +40 -0
- data/test/acceptance/tests/task_history_test.rb +84 -0
- data/test/acceptance/tests/transport_options_test.rb +121 -0
- data/test/test_plugin_helper.rb +12 -3
- data/test/unit/controllers/task_controller_test.rb +351 -0
- data/test/unit/docker/Dockerfile +47 -0
- data/test/unit/docker/docker-compose.yml +33 -0
- data/test/unit/docker/entrypoint.sh +4 -0
- data/test/unit/factories/foreman_openbolt_factories.rb +39 -0
- data/test/unit/lib/actions/cleanup_proxy_artifacts_test.rb +51 -0
- data/test/unit/lib/actions/poll_task_status_test.rb +141 -0
- data/test/unit/lib/proxy_api/openbolt_test.rb +174 -0
- data/test/unit/models/task_job_test.rb +278 -0
- data/webpack/__mocks__/foremanReact/common/I18n.js +15 -0
- data/webpack/__mocks__/foremanReact/components/ToastsList/index.js +6 -0
- data/webpack/__mocks__/foremanReact/redux/API/index.js +11 -0
- data/webpack/src/Components/LaunchTask/FieldTable.js +8 -5
- data/webpack/src/Components/LaunchTask/HostSelector/SearchSelect.js +74 -62
- data/webpack/src/Components/LaunchTask/HostSelector/SelectedChips.js +11 -13
- data/webpack/src/Components/LaunchTask/HostSelector/index.js +28 -33
- data/webpack/src/Components/LaunchTask/OpenBoltOptionsSection.js +3 -2
- data/webpack/src/Components/LaunchTask/ParameterField.js +2 -0
- data/webpack/src/Components/LaunchTask/SmartProxySelect.js +2 -1
- data/webpack/src/Components/LaunchTask/TaskSelect.js +3 -3
- data/webpack/src/Components/LaunchTask/__tests__/EmptyContent.test.js +10 -0
- data/webpack/src/Components/LaunchTask/__tests__/LaunchTask.test.js +83 -0
- data/webpack/src/Components/LaunchTask/__tests__/ParameterField.test.js +86 -0
- data/webpack/src/Components/LaunchTask/__tests__/ParametersSection.test.js +50 -0
- data/webpack/src/Components/LaunchTask/__tests__/SmartProxySelect.test.js +63 -0
- data/webpack/src/Components/LaunchTask/__tests__/TaskSelect.test.js +39 -0
- data/webpack/src/Components/LaunchTask/hooks/__tests__/useOpenBoltOptions.test.js +90 -0
- data/webpack/src/Components/LaunchTask/hooks/__tests__/useSmartProxies.test.js +69 -0
- data/webpack/src/Components/LaunchTask/hooks/__tests__/useTasksData.test.js +103 -0
- data/webpack/src/Components/LaunchTask/hooks/useOpenBoltOptions.js +9 -11
- data/webpack/src/Components/LaunchTask/hooks/useSmartProxies.js +12 -13
- data/webpack/src/Components/LaunchTask/hooks/useTasksData.js +6 -13
- data/webpack/src/Components/LaunchTask/index.js +9 -27
- data/webpack/src/Components/TaskExecution/ExecutionDetails.js +29 -29
- data/webpack/src/Components/TaskExecution/ExecutionDisplay.js +9 -10
- data/webpack/src/Components/TaskExecution/LoadingIndicator.js +7 -2
- data/webpack/src/Components/TaskExecution/ResultDisplay.js +13 -17
- data/webpack/src/Components/TaskExecution/TaskDetails.js +58 -67
- data/webpack/src/Components/TaskExecution/__tests__/ExecutionDetails.test.js +47 -0
- data/webpack/src/Components/TaskExecution/__tests__/ExecutionDisplay.test.js +29 -0
- data/webpack/src/Components/TaskExecution/__tests__/LoadingIndicator.test.js +25 -0
- data/webpack/src/Components/TaskExecution/__tests__/ResultDisplay.test.js +28 -0
- data/webpack/src/Components/TaskExecution/__tests__/TaskDetails.test.js +38 -0
- data/webpack/src/Components/TaskExecution/__tests__/TaskExecution.test.js +80 -0
- data/webpack/src/Components/TaskExecution/hooks/__tests__/useJobPolling.test.js +177 -0
- data/webpack/src/Components/TaskExecution/hooks/useJobPolling.js +34 -33
- data/webpack/src/Components/TaskExecution/index.js +10 -12
- data/webpack/src/Components/TaskHistory/TaskPopover.js +9 -12
- data/webpack/src/Components/TaskHistory/__tests__/TaskHistory.test.js +109 -0
- data/webpack/src/Components/TaskHistory/__tests__/TaskPopover.test.js +26 -0
- data/webpack/src/Components/TaskHistory/index.js +21 -29
- data/webpack/src/Components/common/HostsPopover.js +12 -3
- data/webpack/src/Components/common/__tests__/HostsPopover.test.js +20 -0
- data/webpack/src/Components/common/__tests__/helpers.test.js +135 -0
- data/webpack/src/Components/common/helpers.js +34 -5
- data/webpack/test_setup.js +34 -11
- metadata +65 -87
- data/test/factories/foreman_openbolt_factories.rb +0 -7
- data/test/unit/foreman_openbolt_test.rb +0 -13
- data/webpack/global_test_setup.js +0 -11
- data/webpack/webpack.config.js +0 -7
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
name: foreman-openbolt-acceptance
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
foreman:
|
|
5
|
+
container_name: foreman-openbolt-test
|
|
6
|
+
hostname: foreman.example.com
|
|
7
|
+
image: ${FOREMAN_IMAGE:-foreman-openbolt:3.18}
|
|
8
|
+
pull_policy: never
|
|
9
|
+
platform: linux/amd64
|
|
10
|
+
privileged: true
|
|
11
|
+
ports:
|
|
12
|
+
- "443:443"
|
|
13
|
+
- "8443:8443"
|
|
14
|
+
entrypoint: /entrypoint.sh
|
|
15
|
+
volumes:
|
|
16
|
+
# Entrypoint script (prepares SSH keys, fixtures, then starts systemd)
|
|
17
|
+
- ./foreman/entrypoint.sh:/entrypoint.sh:ro
|
|
18
|
+
# Plugin RPMs built by rake build:rpm
|
|
19
|
+
- ../../../pkg:/opt/pkg:ro
|
|
20
|
+
# Fixture modules for acceptance tasks
|
|
21
|
+
- ../fixtures/modules:/opt/fixtures/modules:ro
|
|
22
|
+
# SSH private key for bolt to connect to targets
|
|
23
|
+
- ../fixtures/keys/id_rsa:/tmp/ssh/id_rsa:ro
|
|
24
|
+
# OpenBolt plugin config for smart-proxy
|
|
25
|
+
- ../fixtures/openbolt.yml:/etc/foreman-proxy/settings.d/openbolt.yml:ro
|
|
26
|
+
depends_on:
|
|
27
|
+
- target1
|
|
28
|
+
- target2
|
|
29
|
+
tmpfs:
|
|
30
|
+
- /run
|
|
31
|
+
- /tmp:rw,exec,nosuid
|
|
32
|
+
healthcheck:
|
|
33
|
+
test: ["CMD", "curl", "-sk", "https://localhost/api/v2/status"]
|
|
34
|
+
interval: 10s
|
|
35
|
+
timeout: 10s
|
|
36
|
+
retries: 30
|
|
37
|
+
start_period: 60s
|
|
38
|
+
|
|
39
|
+
chrome:
|
|
40
|
+
container_name: foreman-openbolt-chrome
|
|
41
|
+
image: ${SELENIUM_IMAGE:-selenium/standalone-chrome:latest}
|
|
42
|
+
shm_size: 2g
|
|
43
|
+
ports:
|
|
44
|
+
- "4444:4444"
|
|
45
|
+
- "7900:7900"
|
|
46
|
+
depends_on:
|
|
47
|
+
- foreman
|
|
48
|
+
|
|
49
|
+
target1:
|
|
50
|
+
container_name: foreman-openbolt-target1
|
|
51
|
+
hostname: target1.example.com
|
|
52
|
+
image: foreman-openbolt-target
|
|
53
|
+
pull_policy: never
|
|
54
|
+
build:
|
|
55
|
+
context: .
|
|
56
|
+
dockerfile: target/Dockerfile
|
|
57
|
+
volumes:
|
|
58
|
+
- ../fixtures/keys/id_rsa.pub:/tmp/id_rsa.pub:ro
|
|
59
|
+
|
|
60
|
+
target2:
|
|
61
|
+
container_name: foreman-openbolt-target2
|
|
62
|
+
hostname: target2.example.com
|
|
63
|
+
image: foreman-openbolt-target
|
|
64
|
+
pull_policy: never
|
|
65
|
+
build:
|
|
66
|
+
context: .
|
|
67
|
+
dockerfile: target/Dockerfile
|
|
68
|
+
volumes:
|
|
69
|
+
- ../fixtures/keys/id_rsa.pub:/tmp/id_rsa.pub:ro
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Foreman base image for acceptance testing.
|
|
2
|
+
# This Dockerfile only installs packages. foreman-installer is run
|
|
3
|
+
# separately via `docker run --hostname` (see Rakefile) because it
|
|
4
|
+
# requires a valid FQDN and systemd, neither of which are available
|
|
5
|
+
# during `docker build`.
|
|
6
|
+
#
|
|
7
|
+
# The final image is created by committing the container after
|
|
8
|
+
# foreman-installer completes. Plugin RPMs are installed at runtime
|
|
9
|
+
# via setup.sh.
|
|
10
|
+
FROM --platform=linux/amd64 rockylinux:9
|
|
11
|
+
|
|
12
|
+
ARG FOREMAN_VERSION=3.18
|
|
13
|
+
|
|
14
|
+
# Locale
|
|
15
|
+
RUN dnf install -y glibc-langpack-en && dnf clean all
|
|
16
|
+
ENV LANG=en_US.UTF-8
|
|
17
|
+
ENV PATH="$PATH:/opt/puppetlabs/bin:/opt/puppetlabs/server/bin"
|
|
18
|
+
|
|
19
|
+
# Enable module streams
|
|
20
|
+
RUN dnf -y module enable nodejs:22 postgresql:16
|
|
21
|
+
|
|
22
|
+
# Add Foreman and OpenVox repos
|
|
23
|
+
RUN dnf install -y \
|
|
24
|
+
"https://yum.theforeman.org/releases/${FOREMAN_VERSION}/el9/x86_64/foreman-release.rpm" \
|
|
25
|
+
https://yum.voxpupuli.org/openvox8-release-el-9.noarch.rpm && \
|
|
26
|
+
dnf clean all
|
|
27
|
+
|
|
28
|
+
# Install foreman-installer, OpenBolt, and OpenVox (agent + server)
|
|
29
|
+
RUN dnf install -y \
|
|
30
|
+
foreman-installer \
|
|
31
|
+
jq \
|
|
32
|
+
openbolt \
|
|
33
|
+
openssh-clients \
|
|
34
|
+
openvox-agent \
|
|
35
|
+
openvox-server && \
|
|
36
|
+
dnf clean all
|
|
37
|
+
|
|
38
|
+
# Fix NSS for container environment. Rocky 9 defaults to "sss files systemd"
|
|
39
|
+
# but sssd isn't running, causing getpwuid(0) to fail in JRuby/puppetserver.
|
|
40
|
+
RUN sed -i 's/^passwd:.*/passwd: files/' /etc/nsswitch.conf && \
|
|
41
|
+
sed -i 's/^shadow:.*/shadow: files/' /etc/nsswitch.conf && \
|
|
42
|
+
sed -i 's/^group:.*/group: files/' /etc/nsswitch.conf
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
CMD ["/usr/sbin/init"]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Prepare the Foreman container for acceptance testing.
|
|
3
|
+
# This runs before systemd (PID 1) takes over.
|
|
4
|
+
set -e
|
|
5
|
+
|
|
6
|
+
# Proxy log directory
|
|
7
|
+
mkdir -p /var/log/foreman-proxy/openbolt
|
|
8
|
+
chown -R foreman-proxy:foreman-proxy /var/log/foreman-proxy
|
|
9
|
+
|
|
10
|
+
# SSH key for proxy to reach targets
|
|
11
|
+
if [ -f /tmp/ssh/id_rsa ]; then
|
|
12
|
+
mkdir -p /opt/foreman-proxy/.ssh
|
|
13
|
+
chown foreman-proxy:foreman-proxy /opt/foreman-proxy /opt/foreman-proxy/.ssh
|
|
14
|
+
cp /tmp/ssh/id_rsa /opt/foreman-proxy/.ssh/id_rsa
|
|
15
|
+
chown foreman-proxy:foreman-proxy /opt/foreman-proxy/.ssh/id_rsa
|
|
16
|
+
chmod 600 /opt/foreman-proxy/.ssh/id_rsa
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
# Deploy fixture modules for acceptance tasks
|
|
20
|
+
if [ -d /opt/fixtures/modules ]; then
|
|
21
|
+
mkdir -p /etc/puppetlabs/code/environments/production/modules
|
|
22
|
+
cp -r /opt/fixtures/modules/* /etc/puppetlabs/code/environments/production/modules/
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# Hand off to systemd
|
|
26
|
+
exec /usr/sbin/init
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
FROM rockylinux:9
|
|
2
|
+
|
|
3
|
+
ARG OPENVOX_RELEASE=8
|
|
4
|
+
|
|
5
|
+
RUN dnf install -y \
|
|
6
|
+
openssh-server \
|
|
7
|
+
openssh-clients \
|
|
8
|
+
sudo \
|
|
9
|
+
jq \
|
|
10
|
+
https://yum.voxpupuli.org/openvox${OPENVOX_RELEASE}-release-el-9.noarch.rpm && \
|
|
11
|
+
dnf install -y openvox-agent && \
|
|
12
|
+
dnf clean all
|
|
13
|
+
|
|
14
|
+
RUN useradd -m openbolt && \
|
|
15
|
+
echo 'openbolt:openbolt' | chpasswd && \
|
|
16
|
+
usermod -aG wheel openbolt && \
|
|
17
|
+
echo 'openbolt ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/openbolt
|
|
18
|
+
|
|
19
|
+
RUN mkdir -p /home/openbolt/.ssh && \
|
|
20
|
+
chmod 700 /home/openbolt/.ssh && \
|
|
21
|
+
chown -R openbolt:openbolt /home/openbolt
|
|
22
|
+
|
|
23
|
+
RUN ssh-keygen -A
|
|
24
|
+
|
|
25
|
+
COPY target/entrypoint.sh /entrypoint.sh
|
|
26
|
+
RUN chmod +x /entrypoint.sh
|
|
27
|
+
|
|
28
|
+
EXPOSE 22
|
|
29
|
+
ENTRYPOINT ["/entrypoint.sh"]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Copy the bind-mounted public key into the openbolt user's authorized_keys
|
|
3
|
+
# with correct ownership and permissions. sshd refuses keys when the
|
|
4
|
+
# file or parent directory is writable by anyone other than the owner.
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
cp /tmp/id_rsa.pub /home/openbolt/.ssh/authorized_keys
|
|
8
|
+
chown openbolt:openbolt /home/openbolt/.ssh/authorized_keys
|
|
9
|
+
chmod 600 /home/openbolt/.ssh/authorized_keys
|
|
10
|
+
|
|
11
|
+
exec /usr/sbin/sshd -D
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"input_method": "environment",
|
|
3
|
+
"description": "A task with multiple parameter types for testing command building",
|
|
4
|
+
"parameters": {
|
|
5
|
+
"required_string": {
|
|
6
|
+
"type": "String",
|
|
7
|
+
"description": "A required string parameter"
|
|
8
|
+
},
|
|
9
|
+
"optional_string": {
|
|
10
|
+
"type": "Optional[String]",
|
|
11
|
+
"description": "An optional string parameter"
|
|
12
|
+
},
|
|
13
|
+
"array_param": {
|
|
14
|
+
"type": "Array",
|
|
15
|
+
"description": "An array parameter"
|
|
16
|
+
},
|
|
17
|
+
"with_default": {
|
|
18
|
+
"type": "String",
|
|
19
|
+
"description": "A parameter with a default value",
|
|
20
|
+
"default": "default_value"
|
|
21
|
+
},
|
|
22
|
+
"hash_param": {
|
|
23
|
+
"type": "Optional[Hash]",
|
|
24
|
+
"description": "An optional hash parameter"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"implementations": [
|
|
28
|
+
{"name": "complex_params.sh", "requirements": ["shell"]}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Echo back all parameters as JSON so the test can verify they arrived correctly.
|
|
3
|
+
# PT_array_param arrives as a JSON string from bolt, so we pass it through as raw JSON.
|
|
4
|
+
jq -n \
|
|
5
|
+
--arg required "$PT_required_string" \
|
|
6
|
+
--arg optional "$PT_optional_string" \
|
|
7
|
+
--argjson array "${PT_array_param:-null}" \
|
|
8
|
+
--arg default_val "$PT_with_default" \
|
|
9
|
+
--argjson hash "${PT_hash_param:-null}" \
|
|
10
|
+
'{
|
|
11
|
+
required_string: $required,
|
|
12
|
+
optional_string: (if $optional == "" then null else $optional end),
|
|
13
|
+
array_param: $array,
|
|
14
|
+
with_default: $default_val,
|
|
15
|
+
hash_param: $hash
|
|
16
|
+
}'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"input_method": "environment",
|
|
3
|
+
"description": "Echo a message back with the hostname",
|
|
4
|
+
"parameters": {
|
|
5
|
+
"message": {
|
|
6
|
+
"type": "String",
|
|
7
|
+
"description": "The message to echo back"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"implementations": [
|
|
11
|
+
{"name": "echo.sh", "requirements": ["shell"]}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"input_method": "environment",
|
|
3
|
+
"description": "A task that sleeps for a specified number of seconds",
|
|
4
|
+
"parameters": {
|
|
5
|
+
"seconds": {
|
|
6
|
+
"type": "Integer",
|
|
7
|
+
"description": "Number of seconds to sleep",
|
|
8
|
+
"default": 5
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"implementations": [
|
|
12
|
+
{"name": "slow_task.sh", "requirements": ["shell"]}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"input_method": "environment",
|
|
3
|
+
"description": "A task that succeeds on target1 and fails on target2",
|
|
4
|
+
"parameters": {
|
|
5
|
+
"succeed_on": {
|
|
6
|
+
"type": "String",
|
|
7
|
+
"description": "Hostname prefix that should succeed"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"implementations": [
|
|
11
|
+
{"name": "target_conditional.sh", "requirements": ["shell"]}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../acceptance_helper'
|
|
4
|
+
|
|
5
|
+
# Tests that errors are handled gracefully through the full stack.
|
|
6
|
+
class ErrorHandlingTest < AcceptanceTestCase
|
|
7
|
+
def setup
|
|
8
|
+
super
|
|
9
|
+
foreman_login
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def test_launch_button_disabled_with_no_matching_hosts
|
|
13
|
+
visit '/foreman_openbolt/page_launch_task'
|
|
14
|
+
assert_selector '#smart-proxy-input', wait: 15
|
|
15
|
+
select_first_proxy
|
|
16
|
+
select_hosts_via_search('nonexistent.example.com')
|
|
17
|
+
|
|
18
|
+
assert_selector '#task-name-input option', minimum: 2, wait: 15
|
|
19
|
+
select 'acceptance::echo', from: 'task-name-input'
|
|
20
|
+
|
|
21
|
+
assert_selector 'button[type="submit"][disabled]', text: 'Launch Task'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test_failing_task_shows_error_in_result
|
|
25
|
+
launch_task_via_ui('acceptance::failing_task')
|
|
26
|
+
assert_task_failed
|
|
27
|
+
assert_result_contains 'This task always fails'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def test_mixed_target_results_show_per_host_status
|
|
31
|
+
launch_task_via_ui('acceptance::target_conditional',
|
|
32
|
+
params: { 'succeed_on' => 'target1' })
|
|
33
|
+
|
|
34
|
+
assert_task_failed
|
|
35
|
+
assert_result_has_content
|
|
36
|
+
# Result should contain output from both targets
|
|
37
|
+
assert_result_contains 'target1'
|
|
38
|
+
assert_result_contains 'target2'
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../acceptance_helper'
|
|
4
|
+
|
|
5
|
+
# Tests for HostSelector-specific affordances: the search query chip
|
|
6
|
+
# and the "Clear all target selections" link.
|
|
7
|
+
class HostSelectorTest < AcceptanceTestCase
|
|
8
|
+
def setup
|
|
9
|
+
super
|
|
10
|
+
foreman_login
|
|
11
|
+
visit '/foreman_openbolt/page_launch_task'
|
|
12
|
+
assert_selector '#smart-proxy-input', wait: 15
|
|
13
|
+
select_first_proxy
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_clear_chips_link_empties_targets_and_removes_chip
|
|
17
|
+
select_hosts_via_search('target1')
|
|
18
|
+
|
|
19
|
+
# The Targets label is conditional on targets.length > 0
|
|
20
|
+
# (LaunchTask/index.js:230).
|
|
21
|
+
assert_selector '.pf-v5-c-label', text: /Targets:\s*\d+/, wait: 15
|
|
22
|
+
|
|
23
|
+
# Click the "Clear all target selections" button (ouiaId=clear-chips).
|
|
24
|
+
find('[data-ouia-component-id="clear-chips"]').click
|
|
25
|
+
|
|
26
|
+
# Targets label disappears and the clear-chips button itself
|
|
27
|
+
# unmounts once there are no selections.
|
|
28
|
+
assert_no_selector '.pf-v5-c-label', text: /Targets:/, wait: 10
|
|
29
|
+
assert_no_selector '[data-ouia-component-id="clear-chips"]', wait: 5
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../acceptance_helper'
|
|
4
|
+
|
|
5
|
+
# Tests launching various task types and verifying their execution results.
|
|
6
|
+
class LaunchTaskTest < AcceptanceTestCase
|
|
7
|
+
def setup
|
|
8
|
+
super
|
|
9
|
+
foreman_login
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def test_echo_task_succeeds_on_all_targets
|
|
13
|
+
launch_task_via_ui('acceptance::echo',
|
|
14
|
+
params: { 'message' => 'hello from acceptance test' })
|
|
15
|
+
|
|
16
|
+
assert_task_completed
|
|
17
|
+
assert_result_has_content
|
|
18
|
+
assert_result_contains 'hello from acceptance test'
|
|
19
|
+
assert_result_contains 'hostname'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_noop_task_succeeds
|
|
23
|
+
launch_task_via_ui('acceptance::noop_task')
|
|
24
|
+
assert_task_completed
|
|
25
|
+
assert_result_has_content
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_complex_params_task_succeeds
|
|
29
|
+
launch_task_via_ui('acceptance::complex_params',
|
|
30
|
+
params: {
|
|
31
|
+
'required_string' => 'test_value',
|
|
32
|
+
'array_param' => '["a","b","c"]',
|
|
33
|
+
'with_default' => 'overridden',
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
assert_task_completed
|
|
37
|
+
assert_result_has_content
|
|
38
|
+
assert_result_contains 'test_value'
|
|
39
|
+
assert_result_contains 'overridden'
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_slow_task_transitions_through_running
|
|
43
|
+
launch_task_via_ui('acceptance::slow_task', params: { 'seconds' => '8' })
|
|
44
|
+
assert_selector '.pf-v5-c-label', text: /Running/i, wait: 30
|
|
45
|
+
assert_task_completed
|
|
46
|
+
assert_result_has_content
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def test_failing_task_shows_failure_with_error_detail
|
|
50
|
+
launch_task_via_ui('acceptance::failing_task')
|
|
51
|
+
assert_task_failed
|
|
52
|
+
assert_result_contains 'This task always fails'
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def test_run_another_task_navigates_back
|
|
56
|
+
launch_task_via_ui('acceptance::noop_task')
|
|
57
|
+
assert_task_completed
|
|
58
|
+
click_button 'Run Another Task'
|
|
59
|
+
assert_selector 'h1', text: 'Launch OpenBolt Task', wait: 15
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def test_launch_button_disabled_until_all_selections_made
|
|
63
|
+
visit '/foreman_openbolt/page_launch_task'
|
|
64
|
+
assert_selector '#smart-proxy-input', wait: 15
|
|
65
|
+
|
|
66
|
+
# No selections at all
|
|
67
|
+
assert find('button', text: /Launch Task/).disabled?,
|
|
68
|
+
'Expected Launch Task disabled with no selections'
|
|
69
|
+
|
|
70
|
+
# Proxy only
|
|
71
|
+
select_first_proxy
|
|
72
|
+
assert_selector '#task-name-input option', minimum: 2, wait: 15
|
|
73
|
+
assert find('button', text: /Launch Task/).disabled?,
|
|
74
|
+
'Expected Launch Task disabled with only proxy selected'
|
|
75
|
+
|
|
76
|
+
# Proxy + task, no targets
|
|
77
|
+
select 'acceptance::noop_task', from: 'task-name-input'
|
|
78
|
+
assert find('button', text: /Launch Task/).disabled?,
|
|
79
|
+
'Expected Launch Task disabled with no targets'
|
|
80
|
+
|
|
81
|
+
# Proxy + task + targets — button enables
|
|
82
|
+
select_hosts_via_search('target1')
|
|
83
|
+
assert_selector 'button:not([disabled])', text: /Launch Task/, wait: 10
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_running_task_shows_loading_indicator
|
|
87
|
+
launch_task_via_ui('acceptance::slow_task', params: { 'seconds' => '8' })
|
|
88
|
+
# While the job is still polling, LoadingIndicator renders an EmptyState
|
|
89
|
+
# with role=status and a title of "Task is <status>..." (running or pending).
|
|
90
|
+
assert_selector '[role="status"]',
|
|
91
|
+
text: /Task is (running|pending)/i, wait: 30
|
|
92
|
+
assert_selector '.pf-v5-c-empty-state__body',
|
|
93
|
+
text: /update automatically when the task completes/, wait: 5
|
|
94
|
+
assert_task_completed
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../acceptance_helper'
|
|
4
|
+
|
|
5
|
+
# Tests the parameter table rendering logic: required indicator
|
|
6
|
+
# based on the Optional[...] type prefix, and the expandable row
|
|
7
|
+
# that reveals type and description for each parameter.
|
|
8
|
+
class ParameterTableTest < AcceptanceTestCase
|
|
9
|
+
def setup
|
|
10
|
+
super
|
|
11
|
+
foreman_login
|
|
12
|
+
visit '/foreman_openbolt/page_launch_task'
|
|
13
|
+
assert_selector '#smart-proxy-input', wait: 15
|
|
14
|
+
select_first_proxy
|
|
15
|
+
assert_selector '#task-name-input option', minimum: 2, wait: 15
|
|
16
|
+
select 'acceptance::complex_params', from: 'task-name-input'
|
|
17
|
+
assert_selector '#param_required_string', wait: 10
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_required_param_shows_required_indicator
|
|
21
|
+
# ParametersSection flags params whose type does not start with
|
|
22
|
+
# "optional" as required, and FieldTable renders a span with
|
|
23
|
+
# role=img and aria-label="Required" in those rows.
|
|
24
|
+
within(:xpath, "//tr[.//input[@id='param_required_string']]") do
|
|
25
|
+
assert_selector 'span[role="img"][aria-label="Required"]'
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_optional_param_has_no_required_indicator
|
|
30
|
+
within(:xpath, "//tr[.//input[@id='param_optional_string']]") do
|
|
31
|
+
assert_no_selector 'span[role="img"][aria-label="Required"]'
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_expanding_required_param_row_shows_type_description_and_required_warning
|
|
36
|
+
within(:xpath, "//tr[.//input[@id='param_required_string']]") do
|
|
37
|
+
first('button').click
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# The expanded row is a sibling in the same Tbody. Assertions on
|
|
41
|
+
# the page level since the ExpandableRowContent is outside the
|
|
42
|
+
# trigger row's scope.
|
|
43
|
+
assert_selector '.pf-v5-c-helper-text__item',
|
|
44
|
+
text: 'This field is required', wait: 5
|
|
45
|
+
assert_selector '.pf-v5-c-helper-text__item code', text: 'String'
|
|
46
|
+
assert_selector '.pf-v5-c-helper-text__item',
|
|
47
|
+
text: 'A required string parameter'
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_expanding_optional_param_row_shows_type_and_description_without_required_warning
|
|
51
|
+
within(:xpath, "//tr[.//input[@id='param_optional_string']]") do
|
|
52
|
+
first('button').click
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
assert_selector '.pf-v5-c-helper-text__item code', text: 'Optional[String]'
|
|
56
|
+
assert_selector '.pf-v5-c-helper-text__item',
|
|
57
|
+
text: 'An optional string parameter'
|
|
58
|
+
assert_no_selector '.pf-v5-c-helper-text__item',
|
|
59
|
+
text: 'This field is required'
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../acceptance_helper'
|
|
4
|
+
|
|
5
|
+
# Tests that Foreman settings correctly populate the OpenBolt options UI
|
|
6
|
+
# and that changes to settings are reflected in the plugin's behavior.
|
|
7
|
+
# This exercises the settings lookup bug fix (Foreman.settings vs Setting.where).
|
|
8
|
+
class SettingsTest < AcceptanceTestCase
|
|
9
|
+
def setup
|
|
10
|
+
super
|
|
11
|
+
foreman_login
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_openbolt_user_setting_populates_default
|
|
15
|
+
visit '/foreman_openbolt/page_launch_task'
|
|
16
|
+
assert_selector '#smart-proxy-input', wait: 15
|
|
17
|
+
select_first_proxy
|
|
18
|
+
|
|
19
|
+
# The user option should show the value configured during acceptance:up
|
|
20
|
+
assert_selector '#param_user', wait: 10
|
|
21
|
+
assert_equal 'openbolt', find_by_id('param_user').value
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# The string the UI shows in an encrypted-default field so the user can tell
|
|
25
|
+
# a saved value exists without exposing it to the browser. Kept in sync with
|
|
26
|
+
# ENCRYPTED_PLACEHOLDER in task_controller.rb and ENCRYPTED_DEFAULT_PLACEHOLDER
|
|
27
|
+
# in webpack/src/Components/common/constants.js.
|
|
28
|
+
ENCRYPTED_DEFAULT_PLACEHOLDER = '[Use saved encrypted default]'
|
|
29
|
+
|
|
30
|
+
def test_encrypted_setting_shows_placeholder_and_is_overrideable
|
|
31
|
+
# Scope the saved password to this test so it does not leak into other
|
|
32
|
+
# task launches (merge_encrypted_defaults would inject it into bolt).
|
|
33
|
+
update_foreman_setting('openbolt_password', 'acceptance-test-password')
|
|
34
|
+
begin
|
|
35
|
+
visit '/foreman_openbolt/page_launch_task'
|
|
36
|
+
assert_selector '#smart-proxy-input', wait: 15
|
|
37
|
+
select_first_proxy
|
|
38
|
+
|
|
39
|
+
# With a saved encrypted setting, the field prefills with the placeholder
|
|
40
|
+
# string so the user knows the saved value will be used. The real value
|
|
41
|
+
# is never sent to the browser.
|
|
42
|
+
field = find_by_id('param_password', wait: 10)
|
|
43
|
+
assert_equal 'password', field['type']
|
|
44
|
+
assert_equal ENCRYPTED_DEFAULT_PLACEHOLDER, field.value.to_s
|
|
45
|
+
|
|
46
|
+
# Expanding the row reveals a warning that an encrypted default is saved.
|
|
47
|
+
# The FieldTable renders an expand toggle in the first cell of each row;
|
|
48
|
+
# click the first button within the row containing our field.
|
|
49
|
+
within(:xpath, "//tr[.//input[@id='param_password']]") do
|
|
50
|
+
first('button').click
|
|
51
|
+
end
|
|
52
|
+
assert_selector '.pf-v5-c-helper-text__item',
|
|
53
|
+
text: /saved, encrypted default/i, wait: 10
|
|
54
|
+
|
|
55
|
+
# Typing a new value must replace the placeholder so the task uses
|
|
56
|
+
# what the user entered instead of the saved default.
|
|
57
|
+
field.fill_in with: 'override-value'
|
|
58
|
+
assert_equal 'override-value', field.value.to_s
|
|
59
|
+
ensure
|
|
60
|
+
update_foreman_setting('openbolt_password', '')
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_changing_user_setting_updates_launch_page
|
|
65
|
+
# Change the setting through the Foreman settings UI
|
|
66
|
+
update_foreman_setting('openbolt_user', 'newuser')
|
|
67
|
+
|
|
68
|
+
# Verify the launch page reflects the change
|
|
69
|
+
visit '/foreman_openbolt/page_launch_task'
|
|
70
|
+
assert_selector '#smart-proxy-input', wait: 15
|
|
71
|
+
select_first_proxy
|
|
72
|
+
assert_selector '#param_user', wait: 10
|
|
73
|
+
assert_equal 'newuser', find_by_id('param_user').value
|
|
74
|
+
|
|
75
|
+
# Restore the original value
|
|
76
|
+
update_foreman_setting('openbolt_user', 'openbolt')
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def test_host_key_check_setting_flows_through_to_checkbox_state
|
|
80
|
+
visit '/foreman_openbolt/page_launch_task'
|
|
81
|
+
assert_selector '#smart-proxy-input', wait: 15
|
|
82
|
+
select_first_proxy
|
|
83
|
+
|
|
84
|
+
# openbolt_host-key-check is a boolean Foreman setting; the
|
|
85
|
+
# acceptance fixture (rakelib/acceptance.rake) sets it to false so
|
|
86
|
+
# SSH does not reject the ephemeral target containers. The registered
|
|
87
|
+
# default in engine.rb is true, so if the Foreman setting lookup
|
|
88
|
+
# were broken, the checkbox would render checked. Asserting
|
|
89
|
+
# unchecked here proves the configured value (false) flowed through.
|
|
90
|
+
field = find_by_id('param_host-key-check', wait: 10)
|
|
91
|
+
assert_equal 'checkbox', field['type']
|
|
92
|
+
refute field.checked?,
|
|
93
|
+
'Expected host-key-check to be unchecked, matching the acceptance fixture value'
|
|
94
|
+
end
|
|
95
|
+
end
|