foreman_openbolt 1.1.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -15
  3. data/Rakefile +0 -0
  4. data/lib/foreman_openbolt/version.rb +1 -1
  5. data/package.json +1 -1
  6. data/test/acceptance/acceptance_helper.rb +146 -0
  7. data/test/acceptance/docker/docker-compose.yml +69 -0
  8. data/test/acceptance/docker/foreman/Dockerfile +45 -0
  9. data/test/acceptance/docker/foreman/entrypoint.sh +26 -0
  10. data/test/acceptance/docker/target/Dockerfile +29 -0
  11. data/test/acceptance/docker/target/entrypoint.sh +11 -0
  12. data/test/acceptance/fixtures/modules/acceptance/tasks/complex_params.json +30 -0
  13. data/test/acceptance/fixtures/modules/acceptance/tasks/complex_params.sh +16 -0
  14. data/test/acceptance/fixtures/modules/acceptance/tasks/echo.json +13 -0
  15. data/test/acceptance/fixtures/modules/acceptance/tasks/echo.sh +3 -0
  16. data/test/acceptance/fixtures/modules/acceptance/tasks/failing_task.json +8 -0
  17. data/test/acceptance/fixtures/modules/acceptance/tasks/failing_task.sh +3 -0
  18. data/test/acceptance/fixtures/modules/acceptance/tasks/noop_task.json +8 -0
  19. data/test/acceptance/fixtures/modules/acceptance/tasks/noop_task.sh +2 -0
  20. data/test/acceptance/fixtures/modules/acceptance/tasks/slow_task.json +14 -0
  21. data/test/acceptance/fixtures/modules/acceptance/tasks/slow_task.sh +3 -0
  22. data/test/acceptance/fixtures/modules/acceptance/tasks/target_conditional.json +13 -0
  23. data/test/acceptance/fixtures/modules/acceptance/tasks/target_conditional.sh +9 -0
  24. data/test/acceptance/fixtures/openbolt.yml +7 -0
  25. data/test/acceptance/tests/error_handling_test.rb +40 -0
  26. data/test/acceptance/tests/host_selector_test.rb +31 -0
  27. data/test/acceptance/tests/launch_task_test.rb +96 -0
  28. data/test/acceptance/tests/parameter_table_test.rb +61 -0
  29. data/test/acceptance/tests/settings_test.rb +95 -0
  30. data/test/acceptance/tests/ssh_options_test.rb +77 -0
  31. data/test/acceptance/tests/task_execution_test.rb +40 -0
  32. data/test/acceptance/tests/task_history_test.rb +84 -0
  33. data/test/acceptance/tests/transport_options_test.rb +121 -0
  34. data/test/test_plugin_helper.rb +17 -0
  35. data/test/unit/controllers/task_controller_test.rb +351 -0
  36. data/test/unit/docker/Dockerfile +47 -0
  37. data/test/unit/docker/docker-compose.yml +33 -0
  38. data/test/unit/docker/entrypoint.sh +4 -0
  39. data/test/unit/factories/foreman_openbolt_factories.rb +39 -0
  40. data/test/unit/lib/actions/cleanup_proxy_artifacts_test.rb +51 -0
  41. data/test/unit/lib/actions/poll_task_status_test.rb +141 -0
  42. data/test/unit/lib/proxy_api/openbolt_test.rb +174 -0
  43. data/test/unit/models/task_job_test.rb +278 -0
  44. metadata +39 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 19c30e74d249eacf814179dac70292c3e42182b39477cab06adf304885298587
4
- data.tar.gz: bbc28cd01be2a7f100d1ec015c92aff471584e7445468184e7ef1bd97c485b27
3
+ metadata.gz: 3747d57bc5198327d01255ec665724e38a46dbfd91fd1e5bd764d6cf3ddd3bb3
4
+ data.tar.gz: ad67d388a524680a567ffdf020e35bbb364471bd190cbdfc597c25d88928a4d2
5
5
  SHA512:
6
- metadata.gz: 2b40018885e22e7d3690b13c524787dadbf8ad2fe8fa5af2e82e5eb4fd99d65a56a7015cbd02717573c5cd12395d1a157130c16257fa94a5b3f28ef78470fd55
7
- data.tar.gz: 753476abd475f3de15ad928874048c9772d4a55534129f35b9d4c032beb5c0f787e8ba6aed271763bb59d66383986360cbf8d0438618aefdb9f2175790b959c7
6
+ metadata.gz: 1570a5623355cd514276d9e25aece89f2b3f5970bde5efa80db4f9cbe1c4e59eb030b4b5194906d9e9b93f6ec62ac3059e9ed1671cfd612b025d805e3cff7f8e
7
+ data.tar.gz: b335d7b62ff7c6a6d93e39d2ae12c8357ca32b36299313ecc28a23ecc9190d74016a60f03e61f8bacbbf2ba7583cfb15b96b31acf5c21590d59dafc70934fb84
data/README.md CHANGED
@@ -252,7 +252,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
252
252
 
253
253
  ### Version locations
254
254
 
255
- Update the version in these files:
255
+ The version is maintained in two files:
256
256
 
257
257
  1. `lib/foreman_openbolt/version.rb` -- the gem version (authoritative source)
258
258
  2. `package.json` -- the npm package version (must match)
@@ -260,23 +260,17 @@ Update the version in these files:
260
260
  If the minimum Foreman version changes, also update:
261
261
 
262
262
  3. `lib/foreman_openbolt/engine.rb` -- `requires_foreman '>= X.Y.Z'`
263
- 4. `foreman_openbolt.spec.erb` -- `%global foreman_min_version X.Y.Z`
264
- 5. `.github/workflows/build.yml` -- default `foreman_version` and `foreman_packaging_ref` inputs
263
+ 4. `.github/workflows/build.yml` -- default `foreman_version` and `foreman_packaging_ref` inputs
265
264
 
266
265
  ### Release steps
267
266
 
268
- 1. Bump the version in the two files listed above
269
- 2. Generate the changelog:
270
- ```bash
271
- CHANGELOG_GITHUB_TOKEN=github_pat_... bundle exec rake changelog
272
- ```
273
- 3. Create a PR with the version bump and changelog, get it reviewed and merged
274
- 4. Create and push a tag matching the version:
275
- ```bash
276
- git tag 1.1.0
277
- git push origin 1.1.0
278
- ```
279
- 5. The [release workflow](.github/workflows/release.yml) runs automatically on tag push and:
267
+ 1. Go to [Actions > Prepare Release](../../actions/workflows/prepare_release.yml) and run the workflow with the version to release (e.g. `1.2.0`)
268
+ 2. The workflow bumps the version in `version.rb` and `package.json`, generates the changelog, and opens a PR with the `skip-changelog` label
269
+ 3. Review and merge the PR
270
+ 4. Go to [Actions > Release](../../actions/workflows/release.yml) and run the workflow with the same version
271
+ 5. The release workflow:
272
+ - Verifies the version in `version.rb` matches the input
273
+ - Creates and pushes a git tag
280
274
  - Builds the gem
281
275
  - Creates a GitHub Release with auto-generated notes and the gem attached
282
276
  - Publishes the gem to GitHub Packages
data/Rakefile CHANGED
File without changes
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ForemanOpenbolt
4
- VERSION = '1.1.0'
4
+ VERSION = '1.1.1'
5
5
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foreman_openbolt",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "OpenBolt integration into Foreman",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capybara'
4
+ require 'capybara/dsl'
5
+ require 'selenium-webdriver'
6
+ require 'test/unit'
7
+
8
+ # Base class for acceptance tests using Capybara + Selenium Chrome.
9
+ # Connects to Foreman through a remote ChromeDriver container and
10
+ # exercises the plugin UI as a real user would.
11
+ class AcceptanceTestCase < Test::Unit::TestCase
12
+ include Capybara::DSL
13
+
14
+ # Chrome runs in a separate container and reaches Foreman via the Docker
15
+ # network service name. The test runner connects to ChromeDriver via the
16
+ # exposed port 4444 on the host.
17
+ FOREMAN_URL = ENV.fetch('FOREMAN_URL', 'https://foreman')
18
+ FOREMAN_USER = ENV.fetch('FOREMAN_USER', 'admin')
19
+ FOREMAN_PASS = ENV.fetch('FOREMAN_PASS', 'changeme')
20
+ CHROMEDRIVER_URL = ENV.fetch('CHROMEDRIVER_URL', 'http://localhost:4444')
21
+
22
+ def setup
23
+ Capybara.app_host = FOREMAN_URL
24
+ Capybara.run_server = false
25
+ Capybara.default_max_wait_time = 15
26
+
27
+ Capybara.register_driver :remote_chrome do |app|
28
+ options = Selenium::WebDriver::Chrome::Options.new
29
+ options.add_argument('--headless') unless ENV['HEADFUL']
30
+ options.add_argument('--no-sandbox')
31
+ options.add_argument('--disable-dev-shm-usage')
32
+ options.add_argument('--disable-gpu')
33
+ options.add_argument('--window-size=1280,720')
34
+ options.add_argument('--ignore-certificate-errors')
35
+
36
+ Capybara::Selenium::Driver.new(
37
+ app,
38
+ browser: :remote,
39
+ url: CHROMEDRIVER_URL,
40
+ options: options
41
+ )
42
+ end
43
+
44
+ Capybara.default_driver = :remote_chrome
45
+ Capybara.javascript_driver = :remote_chrome
46
+ end
47
+
48
+ def teardown
49
+ visit '/users/logout'
50
+ Capybara.reset_sessions!
51
+ end
52
+
53
+ def foreman_login(user: FOREMAN_USER, password: FOREMAN_PASS)
54
+ visit '/users/login'
55
+ fill_in 'login_login', with: user
56
+ fill_in 'login_password', with: password
57
+ click_button 'Log In'
58
+ end
59
+
60
+ # --- Launch page helpers ---
61
+
62
+ def select_first_proxy
63
+ assert_selector '#smart-proxy-input option', minimum: 2, wait: 15
64
+ proxy_option = first('#smart-proxy-input option:not([value=""])')
65
+ select proxy_option.text, from: 'smart-proxy-input'
66
+ end
67
+
68
+ def select_hosts_via_search(query)
69
+ find('[aria-label="Select host targeting method"]').click
70
+ find('[data-ouia-component-id="host_methods"]').find('li', text: 'Search query').click
71
+ search_input = find('.foreman-search-field input[type="text"]', wait: 10)
72
+ search_input.fill_in with: query
73
+ end
74
+
75
+ def launch_task_via_ui(task_name, targets: 'target', params: {})
76
+ visit '/foreman_openbolt/page_launch_task'
77
+ assert_selector '#smart-proxy-input', wait: 15
78
+
79
+ select_first_proxy
80
+ select_hosts_via_search(targets)
81
+
82
+ assert_selector '#task-name-input option', minimum: 2, wait: 15
83
+ select task_name, from: 'task-name-input'
84
+
85
+ params.each do |name, value|
86
+ assert_selector "#param_#{name}", wait: 10
87
+ fill_in "param_#{name}", with: value
88
+ end
89
+
90
+ click_button 'Launch Task'
91
+ assert_selector 'h1', text: 'Task Execution', wait: 15
92
+ end
93
+
94
+ def assert_task_completed
95
+ assert_selector '.pf-v5-c-label', text: /Success|Complete/i, wait: 120
96
+ end
97
+
98
+ def assert_task_failed
99
+ assert_selector '.pf-v5-c-label', text: /Failed|Failure|Error/i, wait: 120
100
+ end
101
+
102
+ def assert_result_contains(text)
103
+ assert_selector '.pf-v5-c-code-block__code', text: text, wait: 15
104
+ end
105
+
106
+ def assert_result_has_content
107
+ assert_no_selector '.pf-v5-c-empty-state', text: 'No result data', wait: 15
108
+ assert_selector '.pf-v5-c-code-block__code', wait: 15
109
+ end
110
+
111
+ def assert_log_contains(text)
112
+ # Click the "Log Output" tab to see the bolt command and log
113
+ find('.pf-v5-c-tabs__link', text: 'Log Output').click
114
+ assert_selector '.pf-v5-c-code-block__code', text: text, wait: 15
115
+ end
116
+
117
+ # OpenBolt options and task parameters both use param_ prefix for field IDs
118
+ def set_openbolt_option(name, value)
119
+ field = find("#param_#{name}", wait: 10)
120
+ if field.tag_name == 'select'
121
+ field.select value
122
+ elsif field['type'] == 'checkbox'
123
+ value ? field.check : field.uncheck
124
+ else
125
+ field.fill_in with: value
126
+ end
127
+ end
128
+
129
+ # --- Settings page helpers ---
130
+
131
+ # Update a Foreman setting through the /settings UI. Each setting row
132
+ # has an inline edit button with id=<setting_name> that reveals an
133
+ # input with id=setting-input-<setting_name> and a submit button with
134
+ # ouiaId=submit-edit-btn (see Foreman's SettingValueCell /
135
+ # SettingValueEdit components).
136
+ def update_foreman_setting(setting_name, new_value, category: 'openbolt')
137
+ visit '/settings'
138
+ find("a[href='##{category}_settings_tab']", wait: 10).click
139
+
140
+ within("##{category}_settings_tab", wait: 10) do
141
+ find("button##{setting_name}", wait: 10).click
142
+ find("#setting-input-#{setting_name}", wait: 10).fill_in with: new_value
143
+ find("[data-ouia-component-id='submit-edit-btn']").click
144
+ end
145
+ end
146
+ end
@@ -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,3 @@
1
+ #!/bin/bash
2
+ jq -n --arg msg "$PT_message" --arg host "$(hostname)" \
3
+ '{"message": $msg, "hostname": $host}'
@@ -0,0 +1,8 @@
1
+ {
2
+ "input_method": "environment",
3
+ "description": "A task that always fails",
4
+ "parameters": {},
5
+ "implementations": [
6
+ {"name": "failing_task.sh", "requirements": ["shell"]}
7
+ ]
8
+ }
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+ echo '{"status": "failure", "error": "This task always fails"}' >&2
3
+ exit 1
@@ -0,0 +1,8 @@
1
+ {
2
+ "input_method": "environment",
3
+ "description": "A no-op task that returns a status",
4
+ "parameters": {},
5
+ "implementations": [
6
+ {"name": "noop_task.sh", "requirements": ["shell"]}
7
+ ]
8
+ }
@@ -0,0 +1,2 @@
1
+ #!/bin/bash
2
+ echo '{"status": "ok"}'
@@ -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,3 @@
1
+ #!/bin/bash
2
+ sleep "${PT_seconds:-5}"
3
+ echo "{\"status\": \"ok\", \"slept\": ${PT_seconds:-5}}"
@@ -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,9 @@
1
+ #!/bin/bash
2
+ hostname=$(hostname)
3
+ if [[ "$hostname" == ${PT_succeed_on}* ]]; then
4
+ echo "{\"status\": \"success\", \"hostname\": \"$hostname\"}"
5
+ exit 0
6
+ else
7
+ echo "{\"status\": \"failure\", \"hostname\": \"$hostname\"}" >&2
8
+ exit 1
9
+ fi
@@ -0,0 +1,7 @@
1
+ ---
2
+ :enabled: https
3
+ :environment_path: /etc/puppetlabs/code/environments/production
4
+ :workers: 5
5
+ :concurrency: 20
6
+ :connect_timeout: 30
7
+ :log_dir: /var/log/foreman-proxy/openbolt
@@ -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