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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3747d57bc5198327d01255ec665724e38a46dbfd91fd1e5bd764d6cf3ddd3bb3
|
|
4
|
+
data.tar.gz: ad67d388a524680a567ffdf020e35bbb364471bd190cbdfc597c25d88928a4d2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1570a5623355cd514276d9e25aece89f2b3f5970bde5efa80db4f9cbe1c4e59eb030b4b5194906d9e9b93f6ec62ac3059e9ed1671cfd612b025d805e3cff7f8e
|
|
7
|
+
data.tar.gz: b335d7b62ff7c6a6d93e39d2ae12c8357ca32b36299313ecc28a23ecc9190d74016a60f03e61f8bacbbf2ba7583cfb15b96b31acf5c21590d59dafc70934fb84
|
data/README.md
CHANGED
|
@@ -10,8 +10,8 @@ Bringing OpenBolt Task & Plans into Foreman!
|
|
|
10
10
|
|
|
11
11
|
## Introduction
|
|
12
12
|
|
|
13
|
-
[OpenBolt](https://github.com/OpenVoxProject/openbolt) is the open source successor of [
|
|
14
|
-
OpenBolt supports running Tasks or Plans against various targets
|
|
13
|
+
[OpenBolt](https://github.com/OpenVoxProject/openbolt) is the open source successor of [Bolt](https://github.com/puppetlabs/bolt) by [Perforce](https://www.perforce.com/).
|
|
14
|
+
OpenBolt supports running Tasks or Plans against various targets via different transport protocols.
|
|
15
15
|
OpenBolt and Bolt are CLI-only tools.
|
|
16
16
|
They connect to the targets from a central location (usually a jumpnode or workstation).
|
|
17
17
|
|
|
@@ -45,21 +45,17 @@ See [How_to_Install_a_Plugin](https://theforeman.org/plugins/#2.Installation) fo
|
|
|
45
45
|
The [theforeman/foreman](https://github.com/theforeman/puppet-foreman/blob/master/manifests/plugin/openbolt.pp) puppet module also supports the **Foreman plugin** installation.
|
|
46
46
|
The [theforeman/foreman_proxy](https://github.com/theforeman/puppet-foreman_proxy/blob/master/manifests/plugin/openbolt.pp) puppet module also supports the **Foreman Smartproxy plugin** installation.
|
|
47
47
|
|
|
48
|
-
**as of 2025-12-15 integration into the foreman installer is still pending**
|
|
49
48
|
|
|
50
49
|
The Foreman plugin provides UI elements to start Tasks on various nodes.
|
|
51
50
|
Foreman then talks to a Smartproxy to run OpenBolt.
|
|
52
51
|
The Smartproxy also establishes the connections to the various targets.
|
|
53
|
-
This is usually a ssh or WinRM connection (and soon choria, see [the TODO section](#todo).
|
|
52
|
+
This is usually a ssh or WinRM connection (and soon choria, see [the TODO section](#todo)).
|
|
54
53
|
|
|
55
54
|
You need to have `bolt` in your `$PATH` on the Smartproxy.
|
|
56
|
-
You can use the legacy bolt packages from Perforce from the `puppet-tools` repo on [apt.puppet.com](https://apt.puppet.com/) or [yum.puppet.com](https://yum.puppet.com/).
|
|
57
|
-
If you have an active Perforce license, you can also download [their commercial bolt version](https://help.puppet.com/bolt/current/topics/bolt_installing.htm).
|
|
58
55
|
OpenBolt packages are available at [yum.voxpupuli.org](https://yum.voxpupuli.org/) & [apt.voxpupuli.org](https://apt.voxpupuli.org/) in the openvox8 repo.
|
|
56
|
+
You can also use the legacy Bolt packages from Perforce from the `puppet-tools` repo on [apt.puppet.com](https://apt.puppet.com/) or [yum.puppet.com](https://yum.puppet.com/).
|
|
59
57
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
The integration is supported on Foreman 3.15 and all following versions, including development/nightly builds.
|
|
58
|
+
The integration is supported on Foreman 3.17 and all following versions, including development/nightly builds.
|
|
63
59
|
|
|
64
60
|
OpenBolt relies on Tasks & Plans. They are distributed as puppet modules.
|
|
65
61
|
The plugin assumes that you deployed your code.
|
|
@@ -115,11 +111,118 @@ For failed tasks but also for passed tasks.
|
|
|
115
111
|
|
|
116
112
|

|
|
117
113
|
|
|
114
|
+
## Development
|
|
115
|
+
|
|
116
|
+
### Linting
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
bundle exec rake lint # Run all linters (rubocop, erb_lint, eslint)
|
|
120
|
+
bundle exec rake lint:fix # Auto-fix where possible
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Ruby and ERB linters run directly. The JavaScript linter requires npm dependencies, so either install them locally (`npm install --legacy-peer-deps`) or run lint:js inside a container:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
CONTAINER=1 bundle exec rake lint:js
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Unit Tests
|
|
130
|
+
|
|
131
|
+
Unit tests run inside Docker containers with a full Foreman installation. Requires Docker with compose support.
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
bundle exec rake test:unit:up # Build image, start containers, install deps
|
|
135
|
+
bundle exec rake test:unit:ruby # Run Ruby tests
|
|
136
|
+
bundle exec rake test:unit:js # Run JavaScript tests
|
|
137
|
+
bundle exec rake test:unit:all # Run all unit tests
|
|
138
|
+
bundle exec rake test:unit:down # Stop and remove containers
|
|
139
|
+
bundle exec rake test # Shortcut: up, test, down in one step
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Set `FOREMAN_VERSION` to test against a specific Foreman version (default: `3.18`):
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
FOREMAN_VERSION=3.17 bundle exec rake test:unit:up
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Acceptance Tests
|
|
149
|
+
|
|
150
|
+
Acceptance tests exercise the plugin through the browser using Capybara and Selenium. They build RPMs, start a multi-container environment (Foreman + OpenVox + SSH targets + Chromium), and run tests against the real UI.
|
|
151
|
+
|
|
152
|
+
**Prerequisites:**
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
bundle install --with acceptance
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The [smart_proxy_openbolt](https://github.com/overlookinfra/smart_proxy_openbolt) and [foreman-packaging](https://github.com/theforeman/foreman-packaging) repos are cloned automatically when needed.
|
|
159
|
+
|
|
160
|
+
**Running:**
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
bundle exec rake acceptance # Full cycle: up, run tests, down
|
|
164
|
+
bundle exec rake acceptance:up # Build RPMs, start Foreman, configure everything
|
|
165
|
+
bundle exec rake acceptance:run # Run tests (requires up first)
|
|
166
|
+
bundle exec rake acceptance:down # Stop containers
|
|
167
|
+
bundle exec rake acceptance:clean # Full reset: stop containers, remove images and artifacts
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The `acceptance:up` task is idempotent and can be re-run to pick up new RPM changes. It caches the Foreman Docker image per version so subsequent runs are faster.
|
|
171
|
+
|
|
172
|
+
**Watching tests in the browser:**
|
|
173
|
+
|
|
174
|
+
Set `HEADFUL=1` to disable headless mode, then open `http://localhost:7900` (password: `secret`) to watch the tests via noVNC:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
HEADFUL=1 bundle exec rake acceptance:run
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Running a subset of tests:**
|
|
181
|
+
|
|
182
|
+
`acceptance:run` accepts `TEST=<path>` to limit which test files are loaded, and `TESTOPTS=<opts>` to forward options (e.g. `--name=/pattern/`) to the Test::Unit autorunner. Both can be combined.
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
# Run every test in one file
|
|
186
|
+
bundle exec rake acceptance:run TEST=test/acceptance/tests/settings_test.rb
|
|
187
|
+
|
|
188
|
+
# Run a single test by exact method name (any file)
|
|
189
|
+
bundle exec rake acceptance:run TESTOPTS='--name=test_echo_task_succeeds_on_all_targets'
|
|
190
|
+
|
|
191
|
+
# Run tests whose name matches a regex within one file
|
|
192
|
+
bundle exec rake acceptance:run \
|
|
193
|
+
TEST=test/acceptance/tests/settings_test.rb \
|
|
194
|
+
TESTOPTS='--name=/host_key/'
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Environment variables:**
|
|
198
|
+
|
|
199
|
+
| Variable | Default | Description |
|
|
200
|
+
|----------|---------|-------------|
|
|
201
|
+
| `CHROMEDRIVER_URL` | `http://localhost:4444` | Selenium WebDriver endpoint |
|
|
202
|
+
| `FOREMAN_BRANCH` | `<version>-stable` | Foreman git branch for unit test image (derived from `FOREMAN_VERSION`) |
|
|
203
|
+
| `FOREMAN_PACKAGING_REPO` | `https://github.com/theforeman/foreman-packaging.git` | Git URL for foreman-packaging (cloned automatically for RPM builds) |
|
|
204
|
+
| `FOREMAN_PASS` | `changeme` | Foreman login password |
|
|
205
|
+
| `FOREMAN_URL` | `https://foreman` | Foreman URL as seen by Chrome. Override to run tests against a live instance |
|
|
206
|
+
| `FOREMAN_USER` | `admin` | Foreman login username |
|
|
207
|
+
| `FOREMAN_VERSION` | `3.18` | Foreman version to test against |
|
|
208
|
+
| `HEADFUL` | unset | Set to `1` to show the browser in noVNC |
|
|
209
|
+
| `SELENIUM_IMAGE` | auto-detected (ARM/x86) | Selenium container image (auto-selects `seleniarm/standalone-chromium` or `selenium/standalone-chrome`) |
|
|
210
|
+
| `SMART_PROXY_OPENBOLT_REF` | `main` | Branch or tag to clone |
|
|
211
|
+
| `SMART_PROXY_OPENBOLT_REPO` | `https://github.com/overlookinfra/smart_proxy_openbolt.git` | Git URL for smart_proxy_openbolt (cloned automatically for RPM builds) |
|
|
212
|
+
|
|
213
|
+
### Building Packages
|
|
214
|
+
|
|
215
|
+
Build RPM or DEB packages locally using containers. The [foreman-packaging](https://github.com/theforeman/foreman-packaging) repo is cloned automatically:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
bundle exec rake build:rpm # Build RPM
|
|
219
|
+
bundle exec rake build:deb # Build DEB
|
|
220
|
+
```
|
|
118
221
|
|
|
119
222
|
## TODO
|
|
120
223
|
|
|
121
224
|
* Integrate plans into the web UI
|
|
122
|
-
*
|
|
225
|
+
* Provide a choria transport plugin
|
|
123
226
|
|
|
124
227
|
## Contributing & support
|
|
125
228
|
|
|
@@ -145,14 +248,82 @@ GNU General Public License for more details.
|
|
|
145
248
|
You should have received a copy of the GNU General Public License
|
|
146
249
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
147
250
|
|
|
148
|
-
##
|
|
251
|
+
## How to Release
|
|
252
|
+
|
|
253
|
+
### Version locations
|
|
254
|
+
|
|
255
|
+
The version is maintained in two files:
|
|
256
|
+
|
|
257
|
+
1. `lib/foreman_openbolt/version.rb` -- the gem version (authoritative source)
|
|
258
|
+
2. `package.json` -- the npm package version (must match)
|
|
259
|
+
|
|
260
|
+
If the minimum Foreman version changes, also update:
|
|
261
|
+
|
|
262
|
+
3. `lib/foreman_openbolt/engine.rb` -- `requires_foreman '>= X.Y.Z'`
|
|
263
|
+
4. `.github/workflows/build.yml` -- default `foreman_version` and `foreman_packaging_ref` inputs
|
|
264
|
+
|
|
265
|
+
### Release steps
|
|
266
|
+
|
|
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
|
|
274
|
+
- Builds the gem
|
|
275
|
+
- Creates a GitHub Release with auto-generated notes and the gem attached
|
|
276
|
+
- Publishes the gem to GitHub Packages
|
|
277
|
+
- Publishes the gem to RubyGems.org (requires the `release` environment)
|
|
278
|
+
- Verifies the gem is available on RubyGems.org
|
|
279
|
+
|
|
280
|
+
### RPM/DEB packaging
|
|
281
|
+
|
|
282
|
+
After the gem is published to RubyGems, both RPM and DEB packages need to be updated in [theforeman/foreman-packaging](https://github.com/theforeman/foreman-packaging).
|
|
283
|
+
|
|
284
|
+
A bot automatically creates PRs against the `rpm/develop` and `deb/develop` branches to pick up the new gem version. These PRs build packages for Foreman nightly.
|
|
285
|
+
|
|
286
|
+
For stable Foreman releases (currently 3.17 and 3.18), cherry-pick the packaging commits from the develop branches into the corresponding stable branches. For each stable version you want to support:
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
cd foreman-packaging
|
|
290
|
+
|
|
291
|
+
# RPM: cherry-pick from rpm/develop into a branch off the stable target
|
|
292
|
+
git checkout rpm/3.18
|
|
293
|
+
git checkout -b cherry-pick/rubygem-foreman_openbolt-rpm-3.18
|
|
294
|
+
git cherry-pick <commit-from-rpm/develop>
|
|
295
|
+
# Push to your fork and open a PR targeting rpm/3.18
|
|
296
|
+
|
|
297
|
+
# DEB: same approach for the deb side
|
|
298
|
+
git checkout deb/3.18
|
|
299
|
+
git checkout -b cherry-pick/rubygem-foreman-openbolt-deb-3.18
|
|
300
|
+
git cherry-pick <commit-from-deb/develop>
|
|
301
|
+
# Push to your fork and open a PR targeting deb/3.18
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
PRs against stable branches should be labeled "Stable branch".
|
|
305
|
+
|
|
306
|
+
**Alternative: manual version bump**
|
|
307
|
+
|
|
308
|
+
If the cherry-pick doesn't apply cleanly, you can bump the version manually on the stable branch instead.
|
|
309
|
+
|
|
310
|
+
*RPM:* Checkout the target branch and run `bump_rpm.sh`:
|
|
311
|
+
```bash
|
|
312
|
+
cd foreman-packaging
|
|
313
|
+
git checkout rpm/3.18
|
|
314
|
+
git checkout -b bump_rpm/rubygem-foreman_openbolt
|
|
315
|
+
./bump_rpm.sh packages/plugins/rubygem-foreman_openbolt
|
|
316
|
+
# Review changes, push to your fork, and open a PR targeting rpm/3.18
|
|
317
|
+
```
|
|
149
318
|
|
|
150
|
-
*
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
* github actions will publish the tag
|
|
319
|
+
*DEB:* Checkout the target branch and update these files:
|
|
320
|
+
- `debian/gem.list` -- new gem filename
|
|
321
|
+
- `foreman_openbolt.rb` -- new version
|
|
322
|
+
- `debian/control` -- dependency versions (if changed)
|
|
323
|
+
- `debian/changelog` -- add a new entry
|
|
156
324
|
|
|
157
|
-
|
|
158
|
-
|
|
325
|
+
```bash
|
|
326
|
+
git checkout deb/3.18
|
|
327
|
+
git checkout -b bump_deb/ruby-foreman-openbolt
|
|
328
|
+
# Make the changes above, push to your fork, and open a PR targeting deb/3.18
|
|
329
|
+
```
|
data/Rakefile
CHANGED
|
@@ -1,99 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
rdoc.title = 'ForemanOpenbolt'
|
|
16
|
-
rdoc.options << '--line-numbers'
|
|
17
|
-
rdoc.rdoc_files.include('README.rdoc')
|
|
18
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
require 'rake/testtask'
|
|
22
|
-
|
|
23
|
-
Rake::TestTask.new(:test) do |t|
|
|
24
|
-
t.libs << 'lib'
|
|
25
|
-
t.libs << 'test'
|
|
26
|
-
t.pattern = 'test/**/*_test.rb'
|
|
27
|
-
t.verbose = false
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
begin
|
|
31
|
-
require 'rubocop/rake_task'
|
|
32
|
-
RuboCop::RakeTask.new
|
|
33
|
-
rescue LoadError
|
|
34
|
-
puts 'Rubocop not loaded.'
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# This is all kinda screwy. Fix it up later.
|
|
38
|
-
LINTERS = {
|
|
39
|
-
ruby: { cmd: 'rubocop', fix: '--auto-correct' },
|
|
40
|
-
erb: { cmd: 'erb_lint', fix: '--autocorrect', glob: '**/*.erb' },
|
|
41
|
-
js: { image: 'registry.access.redhat.com/ubi9/nodejs-20:latest', cmd: 'npm run lint --', fix: '--fix' },
|
|
42
|
-
}.freeze
|
|
43
|
-
|
|
44
|
-
namespace :lint do
|
|
45
|
-
def fix?
|
|
46
|
-
!ENV['FIX'].nil?
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def local?
|
|
50
|
-
!ENV['LOCAL'].nil?
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def bin
|
|
54
|
-
ENV['CONTAINER_BIN'] || 'docker'
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
LINTERS.each do |name, cfg|
|
|
58
|
-
desc "Run #{name} linter#{' (fix)' if fix?}"
|
|
59
|
-
task name do
|
|
60
|
-
cmd = [cfg[:cmd]]
|
|
61
|
-
cmd << cfg[:fix] if fix?
|
|
62
|
-
cmd << cfg[:glob] unless cfg[:glob].nil? || cfg[:glob].empty? # rubocop:disable Rails/Blank
|
|
63
|
-
cmd = cmd.join(' ')
|
|
64
|
-
if cfg[:image] && !local?
|
|
65
|
-
cmd = "#{bin} run --rm -v #{Dir.pwd}:/code #{cfg[:image]} /bin/bash -c " +
|
|
66
|
-
"'cd /code && npm install --loglevel=error && #{cmd}'"
|
|
67
|
-
end
|
|
68
|
-
sh cmd
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
desc 'Run all linters'
|
|
73
|
-
task all: LINTERS.keys
|
|
74
|
-
|
|
75
|
-
desc 'Run all linters and apply fixes'
|
|
76
|
-
task :fix do
|
|
77
|
-
ENV['FIX'] = 'true'
|
|
78
|
-
Rake::Task['lint:all'].invoke
|
|
3
|
+
require_relative 'rakelib/utils/shell'
|
|
4
|
+
|
|
5
|
+
def latest_foreman_version
|
|
6
|
+
result = Shell.capture(
|
|
7
|
+
['git', 'ls-remote', '--tags', 'https://github.com/theforeman/foreman.git'],
|
|
8
|
+
print_command: false, allowed_exit_codes: [0, 1]
|
|
9
|
+
)
|
|
10
|
+
tags = result.output.scan(%r{refs/tags/([^\s]+)$}).flatten
|
|
11
|
+
versions = tags.filter_map do |tag|
|
|
12
|
+
Gem::Version.new(tag)
|
|
13
|
+
rescue ArgumentError
|
|
14
|
+
nil
|
|
79
15
|
end
|
|
16
|
+
latest = versions.reject(&:prerelease?).max
|
|
17
|
+
latest ? "#{latest.segments[0]}.#{latest.segments[1]}" : '3.18'
|
|
80
18
|
end
|
|
81
19
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
begin
|
|
85
|
-
require 'rubygems'
|
|
86
|
-
require 'github_changelog_generator/task'
|
|
20
|
+
DEFAULT_FOREMAN_VERSION = latest_foreman_version
|
|
21
|
+
FOREMAN_VERSION = ENV.fetch('FOREMAN_VERSION', DEFAULT_FOREMAN_VERSION)
|
|
87
22
|
|
|
88
|
-
|
|
89
|
-
config.exclude_labels = %w[duplicate question invalid wontfix wont-fix skip-changelog github_actions]
|
|
90
|
-
config.user = 'overlookinfra'
|
|
91
|
-
config.project = 'foreman_openbolt'
|
|
92
|
-
gem_version = Gem::Specification.load("#{config.project}.gemspec").version
|
|
93
|
-
config.future_release = gem_version
|
|
94
|
-
end
|
|
95
|
-
rescue LoadError
|
|
96
|
-
task :changelog do
|
|
97
|
-
abort("Run `bundle install --with release` to install the `github_changelog_generator` gem.")
|
|
98
|
-
end
|
|
99
|
-
end
|
|
23
|
+
task default: :lint
|
|
@@ -13,10 +13,10 @@ module ForemanOpenbolt
|
|
|
13
13
|
REDACTED_PLACEHOLDER = '*****'
|
|
14
14
|
|
|
15
15
|
before_action :load_smart_proxy, only: [
|
|
16
|
-
:fetch_tasks, :reload_tasks, :fetch_openbolt_options, :launch_task
|
|
16
|
+
:fetch_tasks, :reload_tasks, :fetch_openbolt_options, :launch_task
|
|
17
17
|
]
|
|
18
18
|
before_action :load_openbolt_api, only: [
|
|
19
|
-
:fetch_tasks, :reload_tasks, :fetch_openbolt_options, :launch_task
|
|
19
|
+
:fetch_tasks, :reload_tasks, :fetch_openbolt_options, :launch_task
|
|
20
20
|
]
|
|
21
21
|
before_action :load_task_job, only: [:job_status, :job_result]
|
|
22
22
|
|
|
@@ -44,11 +44,18 @@ module ForemanOpenbolt
|
|
|
44
44
|
def fetch_openbolt_options
|
|
45
45
|
options = @openbolt_api.openbolt_options
|
|
46
46
|
|
|
47
|
-
# Get defaults from Foreman settings
|
|
47
|
+
# Get defaults from Foreman settings.
|
|
48
|
+
# For encrypted settings, show the placeholder only if a non-empty value
|
|
49
|
+
# has been saved, so the UI shows an empty field for unconfigured passwords
|
|
50
|
+
# instead of a misleading placeholder.
|
|
48
51
|
defaults = {}
|
|
49
52
|
openbolt_settings.each do |setting|
|
|
50
53
|
key = setting.name.sub(/^openbolt_/, '')
|
|
51
|
-
|
|
54
|
+
if setting.encrypted?
|
|
55
|
+
defaults[key] = ENCRYPTED_PLACEHOLDER unless setting.value.to_s.empty?
|
|
56
|
+
else
|
|
57
|
+
defaults[key] = setting.value
|
|
58
|
+
end
|
|
52
59
|
end
|
|
53
60
|
|
|
54
61
|
# Merge the defaults into the options metadata
|
|
@@ -60,10 +67,10 @@ module ForemanOpenbolt
|
|
|
60
67
|
|
|
61
68
|
render json: result
|
|
62
69
|
rescue ProxyAPI::ProxyException => e
|
|
63
|
-
|
|
70
|
+
log_exception('fetch_openbolt_options', e)
|
|
64
71
|
render_error("Smart Proxy error: #{e.message}", :bad_gateway)
|
|
65
72
|
rescue StandardError => e
|
|
66
|
-
|
|
73
|
+
log_exception('fetch_openbolt_options', e)
|
|
67
74
|
render_error("Internal server error: #{e.message}", :internal_server_error)
|
|
68
75
|
end
|
|
69
76
|
|
|
@@ -94,9 +101,12 @@ module ForemanOpenbolt
|
|
|
94
101
|
options: options
|
|
95
102
|
)
|
|
96
103
|
|
|
97
|
-
logger.
|
|
104
|
+
logger.debug("Task execution response: #{response.inspect}")
|
|
98
105
|
|
|
99
|
-
|
|
106
|
+
if response['error']
|
|
107
|
+
error_detail = response['error'].is_a?(Hash) ? response['error']['message'] : response['error']
|
|
108
|
+
return render_error("Task execution failed: #{error_detail}", :bad_request)
|
|
109
|
+
end
|
|
100
110
|
return render_error('Task execution failed: No job ID returned', :bad_request) unless response['id']
|
|
101
111
|
|
|
102
112
|
metadata = @openbolt_api.tasks[task_name] || {}
|
|
@@ -117,15 +127,17 @@ module ForemanOpenbolt
|
|
|
117
127
|
|
|
118
128
|
render json: {
|
|
119
129
|
job_id: response['id'],
|
|
120
|
-
proxy_id: @smart_proxy.id,
|
|
121
|
-
proxy_name: @smart_proxy.name,
|
|
122
130
|
}
|
|
131
|
+
rescue ArgumentError => e
|
|
132
|
+
# From merge_encrypted_defaults when a user submits the encrypted
|
|
133
|
+
# placeholder for an option that has no saved Foreman setting.
|
|
134
|
+
log_exception('launch_task', e)
|
|
135
|
+
render_error(e.message, :bad_request)
|
|
123
136
|
rescue ActiveRecord::RecordInvalid => e
|
|
124
|
-
|
|
137
|
+
log_exception('launch_task', e)
|
|
125
138
|
render_error("Database error: #{e.message}", :internal_server_error)
|
|
126
139
|
rescue StandardError => e
|
|
127
|
-
|
|
128
|
-
logger.error("Backtrace: #{e.backtrace.first(5).join("\n")}")
|
|
140
|
+
log_exception('launch_task', e)
|
|
129
141
|
render_error("Error launching task: #{e.message}", :internal_server_error)
|
|
130
142
|
end
|
|
131
143
|
end
|
|
@@ -142,6 +154,10 @@ module ForemanOpenbolt
|
|
|
142
154
|
task_description: @task_job.task_description,
|
|
143
155
|
task_parameters: @task_job.task_parameters,
|
|
144
156
|
targets: @task_job.targets,
|
|
157
|
+
smart_proxy: {
|
|
158
|
+
id: @task_job.smart_proxy_id,
|
|
159
|
+
name: @task_job.smart_proxy&.name || '(unknown)',
|
|
160
|
+
},
|
|
145
161
|
}
|
|
146
162
|
end
|
|
147
163
|
|
|
@@ -158,10 +174,10 @@ module ForemanOpenbolt
|
|
|
158
174
|
|
|
159
175
|
# List of all task history
|
|
160
176
|
def fetch_task_history
|
|
177
|
+
per_page = [(params[:per_page] || 20).to_i, 100].min
|
|
161
178
|
@task_history = TaskJob.includes(:smart_proxy)
|
|
162
179
|
.recent
|
|
163
|
-
.paginate(page: params[:page],
|
|
164
|
-
per_page: (params[:per_page] || 20).to_i)
|
|
180
|
+
.paginate(page: params[:page], per_page: per_page)
|
|
165
181
|
|
|
166
182
|
render json: {
|
|
167
183
|
results: @task_history.map { |job| serialize_task_job(job) },
|
|
@@ -169,18 +185,11 @@ module ForemanOpenbolt
|
|
|
169
185
|
page: @task_history.current_page,
|
|
170
186
|
per_page: @task_history.per_page,
|
|
171
187
|
}
|
|
188
|
+
rescue StandardError => e
|
|
189
|
+
log_exception('fetch_task_history', e)
|
|
190
|
+
render_error("Error loading task history: #{e.message}", :internal_server_error)
|
|
172
191
|
end
|
|
173
192
|
|
|
174
|
-
# Show a specific task job
|
|
175
|
-
def show
|
|
176
|
-
task_job = TaskJob.find(params[:id])
|
|
177
|
-
render json: serialize_task_job(task_job, detailed: true)
|
|
178
|
-
rescue ActiveRecord::RecordNotFound
|
|
179
|
-
render_error('Task job not found', :not_found)
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
private
|
|
183
|
-
|
|
184
193
|
def load_smart_proxy
|
|
185
194
|
proxy_id = params[:proxy_id]
|
|
186
195
|
if proxy_id.blank?
|
|
@@ -207,7 +216,7 @@ module ForemanOpenbolt
|
|
|
207
216
|
begin
|
|
208
217
|
@openbolt_api = ProxyAPI::Openbolt.new(url: @smart_proxy.url)
|
|
209
218
|
rescue StandardError => e
|
|
210
|
-
|
|
219
|
+
log_exception("load_openbolt_api for proxy #{@smart_proxy.name}", e)
|
|
211
220
|
render_error("Failed to connect to Smart Proxy", :bad_gateway)
|
|
212
221
|
return false
|
|
213
222
|
end
|
|
@@ -218,14 +227,17 @@ module ForemanOpenbolt
|
|
|
218
227
|
def load_task_job
|
|
219
228
|
job_id = params[:job_id]
|
|
220
229
|
logger.debug("load_task_job - Job ID: #{job_id}")
|
|
221
|
-
|
|
230
|
+
if job_id.blank?
|
|
231
|
+
render_error('Job ID is required', :bad_request)
|
|
232
|
+
return false
|
|
233
|
+
end
|
|
222
234
|
|
|
223
235
|
@task_job = TaskJob.find_by(job_id: job_id)
|
|
224
236
|
logger.debug("load_task_job - Task Job: #{@task_job.inspect}")
|
|
225
237
|
end
|
|
226
238
|
|
|
227
239
|
def openbolt_settings
|
|
228
|
-
@openbolt_settings ||=
|
|
240
|
+
@openbolt_settings ||= Foreman.settings.select { |s| s.name.start_with?('openbolt_') }
|
|
229
241
|
end
|
|
230
242
|
|
|
231
243
|
def encrypted_settings
|
|
@@ -233,13 +245,18 @@ module ForemanOpenbolt
|
|
|
233
245
|
end
|
|
234
246
|
|
|
235
247
|
def merge_encrypted_defaults(options)
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
248
|
+
merged = options.dup
|
|
249
|
+
merged.each do |key, value|
|
|
250
|
+
next unless value == ENCRYPTED_PLACEHOLDER
|
|
251
|
+
|
|
252
|
+
saved = Setting["openbolt_#{key}"]
|
|
253
|
+
if saved.nil? || saved.to_s.empty?
|
|
254
|
+
raise ArgumentError,
|
|
255
|
+
"No saved value for encrypted option '#{key}'. Configure it in Administer > Settings or provide a value."
|
|
256
|
+
end
|
|
257
|
+
merged[key] = saved
|
|
241
258
|
end
|
|
242
|
-
|
|
259
|
+
merged
|
|
243
260
|
end
|
|
244
261
|
|
|
245
262
|
def scrub_options_for_storage(options)
|
|
@@ -256,19 +273,24 @@ module ForemanOpenbolt
|
|
|
256
273
|
logger.debug("OpenBolt API call #{method_name} successful for proxy #{@smart_proxy.name}")
|
|
257
274
|
render json: result
|
|
258
275
|
rescue ProxyAPI::ProxyException => e
|
|
259
|
-
|
|
276
|
+
log_exception(method_name, e)
|
|
260
277
|
render_error("Smart Proxy error: #{e.message}", :bad_gateway)
|
|
261
278
|
rescue StandardError => e
|
|
262
|
-
|
|
279
|
+
log_exception(method_name, e)
|
|
263
280
|
render_error("Internal server error: #{e.message}", :internal_server_error)
|
|
264
281
|
end
|
|
265
282
|
|
|
283
|
+
def log_exception(message, exception)
|
|
284
|
+
logger.error("#{message}: #{exception.class}: #{exception.message}")
|
|
285
|
+
logger.error(exception.backtrace.join("\n")) if exception.backtrace
|
|
286
|
+
end
|
|
287
|
+
|
|
266
288
|
def render_error(message, status)
|
|
267
289
|
render json: { error: message }, status: status
|
|
268
290
|
end
|
|
269
291
|
|
|
270
|
-
def serialize_task_job(task_job
|
|
271
|
-
|
|
292
|
+
def serialize_task_job(task_job)
|
|
293
|
+
{
|
|
272
294
|
job_id: task_job.job_id,
|
|
273
295
|
task_name: task_job.task_name,
|
|
274
296
|
task_description: task_job.task_description,
|
|
@@ -277,22 +299,12 @@ module ForemanOpenbolt
|
|
|
277
299
|
status: task_job.status,
|
|
278
300
|
smart_proxy: {
|
|
279
301
|
id: task_job.smart_proxy_id,
|
|
280
|
-
name: task_job.smart_proxy
|
|
302
|
+
name: task_job.smart_proxy&.name || '(unknown)',
|
|
281
303
|
},
|
|
282
304
|
submitted_at: task_job.submitted_at,
|
|
283
305
|
completed_at: task_job.completed_at,
|
|
284
306
|
duration: task_job.duration,
|
|
285
307
|
}
|
|
286
|
-
|
|
287
|
-
if detailed
|
|
288
|
-
data.merge!(
|
|
289
|
-
openbolt_options: task_job.scrubbed_openbolt_options,
|
|
290
|
-
result: task_job.result,
|
|
291
|
-
log: task_job.log
|
|
292
|
-
)
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
data
|
|
296
308
|
end
|
|
297
309
|
end
|
|
298
310
|
end
|
|
@@ -8,16 +8,19 @@ module Actions
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def run
|
|
11
|
-
proxy = ::SmartProxy.
|
|
12
|
-
|
|
11
|
+
proxy = ::SmartProxy.find_by(id: input[:proxy_id])
|
|
12
|
+
unless proxy
|
|
13
|
+
Rails.logger.warn("Proxy #{input[:proxy_id]} not found during cleanup for job #{input[:job_id]}, skipping")
|
|
14
|
+
return
|
|
15
|
+
end
|
|
13
16
|
|
|
17
|
+
api = ::ProxyAPI::Openbolt.new(url: proxy.url)
|
|
14
18
|
response = api.delete_job_artifacts(job_id: input[:job_id])
|
|
15
|
-
Rails.logger.
|
|
16
|
-
|
|
17
|
-
Rails.logger.info("Would delete artifacts for job #{input[:job_id]} on proxy #{proxy.name}")
|
|
19
|
+
Rails.logger.debug { "Cleaned up artifacts for job #{input[:job_id]} on proxy #{proxy.name}: #{response}" }
|
|
18
20
|
rescue StandardError => e
|
|
19
21
|
# Don't fail the action if cleanup fails - it's not critical
|
|
20
|
-
Rails.logger.error("Failed to cleanup artifacts for job #{input[:job_id]}: #{e.message}")
|
|
22
|
+
Rails.logger.error("Failed to cleanup artifacts for job #{input[:job_id]}: #{e.class}: #{e.message}")
|
|
23
|
+
Rails.logger.error(e.backtrace.join("\n")) if e.backtrace
|
|
21
24
|
end
|
|
22
25
|
|
|
23
26
|
def rescue_strategy
|
|
@@ -30,10 +33,8 @@ module Actions
|
|
|
30
33
|
end
|
|
31
34
|
|
|
32
35
|
def humanized_input
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
proxy: ::SmartProxy.find_by(id: input[:proxy_id])&.name,
|
|
36
|
-
}
|
|
36
|
+
proxy_name = ::SmartProxy.find_by(id: input[:proxy_id])&.name || '(unknown)'
|
|
37
|
+
"job #{input[:job_id]} on #{proxy_name}"
|
|
37
38
|
end
|
|
38
39
|
end
|
|
39
40
|
end
|