foreman_openbolt 1.2.0 → 1.3.0

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -0
  3. data/Rakefile +32 -0
  4. data/app/controllers/api/v2/openbolt_jobs_controller.rb +77 -0
  5. data/app/controllers/api/v2/openbolt_tasks_controller.rb +83 -0
  6. data/app/controllers/concerns/foreman_openbolt/common.rb +121 -0
  7. data/app/controllers/concerns/foreman_openbolt/jobs.rb +84 -0
  8. data/app/controllers/concerns/foreman_openbolt/tasks.rb +140 -0
  9. data/app/controllers/foreman_openbolt/task_controller.rb +12 -291
  10. data/app/lib/actions/foreman_openbolt/cleanup_proxy_artifacts.rb +4 -2
  11. data/app/lib/actions/foreman_openbolt/poll_task_status.rb +48 -29
  12. data/app/models/foreman_openbolt/task_job.rb +9 -2
  13. data/config/routes.rb +39 -4
  14. data/db/migrate/20260824000000_remove_view_smart_proxies_openbolt_permission.rb +18 -0
  15. data/lib/foreman_openbolt/engine.rb +15 -7
  16. data/lib/foreman_openbolt/version.rb +1 -1
  17. data/lib/proxy_api/openbolt.rb +64 -13
  18. data/package.json +1 -1
  19. data/test/acceptance/acceptance_helper.rb +13 -0
  20. data/test/acceptance/api_acceptance_helper.rb +109 -0
  21. data/test/acceptance/tests/api/authn_authz_test.rb +82 -0
  22. data/test/acceptance/tests/api/jobs_test.rb +77 -0
  23. data/test/acceptance/tests/api/launch_test.rb +79 -0
  24. data/test/acceptance/tests/api/proxy_tasks_test.rb +51 -0
  25. data/test/unit/controllers/api/v2/openbolt_jobs_controller_test.rb +251 -0
  26. data/test/unit/controllers/api/v2/openbolt_tasks_controller_test.rb +438 -0
  27. data/test/unit/controllers/task_controller_test.rb +197 -39
  28. data/test/unit/docker/Dockerfile +1 -0
  29. data/test/unit/lib/actions/poll_task_status_test.rb +112 -34
  30. data/test/unit/lib/proxy_api/openbolt_test.rb +121 -6
  31. data/test/unit/models/task_job_test.rb +11 -0
  32. data/webpack/src/Components/LaunchTask/__tests__/LaunchTask.test.js +44 -1
  33. data/webpack/src/Components/LaunchTask/hooks/__tests__/useOpenBoltOptions.test.js +3 -0
  34. data/webpack/src/Components/LaunchTask/hooks/__tests__/useTasksData.test.js +5 -4
  35. data/webpack/src/Components/LaunchTask/hooks/useOpenBoltOptions.js +1 -1
  36. data/webpack/src/Components/LaunchTask/hooks/useTasksData.js +4 -1
  37. data/webpack/src/Components/LaunchTask/index.js +21 -6
  38. data/webpack/src/Components/TaskExecution/__tests__/TaskExecution.test.js +1 -1
  39. data/webpack/src/Components/TaskExecution/hooks/__tests__/useJobPolling.test.js +8 -8
  40. data/webpack/src/Components/TaskExecution/hooks/useJobPolling.js +3 -3
  41. data/webpack/src/Components/TaskHistory/__tests__/TaskHistory.test.js +6 -6
  42. data/webpack/src/Components/TaskHistory/index.js +3 -3
  43. metadata +17 -4
@@ -29,11 +29,11 @@ class ProxyApiOpenboltTest < ForemanOpenbolt::PluginTestCase
29
29
  assert_equal tasks, result
30
30
  end
31
31
 
32
- test 'raises on proxy HTTP error' do
32
+ test 'wraps proxy HTTP errors as ProxyException' do
33
33
  stub_request(:get, "#{PROXY_URL}/openbolt/tasks")
34
34
  .to_return(status: 500, body: 'Internal Server Error')
35
35
 
36
- assert_raises(RestClient::InternalServerError) { @api.fetch_tasks }
36
+ assert_raises(ProxyAPI::ProxyException) { @api.fetch_tasks }
37
37
  end
38
38
 
39
39
  test 'raises ProxyException on unparseable response body' do
@@ -159,16 +159,131 @@ class ProxyApiOpenboltTest < ForemanOpenbolt::PluginTestCase
159
159
  end
160
160
 
161
161
  context 'connection errors' do
162
- test 'raises on timeout' do
162
+ test 'wraps timeouts as ProxyException' do
163
163
  stub_request(:get, "#{PROXY_URL}/openbolt/tasks").to_timeout
164
164
 
165
- assert_raises(RestClient::Exceptions::OpenTimeout) { @api.fetch_tasks }
165
+ assert_raises(ProxyAPI::ProxyException) { @api.fetch_tasks }
166
166
  end
167
167
 
168
- test 'raises on connection refused' do
168
+ test 'wraps connection refused as ProxyException' do
169
169
  stub_request(:get, "#{PROXY_URL}/openbolt/tasks").to_raise(Errno::ECONNREFUSED)
170
170
 
171
- assert_raises(Errno::ECONNREFUSED) { @api.fetch_tasks }
171
+ assert_raises(ProxyAPI::ProxyException) { @api.fetch_tasks }
172
+ end
173
+
174
+ test 'wraps SocketError as ProxyException' do
175
+ stub_request(:get, "#{PROXY_URL}/openbolt/tasks").to_raise(SocketError.new('host not found'))
176
+
177
+ assert_raises(ProxyAPI::ProxyException) { @api.fetch_tasks }
178
+ end
179
+
180
+ test 'wraps SSL errors as ProxyException' do
181
+ stub_request(:get, "#{PROXY_URL}/openbolt/tasks")
182
+ .to_raise(OpenSSL::SSL::SSLError.new('bad cert'))
183
+
184
+ assert_raises(ProxyAPI::ProxyException) { @api.fetch_tasks }
185
+ end
186
+ end
187
+
188
+ # The proxy reports domain errors as HTTP 200 with {"error": {"message":
189
+ # ...}}. ProxyReportedError makes those loud everywhere except launch_task,
190
+ # which passes the envelope through so the caller renders a 400.
191
+ context 'proxy-reported errors (200 + error envelope)' do
192
+ error_body = { 'error' => { 'message' => 'Job not found: bogus' } }.to_json
193
+
194
+ test 'fetch_tasks raises ProxyReportedError' do
195
+ stub_request(:get, "#{PROXY_URL}/openbolt/tasks")
196
+ .to_return(status: 200, body: error_body, headers: { 'Content-Type' => 'application/json' })
197
+
198
+ error = assert_raises(ProxyAPI::Openbolt::ProxyReportedError) { @api.fetch_tasks }
199
+ assert_match(/Job not found: bogus/, error.message)
200
+ end
201
+
202
+ test 'reload_tasks raises ProxyReportedError' do
203
+ stub_request(:get, "#{PROXY_URL}/openbolt/tasks/reload")
204
+ .to_return(status: 200, body: error_body, headers: { 'Content-Type' => 'application/json' })
205
+
206
+ assert_raises(ProxyAPI::Openbolt::ProxyReportedError) { @api.reload_tasks }
207
+ end
208
+
209
+ test 'openbolt_options raises ProxyReportedError' do
210
+ stub_request(:get, "#{PROXY_URL}/openbolt/tasks/options")
211
+ .to_return(status: 200, body: error_body, headers: { 'Content-Type' => 'application/json' })
212
+
213
+ assert_raises(ProxyAPI::Openbolt::ProxyReportedError) { @api.openbolt_options }
214
+ end
215
+
216
+ test 'job_status raises ProxyReportedError' do
217
+ stub_request(:get, "#{PROXY_URL}/openbolt/job/test-123/status")
218
+ .to_return(status: 200, body: error_body, headers: { 'Content-Type' => 'application/json' })
219
+
220
+ assert_raises(ProxyAPI::Openbolt::ProxyReportedError) { @api.job_status(job_id: 'test-123') }
221
+ end
222
+
223
+ test 'job_result raises ProxyReportedError' do
224
+ stub_request(:get, "#{PROXY_URL}/openbolt/job/test-123/result")
225
+ .to_return(status: 200, body: error_body, headers: { 'Content-Type' => 'application/json' })
226
+
227
+ assert_raises(ProxyAPI::Openbolt::ProxyReportedError) { @api.job_result(job_id: 'test-123') }
228
+ end
229
+
230
+ test 'delete_job_artifacts raises ProxyReportedError' do
231
+ stub_request(:delete, "#{PROXY_URL}/openbolt/job/test-123/artifacts")
232
+ .to_return(status: 200, body: error_body, headers: { 'Content-Type' => 'application/json' })
233
+
234
+ assert_raises(ProxyAPI::Openbolt::ProxyReportedError) { @api.delete_job_artifacts(job_id: 'test-123') }
235
+ end
236
+
237
+ test 'launch_task returns the error envelope unchanged' do
238
+ stub_request(:post, "#{PROXY_URL}/openbolt/launch/task")
239
+ .to_return(status: 200, body: error_body, headers: { 'Content-Type' => 'application/json' })
240
+
241
+ result = @api.launch_task(name: 'bogus', targets: 'host1', parameters: {}, options: {})
242
+ assert_equal('Job not found: bogus', result['error']['message'])
243
+ end
244
+
245
+ test 'launch_task still wraps transport errors' do
246
+ stub_request(:post, "#{PROXY_URL}/openbolt/launch/task").to_timeout
247
+
248
+ assert_raises(ProxyAPI::ProxyException) do
249
+ @api.launch_task(name: 'mymod::install', targets: 'host1', parameters: {}, options: {})
250
+ end
251
+ end
252
+
253
+ test 'ProxyReportedError is a ProxyException so existing rescue_from chains catch it' do
254
+ stub_request(:get, "#{PROXY_URL}/openbolt/tasks")
255
+ .to_return(status: 200, body: error_body, headers: { 'Content-Type' => 'application/json' })
256
+
257
+ assert_raises(ProxyAPI::ProxyException) { @api.fetch_tasks }
258
+ end
259
+
260
+ test 'a task named error does not raise ProxyReportedError' do
261
+ tasks = {
262
+ 'error' => { 'description' => 'Raise an error on the target' },
263
+ 'mymod::install' => { 'description' => 'Install a package' },
264
+ }
265
+ stub_request(:get, "#{PROXY_URL}/openbolt/tasks")
266
+ .to_return(status: 200, body: tasks.to_json, headers: { 'Content-Type' => 'application/json' })
267
+
268
+ assert_equal tasks, @api.fetch_tasks
269
+ end
270
+
271
+ test 'top-level error key with a string value is not treated as an envelope' do
272
+ body = { 'error' => 'Plain string error' }
273
+ stub_request(:get, "#{PROXY_URL}/openbolt/tasks")
274
+ .to_return(status: 200, body: body.to_json,
275
+ headers: { 'Content-Type' => 'application/json' })
276
+
277
+ assert_equal body, @api.fetch_tasks
278
+ end
279
+
280
+ test 'error hash without a message key is not treated as an envelope' do
281
+ body = { 'error' => { 'code' => 42 } }
282
+ stub_request(:get, "#{PROXY_URL}/openbolt/tasks")
283
+ .to_return(status: 200, body: body.to_json,
284
+ headers: { 'Content-Type' => 'application/json' })
285
+
286
+ assert_equal body, @api.fetch_tasks
172
287
  end
173
288
  end
174
289
  end
@@ -200,6 +200,17 @@ class TaskJobTest < ForemanOpenbolt::PluginTestCase
200
200
  end
201
201
  assert_equal({ 'items' => [] }, job.reload.result)
202
202
  end
203
+
204
+ test 'cleanup_proxy_artifacts schedules Dynflow action when log is saved without result' do
205
+ job = FactoryBot.create(:task_job, status: 'success', completed_at: Time.current)
206
+ ForemanTasks.expects(:async_task).with(
207
+ ::Actions::ForemanOpenbolt::CleanupProxyArtifacts,
208
+ job.smart_proxy_id,
209
+ job.job_id
210
+ )
211
+
212
+ job.update!(log: 'task finished')
213
+ end
203
214
  end
204
215
 
205
216
  context 'create_from_execution!' do
@@ -4,7 +4,7 @@ import { Provider } from 'react-redux';
4
4
  import { createStore } from 'redux';
5
5
  import { MemoryRouter } from 'react-router-dom';
6
6
  import { API } from 'foremanReact/redux/API';
7
- import LaunchTask from '../index';
7
+ import LaunchTask, { buildLaunchPayload } from '../index';
8
8
 
9
9
  // Mock HostSelector to avoid Apollo/GraphQL dependency chain
10
10
  jest.mock('../HostSelector', () => {
@@ -81,3 +81,46 @@ describe('LaunchTask', () => {
81
81
  });
82
82
  });
83
83
  });
84
+
85
+ describe('buildLaunchPayload', () => {
86
+ test('passes "parameters" key through correctly', () => {
87
+ const body = buildLaunchPayload({
88
+ proxyId: 1,
89
+ taskName: 'mymod::install',
90
+ targets: ['host1.example.com'],
91
+ parameters: { name: 'nginx', version: '1.21' },
92
+ options: { transport: 'ssh' },
93
+ });
94
+
95
+ expect(body).toHaveProperty('parameters', {
96
+ name: 'nginx',
97
+ version: '1.21',
98
+ });
99
+ });
100
+
101
+ test('joins targets with comma into a single string', () => {
102
+ const body = buildLaunchPayload({
103
+ proxyId: 1,
104
+ taskName: 'mymod::install',
105
+ targets: ['host1', 'host2', 'host3'],
106
+ parameters: {},
107
+ options: {},
108
+ });
109
+
110
+ expect(body.targets).toBe('host1,host2,host3');
111
+ });
112
+
113
+ test('forwards smart_proxy_id, task_name, and options unchanged', () => {
114
+ const body = buildLaunchPayload({
115
+ proxyId: 42,
116
+ taskName: 'puppet_conf::set',
117
+ targets: ['host1'],
118
+ parameters: {},
119
+ options: { transport: 'choria', 'run-as': 'root' },
120
+ });
121
+
122
+ expect(body.smart_proxy_id).toBe(42);
123
+ expect(body.task_name).toBe('puppet_conf::set');
124
+ expect(body.options).toEqual({ transport: 'choria', 'run-as': 'root' });
125
+ });
126
+ });
@@ -42,6 +42,9 @@ describe('useOpenBoltOptions', () => {
42
42
  await result.current.fetchOpenBoltOptions(42);
43
43
  });
44
44
 
45
+ expect(API.get).toHaveBeenCalledWith(
46
+ expect.stringContaining('smart_proxy_id=42')
47
+ );
45
48
  expect(result.current.openBoltOptionsMetadata).toEqual(options);
46
49
  expect(result.current.openBoltOptions).toEqual({
47
50
  transport: 'ssh',
@@ -42,14 +42,14 @@ describe('useTasksData', () => {
42
42
  expect.stringContaining('fetch_tasks')
43
43
  );
44
44
  expect(API.get).toHaveBeenCalledWith(
45
- expect.stringContaining('proxy_id=42')
45
+ expect.stringContaining('smart_proxy_id=42')
46
46
  );
47
47
  expect(result.current.taskMetadata).toEqual(tasks);
48
48
  expect(result.current.isLoadingTasks).toBe(false);
49
49
  });
50
50
 
51
- test('fetchTasks uses reload endpoint when forceReload is true', async () => {
52
- API.get.mockResolvedValue({ data: {} });
51
+ test('fetchTasks uses POST to the reload endpoint when forceReload is true', async () => {
52
+ API.post.mockResolvedValue({ data: {} });
53
53
 
54
54
  const { result } = renderHook(() => useTasksData(), { wrapper });
55
55
 
@@ -57,9 +57,10 @@ describe('useTasksData', () => {
57
57
  await result.current.fetchTasks(42, true);
58
58
  });
59
59
 
60
- expect(API.get).toHaveBeenCalledWith(
60
+ expect(API.post).toHaveBeenCalledWith(
61
61
  expect.stringContaining('reload_tasks')
62
62
  );
63
+ expect(API.get).not.toHaveBeenCalled();
63
64
  });
64
65
 
65
66
  test('fetchTasks resets state before fetching', async () => {
@@ -21,7 +21,7 @@ export const useOpenBoltOptions = () => {
21
21
 
22
22
  try {
23
23
  const { data } = await API.get(
24
- `${ROUTES.API.FETCH_OPENBOLT_OPTIONS}?proxy_id=${proxyId}`
24
+ `${ROUTES.API.FETCH_OPENBOLT_OPTIONS}?smart_proxy_id=${proxyId}`
25
25
  );
26
26
 
27
27
  setOpenBoltOptionsMetadata(data || {});
@@ -24,7 +24,10 @@ export const useTasksData = () => {
24
24
  const endpoint = forceReload
25
25
  ? ROUTES.API.RELOAD_TASKS
26
26
  : ROUTES.API.FETCH_TASKS;
27
- const { data } = await API.get(`${endpoint}?proxy_id=${proxyId}`);
27
+ const method = forceReload ? 'post' : 'get';
28
+ const { data } = await API[method](
29
+ `${endpoint}?smart_proxy_id=${proxyId}`
30
+ );
28
31
 
29
32
  setTaskMetadata(data || {});
30
33
 
@@ -29,6 +29,21 @@ import { useTasksData } from './hooks/useTasksData';
29
29
  import { useOpenBoltOptions } from './hooks/useOpenBoltOptions';
30
30
  import { useShowMessage, extractErrorMessage } from '../common/helpers';
31
31
 
32
+ // Exported so tests can use the launch wire format
33
+ export const buildLaunchPayload = ({
34
+ proxyId,
35
+ taskName,
36
+ targets,
37
+ parameters,
38
+ options,
39
+ }) => ({
40
+ smart_proxy_id: proxyId,
41
+ task_name: taskName,
42
+ targets: targets.join(','),
43
+ parameters,
44
+ options,
45
+ });
46
+
32
47
  const LaunchTask = () => {
33
48
  const history = useHistory();
34
49
  const showMessage = useShowMessage();
@@ -165,13 +180,13 @@ const LaunchTask = () => {
165
180
  }
166
181
  );
167
182
 
168
- const body = {
169
- proxy_id: selectedProxy,
170
- task_name: selectedTask,
171
- targets: targets.join(','),
172
- params: taskParameters,
183
+ const body = buildLaunchPayload({
184
+ proxyId: selectedProxy,
185
+ taskName: selectedTask,
186
+ targets,
187
+ parameters: taskParameters,
173
188
  options: visibleOptions,
174
- };
189
+ });
175
190
 
176
191
  const { data } = await API.post(ROUTES.API.LAUNCH_TASK, body);
177
192
 
@@ -53,7 +53,7 @@ describe('TaskExecution', () => {
53
53
  test('strips ANSI codes from log output when result is available', async () => {
54
54
  const statusData = {
55
55
  status: 'success',
56
- task_name: 'test::task',
56
+ name: 'test::task',
57
57
  targets: ['host1'],
58
58
  submitted_at: '2026-01-01T00:00:00Z',
59
59
  completed_at: '2026-01-01T00:01:00Z',
@@ -22,9 +22,9 @@ describe('useJobPolling', () => {
22
22
  status: 'success',
23
23
  submitted_at: '2026-01-01T00:00:00Z',
24
24
  completed_at: '2026-01-01T00:01:00Z',
25
- task_name: 'test::task',
26
- task_description: 'A test task',
27
- task_parameters: { name: 'nginx' },
25
+ name: 'test::task',
26
+ description: 'A test task',
27
+ parameters: { name: 'nginx' },
28
28
  targets: ['host1.example.com'],
29
29
  smart_proxy: { id: 1, name: 'proxy1' },
30
30
  };
@@ -60,7 +60,7 @@ describe('useJobPolling', () => {
60
60
  test('does not fetch result for INVALID status', async () => {
61
61
  const statusData = {
62
62
  status: 'invalid',
63
- task_name: 'broken::task',
63
+ name: 'broken::task',
64
64
  targets: [],
65
65
  };
66
66
 
@@ -96,7 +96,7 @@ describe('useJobPolling', () => {
96
96
  test('handles result fetch failure gracefully', async () => {
97
97
  const statusData = {
98
98
  status: 'success',
99
- task_name: 'test::task',
99
+ name: 'test::task',
100
100
  targets: [],
101
101
  };
102
102
 
@@ -119,7 +119,7 @@ describe('useJobPolling', () => {
119
119
  test('cancels polling on unmount', async () => {
120
120
  const statusData = {
121
121
  status: 'running',
122
- task_name: 'long::task',
122
+ name: 'long::task',
123
123
  targets: [],
124
124
  };
125
125
  API.get.mockResolvedValue({ data: statusData });
@@ -145,12 +145,12 @@ describe('useJobPolling', () => {
145
145
  test('loads metadata only once across multiple polls', async () => {
146
146
  API.get
147
147
  .mockResolvedValueOnce({
148
- data: { status: 'running', task_name: 'my::task', targets: ['host1'] },
148
+ data: { status: 'running', name: 'my::task', targets: ['host1'] },
149
149
  })
150
150
  .mockResolvedValueOnce({
151
151
  data: {
152
152
  status: 'success',
153
- task_name: 'changed::name',
153
+ name: 'changed::name',
154
154
  targets: ['host2'],
155
155
  },
156
156
  })
@@ -55,9 +55,9 @@ const useJobPolling = jobId => {
55
55
  // Task metadata only needs to be set once since it never changes
56
56
  if (!metadataLoaded.current) {
57
57
  setSubmittedAt(statusData.submitted_at || null);
58
- setTaskName(statusData.task_name || null);
59
- setTaskDescription(statusData.task_description || null);
60
- setTaskParameters(statusData.task_parameters || {});
58
+ setTaskName(statusData.name || null);
59
+ setTaskDescription(statusData.description || null);
60
+ setTaskParameters(statusData.parameters || {});
61
61
  setTargets(statusData.targets || []);
62
62
  setSmartProxy(statusData.smart_proxy || null);
63
63
  metadataLoaded.current = true;
@@ -18,9 +18,9 @@ afterEach(() => {
18
18
  const sampleJobs = [
19
19
  {
20
20
  job_id: 'job-1',
21
- task_name: 'mymod::install',
22
- task_description: 'Install a package',
23
- task_parameters: { name: 'nginx' },
21
+ name: 'mymod::install',
22
+ description: 'Install a package',
23
+ parameters: { name: 'nginx' },
24
24
  status: 'success',
25
25
  targets: ['host1.example.com'],
26
26
  submitted_at: '2026-01-15T10:00:00Z',
@@ -29,9 +29,9 @@ const sampleJobs = [
29
29
  },
30
30
  {
31
31
  job_id: 'job-2',
32
- task_name: 'mymod::mytask',
33
- task_description: 'Restart a service',
34
- task_parameters: {},
32
+ name: 'mymod::mytask',
33
+ description: 'Restart a service',
34
+ parameters: {},
35
35
  status: 'running',
36
36
  targets: ['host2.example.com'],
37
37
  submitted_at: '2026-01-15T11:00:00Z',
@@ -138,9 +138,9 @@ const TaskHistory = () => {
138
138
  <Tr key={job.job_id}>
139
139
  <Td hasRightBorder>
140
140
  <TaskPopover
141
- taskName={job.task_name}
142
- taskDescription={job.task_description}
143
- taskParameters={job.task_parameters}
141
+ taskName={job.name}
142
+ taskDescription={job.description}
143
+ taskParameters={job.parameters}
144
144
  />
145
145
  </Td>
146
146
  <Td hasRightBorder>{getStatusLabel(job.status)}</Td>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_openbolt
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Overlook InfraTech
@@ -18,7 +18,7 @@ dependencies:
18
18
  version: 11.0.0
19
19
  - - "<"
20
20
  - !ruby/object:Gem::Version
21
- version: '13'
21
+ version: '14'
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -28,7 +28,7 @@ dependencies:
28
28
  version: 11.0.0
29
29
  - - "<"
30
30
  - !ruby/object:Gem::Version
31
- version: '13'
31
+ version: '14'
32
32
  description: This plugin adds OpenBolt integration into Foreman, allowing users to
33
33
  run tasks and plans present in their environment.
34
34
  email:
@@ -40,6 +40,11 @@ files:
40
40
  - LICENSE
41
41
  - README.md
42
42
  - Rakefile
43
+ - app/controllers/api/v2/openbolt_jobs_controller.rb
44
+ - app/controllers/api/v2/openbolt_tasks_controller.rb
45
+ - app/controllers/concerns/foreman_openbolt/common.rb
46
+ - app/controllers/concerns/foreman_openbolt/jobs.rb
47
+ - app/controllers/concerns/foreman_openbolt/tasks.rb
43
48
  - app/controllers/foreman_openbolt/task_controller.rb
44
49
  - app/lib/actions/foreman_openbolt/cleanup_proxy_artifacts.rb
45
50
  - app/lib/actions/foreman_openbolt/poll_task_status.rb
@@ -49,6 +54,7 @@ files:
49
54
  - db/migrate/20250819000000_create_openbolt_task_jobs.rb
50
55
  - db/migrate/20250925000000_add_command_to_openbolt_task_jobs.rb
51
56
  - db/migrate/20251001000000_add_task_description_to_task_jobs.rb
57
+ - db/migrate/20260824000000_remove_view_smart_proxies_openbolt_permission.rb
52
58
  - db/seeds.d/001_add_openbolt_feature.rb
53
59
  - lib/foreman_openbolt.rb
54
60
  - lib/foreman_openbolt/engine.rb
@@ -61,6 +67,7 @@ files:
61
67
  - locale/gemspec.rb
62
68
  - package.json
63
69
  - test/acceptance/acceptance_helper.rb
70
+ - test/acceptance/api_acceptance_helper.rb
64
71
  - test/acceptance/docker/docker-compose.yml
65
72
  - test/acceptance/docker/foreman/Dockerfile
66
73
  - test/acceptance/docker/foreman/entrypoint.sh
@@ -79,6 +86,10 @@ files:
79
86
  - test/acceptance/fixtures/modules/acceptance/tasks/target_conditional.json
80
87
  - test/acceptance/fixtures/modules/acceptance/tasks/target_conditional.sh
81
88
  - test/acceptance/fixtures/openbolt.yml
89
+ - test/acceptance/tests/api/authn_authz_test.rb
90
+ - test/acceptance/tests/api/jobs_test.rb
91
+ - test/acceptance/tests/api/launch_test.rb
92
+ - test/acceptance/tests/api/proxy_tasks_test.rb
82
93
  - test/acceptance/tests/error_handling_test.rb
83
94
  - test/acceptance/tests/host_selector_test.rb
84
95
  - test/acceptance/tests/launch_task_test.rb
@@ -89,6 +100,8 @@ files:
89
100
  - test/acceptance/tests/task_history_test.rb
90
101
  - test/acceptance/tests/transport_options_test.rb
91
102
  - test/test_plugin_helper.rb
103
+ - test/unit/controllers/api/v2/openbolt_jobs_controller_test.rb
104
+ - test/unit/controllers/api/v2/openbolt_tasks_controller_test.rb
92
105
  - test/unit/controllers/task_controller_test.rb
93
106
  - test/unit/docker/Dockerfile
94
107
  - test/unit/docker/docker-compose.yml
@@ -180,7 +193,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
180
193
  - !ruby/object:Gem::Version
181
194
  version: '0'
182
195
  requirements: []
183
- rubygems_version: 4.0.6
196
+ rubygems_version: 4.0.16
184
197
  specification_version: 4
185
198
  summary: Foreman OpenBolt integration
186
199
  test_files: []