foreman_snapshot_management 1.6.1 → 2.0.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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -2
  3. data/Rakefile +7 -2
  4. data/app/controllers/api/v2/snapshots_controller.rb +37 -7
  5. data/app/controllers/concerns/foreman/controller/parameters/snapshot.rb +1 -1
  6. data/app/controllers/foreman_snapshot_management/snapshots_controller.rb +5 -5
  7. data/app/models/concerns/fog_extensions/proxmox/snapshots/mock.rb +24 -0
  8. data/app/models/foreman_snapshot_management/proxmox_extensions.rb +101 -0
  9. data/app/models/foreman_snapshot_management/snapshot.rb +28 -28
  10. data/app/models/foreman_snapshot_management/vmware_extensions.rb +40 -13
  11. data/app/views/api/v2/snapshots/base.json.rabl +2 -0
  12. data/app/views/api/v2/snapshots/main.json.rabl +2 -2
  13. data/app/views/foreman_snapshot_management/snapshots/_index.html.erb +12 -74
  14. data/app/views/hosts/_snapshots_tab.html.erb +8 -0
  15. data/lib/foreman_snapshot_management/engine.rb +35 -16
  16. data/lib/foreman_snapshot_management/version.rb +1 -1
  17. data/lib/tasks/foreman_snapshot_management_tasks.rake +2 -2
  18. data/locale/de/LC_MESSAGES/foreman_snapshot_management.mo +0 -0
  19. data/locale/de/foreman_snapshot_management.po +195 -0
  20. data/locale/en/LC_MESSAGES/foreman_snapshot_management.mo +0 -0
  21. data/locale/en/foreman_snapshot_management.po +179 -11
  22. data/locale/foreman_snapshot_management.pot +259 -8
  23. data/locale/gemspec.rb +1 -1
  24. data/package.json +46 -0
  25. data/test/controllers/api/v2/snapshots_test.rb +250 -39
  26. data/test/controllers/foreman_snapshot_management/snapshots_controller_test.rb +61 -9
  27. data/test/factories/proxmox_factory.rb +18 -0
  28. data/test/test_plugin_helper.rb +3 -0
  29. data/webpack/components/SnapshotManagement/SnapshotManagement.js +84 -0
  30. data/webpack/components/SnapshotManagement/SnapshotManagementActions.js +212 -0
  31. data/webpack/components/SnapshotManagement/SnapshotManagementConstants.js +9 -0
  32. data/webpack/components/SnapshotManagement/SnapshotManagementReducer.js +100 -0
  33. data/webpack/components/SnapshotManagement/SnapshotManagementSelectors.js +8 -0
  34. data/webpack/components/SnapshotManagement/__tests__/SnapshotManagementActions.test.js +123 -0
  35. data/webpack/components/SnapshotManagement/__tests__/SnapshotManagementReducer.test.js +157 -0
  36. data/webpack/components/SnapshotManagement/__tests__/__snapshots__/SnapshotManagementActions.test.js.snap +314 -0
  37. data/webpack/components/SnapshotManagement/__tests__/__snapshots__/SnapshotManagementReducer.test.js.snap +214 -0
  38. data/webpack/components/SnapshotManagement/components/SnapshotForm/SnapshotForm.js +118 -0
  39. data/webpack/components/SnapshotManagement/components/SnapshotForm/SnapshotFormConstants.js +5 -0
  40. data/webpack/components/SnapshotManagement/components/SnapshotForm/__tests__/SnapshotForm.test.js +26 -0
  41. data/webpack/components/SnapshotManagement/components/SnapshotForm/__tests__/__snapshots__/SnapshotForm.test.js.snap +476 -0
  42. data/webpack/components/SnapshotManagement/components/SnapshotForm/index.js +19 -0
  43. data/webpack/components/SnapshotManagement/components/SnapshotForm/snapshotForm.scss +3 -0
  44. data/webpack/components/SnapshotManagement/components/SnapshotFormModal/SnapshotFormModal.js +37 -0
  45. data/webpack/components/SnapshotManagement/components/SnapshotFormModal/SnapshotFormModalConstants.js +1 -0
  46. data/webpack/components/SnapshotManagement/components/SnapshotFormModal/__tests__/SnapshotFormModal.test.js +19 -0
  47. data/webpack/components/SnapshotManagement/components/SnapshotFormModal/__tests__/__snapshots__/SnapshotFormModal.test.js.snap +19 -0
  48. data/webpack/components/SnapshotManagement/components/SnapshotFormModal/index.js +12 -0
  49. data/webpack/components/SnapshotManagement/components/SnapshotFormModal/useSnapshotFormModal.js +7 -0
  50. data/webpack/components/SnapshotManagement/components/SnapshotList/SnapshotList.js +314 -0
  51. data/webpack/components/SnapshotManagement/components/SnapshotList/SnapshotListHelper.js +70 -0
  52. data/webpack/components/SnapshotManagement/components/SnapshotList/__tests__/SnapshotList.test.js +88 -0
  53. data/webpack/components/SnapshotManagement/components/SnapshotList/__tests__/__snapshots__/SnapshotList.test.js.snap +1081 -0
  54. data/webpack/components/SnapshotManagement/components/SnapshotList/snapshotList.scss +13 -0
  55. data/webpack/components/SnapshotManagement/index.js +33 -0
  56. data/webpack/components/SnapshotManagement/snapshotManagement.scss +5 -0
  57. data/webpack/global_index.js +7 -0
  58. data/webpack/global_test_setup.js +11 -0
  59. data/webpack/index.js +8 -0
  60. data/webpack/reducers.js +7 -0
  61. data/webpack/test_setup.js +17 -0
  62. metadata +50 -37
@@ -1,39 +1,61 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'test_helper'
3
+ require 'test_plugin_helper'
4
4
 
5
5
  module ForemanSnapshotManagement
6
6
  class SnapshotsControllerTest < ActionController::TestCase
7
- let(:tax_location) { Location.find_by_name('Location 1') }
8
- let(:tax_organization) { Organization.find_by_name('Organization 1') }
7
+ let(:tax_location) { Location.find_by(name: 'Location 1') }
8
+ let(:tax_organization) { Organization.find_by(name: 'Organization 1') }
9
9
  let(:compute_resource) do
10
10
  cr = FactoryBot.create(:compute_resource, :vmware, :uuid => 'Solutions', :locations => [tax_location], organizations: [tax_organization])
11
- ComputeResource.find_by_id(cr.id)
11
+ ComputeResource.find_by(id: cr.id)
12
12
  end
13
13
  let(:uuid) { '5032c8a5-9c5e-ba7a-3804-832a03e16381' }
14
14
  let(:uuid2) { 'a7169e20-74d3-4367-afc2-d355716e7555' }
15
15
  let(:host) { FactoryBot.create(:host, :managed, :compute_resource => compute_resource, :uuid => uuid) }
16
16
  let(:host2) { FactoryBot.create(:host, :managed, :compute_resource => compute_resource, :uuid => uuid2) }
17
17
  let(:snapshot_id) { 'snapshot-0101' }
18
+ let(:proxmox_compute_resource) do
19
+ FactoryBot.create(:proxmox_cr)
20
+ ComputeResource.find_by(type: 'ForemanFogProxmox::Proxmox')
21
+ end
22
+ let(:vmid) { '1_100' }
23
+ let(:proxmox_host) { FactoryBot.create(:host, :managed, :compute_resource => proxmox_compute_resource, :uuid => vmid) }
24
+ let(:proxmox_snapshot) { 'snapshot1' }
18
25
  setup { ::Fog.mock! }
19
26
  teardown { ::Fog.unmock! }
20
27
 
21
28
  context 'GET #index' do
22
- test 'should get index' do
29
+ test 'should get VMware snapshot index' do
23
30
  get :index, params: { :host_id => host.to_param }, session: set_session_user
24
31
  assert_response :success
25
32
  assert_not_nil assigns(:snapshots)
26
33
  assert_template 'foreman_snapshot_management/snapshots/_index'
27
34
  end
35
+
36
+ test 'should get Proxmox index' do
37
+ Host::Managed.any_instance.stubs(:vm_exists?).returns(false)
38
+ get :index, params: { :host_id => proxmox_host.to_param }, session: set_session_user
39
+ assert_response :success
40
+ assert_not_nil assigns(:snapshots)
41
+ assert_template 'foreman_snapshot_management/snapshots/_index'
42
+ end
28
43
  end
29
44
 
30
45
  context 'POST #create' do
31
- test 'create valid' do
46
+ test 'create valid VMware snapshot' do
32
47
  post :create, params: { :host_id => host.to_param, :snapshot => { :name => 'test' } }, session: set_session_user
33
48
  assert_redirected_to host_url(host, :anchor => 'snapshots')
34
49
  assert_includes flash[:notice] || flash[:success], 'Successfully created Snapshot.'
35
50
  end
36
51
 
52
+ test 'create valid proxmox snapshot' do
53
+ Host::Managed.any_instance.stubs(:vm_exists?).returns(false)
54
+ post :create, params: { :host_id => proxmox_host.to_param, :snapshot => { :name => 'test' } }, session: set_session_user
55
+ assert_redirected_to host_url(proxmox_host, :anchor => 'snapshots')
56
+ assert_includes flash[:notice] || flash[:success], 'Successfully created Snapshot.'
57
+ end
58
+
37
59
  test 'create valid multiple' do
38
60
  post :create_multiple_host, params: { :host_ids => [host.id, host2.id], :snapshot => { :name => 'test' } }, session: set_session_user
39
61
  assert_redirected_to hosts_url
@@ -62,6 +84,13 @@ module ForemanSnapshotManagement
62
84
  assert_includes flash[:notice] || flash[:success], 'Successfully deleted Snapshot.'
63
85
  end
64
86
 
87
+ test 'destroy successful' do
88
+ Host::Managed.any_instance.stubs(:vm_exists?).returns(false)
89
+ delete :destroy, params: { :host_id => proxmox_host.to_param, :id => proxmox_snapshot }, session: set_session_user
90
+ assert_redirected_to host_url(proxmox_host, :anchor => 'snapshots')
91
+ assert_includes flash[:notice] || flash[:success], 'Successfully deleted Snapshot.'
92
+ end
93
+
65
94
  test 'destroy with error' do
66
95
  ForemanSnapshotManagement::Snapshot.any_instance.stubs(:destroy).returns(false)
67
96
  delete :destroy, params: { :host_id => host.to_param, :id => snapshot_id }, session: set_session_user
@@ -71,12 +100,19 @@ module ForemanSnapshotManagement
71
100
  end
72
101
 
73
102
  context 'PUT #revert' do
74
- test 'revert successful' do
103
+ test 'revert successful VMware' do
75
104
  put :revert, params: { :host_id => host.to_param, :id => snapshot_id }, session: set_session_user
76
105
  assert_redirected_to host_url(host, :anchor => 'snapshots')
77
106
  assert_includes flash[:notice] || flash[:success], 'VM successfully rolled back.'
78
107
  end
79
108
 
109
+ test 'revert successful proxmox snapshot' do
110
+ Host::Managed.any_instance.stubs(:vm_exists?).returns(false)
111
+ put :revert, params: { :host_id => proxmox_host.to_param, :id => proxmox_snapshot }, session: set_session_user
112
+ assert_redirected_to host_url(proxmox_host, :anchor => 'snapshots')
113
+ assert_includes flash[:notice] || flash[:success], 'VM successfully rolled back.'
114
+ end
115
+
80
116
  test 'revert with error' do
81
117
  ForemanSnapshotManagement::Snapshot.any_instance.stubs(:revert).returns(false)
82
118
  put :revert, params: { :host_id => host.to_param, :id => snapshot_id }, session: set_session_user
@@ -86,7 +122,7 @@ module ForemanSnapshotManagement
86
122
  end
87
123
 
88
124
  context 'PUT #update' do
89
- test 'update successful' do
125
+ test 'update successful VMware snapsoht' do
90
126
  data = { 'name' => 'test 2', 'description' => '' }
91
127
  put :update, params: { :host_id => host.to_param, :id => snapshot_id, :snapshot => data }, session: set_session_user
92
128
  assert_response :success
@@ -94,11 +130,27 @@ module ForemanSnapshotManagement
94
130
  assert_equal(data, body)
95
131
  end
96
132
 
97
- test 'update with error' do
133
+ test 'update successful proxmox' do
134
+ Host::Managed.any_instance.stubs(:vm_exists?).returns(false)
135
+ data = { 'name' => 'snapshot1', 'description' => 'updated snapshot1' }
136
+ put :update, params: { :host_id => proxmox_host.to_param, :id => proxmox_snapshot, :snapshot => data }, session: set_session_user
137
+ assert_response :success
138
+ body = ActiveSupport::JSON.decode(@response.body)
139
+ assert_equal(data, body)
140
+ end
141
+
142
+ test 'update with error VMware snapshot' do
98
143
  ForemanSnapshotManagement::Snapshot.any_instance.stubs(:save).returns(false)
99
144
  put :update, params: { :host_id => host.to_param, :id => snapshot_id, :snapshot => { :name => 'test 2' } }, session: set_session_user
100
145
  assert_response :unprocessable_entity
101
146
  end
147
+
148
+ test 'update with error proxmox snapshot' do
149
+ Host::Managed.any_instance.stubs(:vm_exists?).returns(false)
150
+ ForemanSnapshotManagement::Snapshot.any_instance.stubs(:save).returns(false)
151
+ put :update, params: { :host_id => proxmox_host.to_param, :id => proxmox_snapshot, :snapshot => { :name => 'snapshot1', :description => 'fail' } }, session: set_session_user
152
+ assert_response :unprocessable_entity
153
+ end
102
154
  end
103
155
  end
104
156
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :proxmox_resource, :class => ComputeResource do
5
+ sequence(:name) { |n| "compute_resource_proxmox#{n}" }
6
+ organizations { [Organization.find_by(name: 'Organization 1')] }
7
+ locations { [Location.find_by(name: 'Location 1')] }
8
+
9
+ trait :proxmox do
10
+ provider { 'Proxmox' }
11
+ user { 'root@pam' }
12
+ password { 'proxmox01' }
13
+ url { 'https://192.168.56.101:8006/api2/json' }
14
+ end
15
+
16
+ factory :proxmox_cr, :class => ForemanFogProxmox::Proxmox, :traits => [:proxmox]
17
+ end
18
+ end
@@ -2,3 +2,6 @@
2
2
 
3
3
  # This calls the main test_helper in Foreman-core
4
4
  require 'test_helper'
5
+
6
+ FactoryBot.definition_file_paths << File.join(File.dirname(__FILE__), 'factories')
7
+ FactoryBot.reload
@@ -0,0 +1,84 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Button } from 'patternfly-react';
4
+
5
+ import { translate as __ } from 'foremanReact/common/I18n';
6
+
7
+ import SnapshotFormModal from './components/SnapshotFormModal';
8
+ import useSnapshotFormModal from './components/SnapshotFormModal/useSnapshotFormModal';
9
+ import SnapshotList from './components/SnapshotList/SnapshotList';
10
+ import './snapshotManagement.scss';
11
+
12
+ const SnapshotManagement = ({ canCreate, host, ...props }) => {
13
+ const children = [];
14
+ const { setModalOpen, setModalClosed } = useSnapshotFormModal();
15
+
16
+ const onCreateClick = () => {
17
+ setModalOpen();
18
+ };
19
+ const allowedHostAttr = ['id', 'name'];
20
+ const filteredHost = Object.keys(host)
21
+ .filter(key => allowedHostAttr.includes(key))
22
+ .reduce(
23
+ (obj, key) => ({
24
+ ...obj,
25
+ [key]: host[key],
26
+ }),
27
+ {}
28
+ );
29
+
30
+ if (canCreate) {
31
+ children.push(
32
+ <SnapshotFormModal
33
+ key="snapshot-form-modal"
34
+ setModalClosed={setModalClosed}
35
+ host={filteredHost}
36
+ hostId={host.id}
37
+ {...props}
38
+ />
39
+ );
40
+ children.push(
41
+ <Button
42
+ key="snapshot-create-button"
43
+ onClick={onCreateClick}
44
+ className="snapshot-create"
45
+ >
46
+ {__('Create Snapshot')}
47
+ </Button>
48
+ );
49
+ }
50
+
51
+ children.push(
52
+ <SnapshotList key="snapshot-list" host={filteredHost} {...props} />
53
+ );
54
+
55
+ return <div>{children}</div>;
56
+ };
57
+
58
+ SnapshotManagement.propTypes = {
59
+ host: PropTypes.shape({
60
+ id: PropTypes.number.isRequired,
61
+ name: PropTypes.string.isRequired,
62
+ }).isRequired,
63
+ canCreate: PropTypes.bool,
64
+ canUpdate: PropTypes.bool,
65
+ canRevert: PropTypes.bool,
66
+ canDelete: PropTypes.bool,
67
+ capabilities: PropTypes.shape({
68
+ editSnapshotName: PropTypes.bool,
69
+ limitSnapshotNameFormat: PropTypes.bool,
70
+ }),
71
+ };
72
+
73
+ SnapshotManagement.defaultProps = {
74
+ canCreate: false,
75
+ canUpdate: false,
76
+ canRevert: false,
77
+ canDelete: false,
78
+ capabilities: {
79
+ editSnapshotName: true,
80
+ limitSnapshotNameFormat: false,
81
+ },
82
+ };
83
+
84
+ export default SnapshotManagement;
@@ -0,0 +1,212 @@
1
+ import { API, actionTypeGenerator } from 'foremanReact/redux/API';
2
+ import { sprintf, translate as __ } from 'foremanReact/common/I18n';
3
+ import { addToast } from 'foremanReact/redux/actions/toasts';
4
+
5
+ import {
6
+ SNAPSHOT_LIST,
7
+ SNAPSHOT_LIST_URL,
8
+ SNAPSHOT_DELETE,
9
+ SNAPSHOT_DELETE_URL,
10
+ SNAPSHOT_UPDATE,
11
+ SNAPSHOT_UPDATE_URL,
12
+ SNAPSHOT_ROLLBACK,
13
+ SNAPSHOT_ROLLBACK_URL,
14
+ } from './SnapshotManagementConstants';
15
+
16
+ export const loadSnapshotList = hostId => async dispatch => {
17
+ const { REQUEST, SUCCESS, FAILURE } = actionTypeGenerator(SNAPSHOT_LIST);
18
+
19
+ dispatch({
20
+ type: REQUEST,
21
+ payload: { hostId },
22
+ });
23
+ try {
24
+ const { data } = await API.get(
25
+ SNAPSHOT_LIST_URL.replace(':host_id', hostId)
26
+ );
27
+ return dispatch({
28
+ type: SUCCESS,
29
+ payload: { hostId },
30
+ response: data,
31
+ });
32
+ } catch (error) {
33
+ return dispatch({
34
+ type: FAILURE,
35
+ payload: { hostId },
36
+ response: error,
37
+ });
38
+ }
39
+ };
40
+
41
+ export const snapshotDeleteAction = (host, rowData) => async dispatch => {
42
+ const { REQUEST, SUCCESS, FAILURE } = actionTypeGenerator(SNAPSHOT_DELETE);
43
+
44
+ dispatch({
45
+ type: REQUEST,
46
+ payload: {
47
+ host,
48
+ id: rowData.id,
49
+ },
50
+ });
51
+ try {
52
+ const { data } = await API.delete(
53
+ sprintf(SNAPSHOT_DELETE_URL, host.id, rowData.id)
54
+ );
55
+
56
+ dispatch(
57
+ addToast({
58
+ type: 'success',
59
+ message: sprintf(
60
+ __('Successfully removed Snapshot "%s" from host %s'),
61
+ rowData.name,
62
+ host.name
63
+ ),
64
+ key: SUCCESS,
65
+ })
66
+ );
67
+ dispatch(loadSnapshotList(host.id));
68
+ return dispatch({
69
+ type: SUCCESS,
70
+ payload: {
71
+ host,
72
+ id: rowData.id,
73
+ },
74
+ response: data,
75
+ });
76
+ } catch (error) {
77
+ dispatch(
78
+ addToast({
79
+ type: 'error',
80
+ message: sprintf(
81
+ __('Error occurred while removing Snapshot: %s'),
82
+ error
83
+ ),
84
+ key: FAILURE,
85
+ })
86
+ );
87
+ return dispatch({
88
+ type: FAILURE,
89
+ payload: {
90
+ host,
91
+ id: rowData.id,
92
+ },
93
+ response: error,
94
+ });
95
+ }
96
+ };
97
+
98
+ export const snapshotUpdateAction = (host, rowData) => async dispatch => {
99
+ const { REQUEST, SUCCESS, FAILURE } = actionTypeGenerator(SNAPSHOT_UPDATE);
100
+
101
+ dispatch({
102
+ type: REQUEST,
103
+ payload: {
104
+ host,
105
+ id: rowData.id,
106
+ snapshot: {
107
+ name: rowData.name,
108
+ description: rowData.description,
109
+ },
110
+ },
111
+ });
112
+ try {
113
+ const { data } = await API.put(
114
+ sprintf(SNAPSHOT_UPDATE_URL, host.id, rowData.id),
115
+ {
116
+ snapshot: {
117
+ name: rowData.name,
118
+ description: rowData.description,
119
+ },
120
+ }
121
+ );
122
+ dispatch(
123
+ addToast({
124
+ type: 'success',
125
+ message: sprintf(
126
+ __('Successfully updated Snapshot "%s"'),
127
+ rowData.name
128
+ ),
129
+ key: SUCCESS,
130
+ })
131
+ );
132
+ return dispatch({
133
+ type: SUCCESS,
134
+ payload: {
135
+ host,
136
+ id: rowData.id,
137
+ },
138
+ response: data,
139
+ });
140
+ } catch (error) {
141
+ dispatch(
142
+ addToast({
143
+ type: 'error',
144
+ message: sprintf(
145
+ __('Error occurred while updating Snapshot: %s'),
146
+ error
147
+ ),
148
+ key: FAILURE,
149
+ })
150
+ );
151
+ return dispatch({
152
+ type: FAILURE,
153
+ payload: {
154
+ host,
155
+ id: rowData.id,
156
+ },
157
+ response: error,
158
+ });
159
+ }
160
+ };
161
+
162
+ export const snapshotRollbackAction = (host, rowData) => async dispatch => {
163
+ const { REQUEST, SUCCESS, FAILURE } = actionTypeGenerator(SNAPSHOT_ROLLBACK);
164
+
165
+ dispatch({
166
+ type: REQUEST,
167
+ payload: {
168
+ host,
169
+ id: rowData.id,
170
+ },
171
+ });
172
+ try {
173
+ const { data } = await API.put(
174
+ sprintf(SNAPSHOT_ROLLBACK_URL, host.id, rowData.id)
175
+ );
176
+ dispatch(
177
+ addToast({
178
+ type: 'success',
179
+ message: sprintf(
180
+ __('Successfully rolled back Snapshot "%s" on host %s'),
181
+ rowData.name,
182
+ host.name
183
+ ),
184
+ key: SUCCESS,
185
+ })
186
+ );
187
+ return dispatch({
188
+ type: SUCCESS,
189
+ payload: {
190
+ host,
191
+ id: rowData.id,
192
+ },
193
+ response: data,
194
+ });
195
+ } catch (error) {
196
+ dispatch(
197
+ addToast({
198
+ type: 'error',
199
+ message: sprintf(__('Error occurred while rolling back VM: %s'), error),
200
+ key: FAILURE,
201
+ })
202
+ );
203
+ return dispatch({
204
+ type: FAILURE,
205
+ payload: {
206
+ host,
207
+ id: rowData.id,
208
+ },
209
+ response: error,
210
+ });
211
+ }
212
+ };