foreman_snapshot_management 1.0.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.
data/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # ForemanSnapshotManagement
2
+
3
+ ForemanSnapshotManagement is a Foreman plugin to manage snapshots.
4
+ As Hypervisor VMware vSphere is supported.
5
+
6
+ ## Features
7
+
8
+ - List existing snapshots of a virtual machine.
9
+ - Create a snapshot of a virtual machine.
10
+ - Revert existing virtual machine to a previously created snapshot.
11
+ - Remove existing snapshots of a virtual machine.
12
+
13
+ ## Installation
14
+
15
+ See [How_to_Install_a_Plugin](http://projects.theforeman.org/projects/foreman/wiki/How_to_Install_a_Plugin) for how to install Foreman plugins
data/Rakefile ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'ForemanSnapshotManagement'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path('../test/dummy/Rakefile', __FILE__)
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+ task default: :test
37
+
38
+ begin
39
+ require 'rubocop/rake_task'
40
+ RuboCop::RakeTask.new
41
+ rescue => _
42
+ puts 'Rubocop not loaded.'
43
+ end
44
+
45
+ task :default do
46
+ Rake::Task['rubocop'].execute
47
+ end
@@ -0,0 +1,117 @@
1
+ module ForemanSnapshotManagement
2
+ class SnapshotsController < ApplicationController
3
+ before_action :find_host
4
+ before_action :enumerate_snapshots, only: [:index]
5
+ before_action :find_snapshot, only: %i[destroy revert update]
6
+ helper_method :xeditable?
7
+
8
+ def xeditable?(_object = nil)
9
+ true
10
+ end
11
+
12
+ def index
13
+ @new_snapshot = Snapshot.new(host: @host)
14
+ render partial: 'index'
15
+ end
16
+
17
+ # Create a Snapshot.
18
+ #
19
+ # This method creates a Snapshot with a given name and optional description.
20
+ def create
21
+ # Create snapshot
22
+ name = params['snapshot']['name']
23
+ description = params['snapshot']['description'] || ''
24
+ snapshot = Snapshot.new(host: @host, name: name, description: description)
25
+ begin
26
+ task = snapshot.create
27
+
28
+ # Check state of Snapshot creation
29
+ if task['task_state'] == 'success'
30
+ status = _('Snapshot successfully created')
31
+ else
32
+ status = _('Error occurred while creating snapshot: %s') % task['task_state']
33
+ end
34
+
35
+ # Redirect to specific Host page
36
+ redirect_to host_path(@host, anchor: 'snapshots'), flash: { notice: status }
37
+ rescue
38
+ logger.error 'Failed to take snapshot.'
39
+ status = _('Error occurred while creating snapshot.')
40
+ redirect_to host_path(@host, anchor: 'snapshots'), flash: { error: status }
41
+ end
42
+ end
43
+
44
+ # Remove Snapshot
45
+ #
46
+ # This method removes a Snapshot from a given host.
47
+ def destroy
48
+ # Remove Snapshot
49
+ task = @snapshot.destroy
50
+
51
+ # Check state of Snapshot creation
52
+ if task['task_state'] == 'success'
53
+ status = 'Snapshot successfully deleted'
54
+ else
55
+ status = 'Error occurred while removing Snapshot: ' + task['task_state']
56
+ end
57
+
58
+ # Redirect to specific Host page
59
+ redirect_to host_path(@host, anchor: 'snapshots'), flash: { notice: status }
60
+ end
61
+
62
+ # Revert Snapshot
63
+ #
64
+ # This method reverts a host to a given Snapshot.
65
+ def revert
66
+ # Revert Snapshot
67
+ task = @snapshot.revert
68
+
69
+ # Check state of Snapshot creation
70
+ if task['state'] == 'success'
71
+ status = _('VM successfully rolled back')
72
+ else
73
+ status = _('Error occurred while rolling back VM: %s') % task['state']
74
+ end
75
+
76
+ # Redirect to specific Host page
77
+ redirect_to host_path(@host, anchor: 'snapshots'), flash: { notice: status }
78
+ end
79
+
80
+ # Update Snapshot
81
+ #
82
+ # This method renames a Snapshot from a given host.
83
+ def update
84
+ # Rename Snapshot
85
+ @snapshot.name = params['snapshot']['name'] if params['snapshot']['name']
86
+ @snapshot.description = params['snapshot']['description'] if params['snapshot']['description']
87
+ begin
88
+ task = @snapshot.save
89
+ render json: { name: @snapshot.name, description: @snapshot.description }
90
+ rescue
91
+ logger.error "Failed to update snapshot #{@snapshot.id}."
92
+ render json: { errors: _('Failed to update snapshot.') }, status: :unprocessable_entity
93
+ end
94
+ end
95
+
96
+ # Find Host
97
+ #
98
+ # This method is responsible that methods of the controller know the current host.
99
+
100
+ private
101
+
102
+ def find_host
103
+ @host = Host.find_by! name: params['host_id']
104
+ rescue => e
105
+ process_ajax_error e, 'Host not found!'
106
+ end
107
+
108
+ def enumerate_snapshots
109
+ # Hash of Snapshot
110
+ @snapshots = Snapshot.all_for_host @host
111
+ end
112
+
113
+ def find_snapshot
114
+ @snapshot = Snapshot.find_for_host @host, params['id']
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,7 @@
1
+ module ForemanSnapshotManagement
2
+ module SnapshotHelper
3
+ def foreman_snapshot_management_snapshot_path(snapshot)
4
+ host_snapshot_path(host_id: snapshot.host, id: snapshot.id)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,72 @@
1
+ module ForemanSnapshotManagement
2
+ module SnapshotadministrationHelper
3
+ end
4
+ end
5
+
6
+ module Fog
7
+ module Compute
8
+ class Vsphere
9
+ class Real
10
+ # Extends fog-vsphere gem for a remove Snapshot method.
11
+ def remove_snapshot(options = {})
12
+ raise ArgumentError, 'snapshot is a required parameter' unless options.key? 'snapshot'
13
+ raise ArgumentError, 'removeChildren is a required parameter' unless options.key? 'removeChildren'
14
+
15
+ unless Snapshot === options['snapshot']
16
+ raise ArgumentError, 'snapshot is a required parameter'
17
+ end
18
+
19
+ task = options['snapshot'].mo_ref.RemoveSnapshot_Task(
20
+ removeChildren: options['removeChildren']
21
+ )
22
+ task.wait_for_completion
23
+
24
+ {
25
+ 'task_state' => task.info.state
26
+ }
27
+ end
28
+
29
+ # Extends fog-vsphere gem for a rename Snapshot method.
30
+ # TODO: Add info state
31
+ def rename_snapshot(options = {})
32
+ raise ArgumentError, 'snapshot is a required parameter' unless options.key? 'snapshot'
33
+ raise ArgumentError, 'name is a required parameter' unless options.key? 'name'
34
+ raise ArgumentError, 'description is a required parameter' unless options.key? 'description'
35
+
36
+ unless Snapshot === options['snapshot']
37
+ raise ArgumentError, 'snapshot is a required parameter'
38
+ end
39
+
40
+ task = options['snapshot'].mo_ref.RenameSnapshot(
41
+ name: options['name'],
42
+ description: options['description']
43
+ )
44
+ # task.wait_for_completion
45
+
46
+ # {
47
+ # 'task_state' => task.info.state
48
+ # }
49
+ end
50
+ end
51
+ class Mock
52
+ def remove_snapshot(snapshot)
53
+ raise ArgumentError, 'snapshot is a required parameter' unless options.key? 'snapshot'
54
+ raise ArgumentError, 'removeChildren is a required parameter' unless options.key? 'removeChildren'
55
+ raise ArgumentError, 'snapshot is a required parameter' if snapshot.nil?
56
+ {
57
+ 'task_state' => 'success'
58
+ }
59
+ end
60
+
61
+ def rename_snapshot(_snapshot)
62
+ raise ArgumentError, 'snapshot is a required parameter' unless options.key? 'snapshot'
63
+ raise ArgumentError, 'name is a required parameter' unless options.key? 'name'
64
+ raise ArgumentError, 'description is a required parameter' unless options.key? 'description'
65
+ {
66
+ 'task_state' => 'success'
67
+ }
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,73 @@
1
+ module ForemanSnapshotManagement
2
+ class Snapshot
3
+ extend ActiveModel::Callbacks
4
+ include ActiveModel::Conversion
5
+ include ActiveModel::Model
6
+ include ActiveModel::Dirty
7
+
8
+ define_model_callbacks :create, :save, :destroy, :revert
9
+ attr_accessor :id, :vmware_snapshot, :name, :description, :host_id
10
+
11
+ def self.add_snapshot_with_children(snapshots, host, vmware_snapshot)
12
+ snapshots[vmware_snapshot.ref] = new_from_vmware(host, vmware_snapshot)
13
+ vmware_snapshot.child_snapshots.each do |snap|
14
+ add_snapshot_with_children(snapshots, host, snap)
15
+ end
16
+ end
17
+
18
+ def self.all_for_host(host)
19
+ snapshots = {}
20
+ root_snapshot = host.compute_resource.get_snapshots(host.uuid).first
21
+ add_snapshot_with_children(snapshots, host, root_snapshot) if root_snapshot
22
+ snapshots
23
+ end
24
+
25
+ def self.find_for_host(host, id)
26
+ all_for_host(host)[id]
27
+ end
28
+
29
+ def self.new_from_vmware(host, vmware_snapshot)
30
+ new(host: host, id: vmware_snapshot.ref, vmware_snapshot: vmware_snapshot, name: vmware_snapshot.name, description: vmware_snapshot.description)
31
+ end
32
+
33
+ def persisted?
34
+ @id.present?
35
+ end
36
+
37
+ # host accessors
38
+ def host
39
+ Host.find(@host_id)
40
+ end
41
+
42
+ def host=(host)
43
+ @host_id = host.id
44
+ end
45
+
46
+ # crud
47
+ def create
48
+ run_callbacks(:create) do
49
+ host.compute_resource.create_snapshot(host.uuid, name, description)
50
+ end
51
+ end
52
+
53
+ def save
54
+ run_callbacks(:save) do
55
+ host.compute_resource.update_snapshot(vmware_snapshot, name, description)
56
+ end
57
+ end
58
+
59
+ def destroy
60
+ run_callbacks(:destroy) do
61
+ result = host.compute_resource.remove_snapshot(vmware_snapshot, false)
62
+ id = nil
63
+ result
64
+ end
65
+ end
66
+
67
+ def revert
68
+ run_callbacks(:revert) do
69
+ host.compute_resource.revert_snapshot(vmware_snapshot)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,40 @@
1
+ module ForemanSnapshotManagement
2
+ module VmwareExtensions
3
+ extend ActiveSupport::Concern
4
+
5
+ # Create a Snapshot.
6
+ #
7
+ # This method creates a Snapshot with a given name and optional description.
8
+ def create_snapshot(uuid, name, description)
9
+ client.vm_take_snapshot('instance_uuid' => uuid, 'name' => name, 'description' => description)
10
+ end
11
+
12
+ # Remove Snapshot
13
+ #
14
+ # This method removes a Snapshot from a given host.
15
+ def remove_snapshot(snapshot, removeChildren)
16
+ client.remove_snapshot('snapshot' => snapshot, 'removeChildren' => removeChildren)
17
+ end
18
+
19
+ # Revert Snapshot
20
+ #
21
+ # This method revert a host to a given Snapshot.
22
+ def revert_snapshot(snapshot)
23
+ client.revert_to_snapshot(snapshot)
24
+ end
25
+
26
+ # Update Snapshot
27
+ #
28
+ # This method renames a Snapshot from a given host.
29
+ def update_snapshot(snapshot, name, description)
30
+ client.rename_snapshot('snapshot' => snapshot, 'name' => name, 'description' => description)
31
+ end
32
+
33
+ # Get Snapshots
34
+ #
35
+ # This methods returns Snapshots from a given host.
36
+ def get_snapshots(server_id)
37
+ client.snapshots(server_id: server_id)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,21 @@
1
+ tab = "<% if @host.provider == 'VMware' %>
2
+ <li><a href='#snapshots' data-toggle='tab'><%= _('Snapshots') %></a></li>
3
+ <% end %>"
4
+
5
+ tab_content = "<div id='snapshots' class='tab-pane'
6
+ data-ajax-url='<%= host_snapshots_path(host_id: @host)%>'
7
+ data-on-complete='onContentLoad'>
8
+ <%= spinner(_('Loading Parameters information ...')) %>
9
+ </div>"
10
+
11
+ # Add a Snapshots tab in the view of a host
12
+ Deface::Override.new(virtual_path: 'hosts/show',
13
+ name: 'add_host_snapshot_tab',
14
+ insert_bottom: 'ul',
15
+ text: tab)
16
+
17
+ # Load content of Snapshots tab
18
+ Deface::Override.new(virtual_path: 'hosts/show',
19
+ name: 'add_host_snapshots_tab_content',
20
+ insert_bottom: 'div#myTabContent',
21
+ text: tab_content)
@@ -0,0 +1,57 @@
1
+ <%= form_for @new_snapshot, as: :snapshot, url: host_snapshots_path(@host), html: {class: ""} do |f| %>
2
+ <table class="<%= table_css_classes %>">
3
+ <thead>
4
+ <tr>
5
+ <th class="col-md-1"><%= _('Snapshot') %></th>
6
+ <th class="col-md-2"><%= _('Description') %></th>
7
+ <th class="col-md-1"><%= _('Action') %></th>
8
+ </tr>
9
+ </thead>
10
+ <tbody>
11
+ <tr>
12
+ <td>
13
+ <%= f.text_field :name, class: 'form-control' %>
14
+ </td>
15
+ <td>
16
+ <%= f.text_field :description, class: 'form-control' %>
17
+ </td>
18
+ <td>
19
+ <%= f.submit _('Create'), class: 'btn btn-success' %>
20
+ </td>
21
+ </tr>
22
+ <% @snapshots.each do |ref, snap| %>
23
+ <tr>
24
+ <td>
25
+ <%= edit_textfield snap, :name %>
26
+ </td>
27
+ <td>
28
+ <%= edit_textarea snap, :description %>
29
+ </td>
30
+ <td>
31
+ <%= action_buttons(
32
+ display_link_if_authorized(_('Rollback'), hash_for_revert_host_snapshot_path(host_id: @host, id: ref), method: :put, class: 'btn btn-primary', data: {confirm: _("Are you sure to revert this Snapshot?")}),
33
+ display_delete_if_authorized(hash_for_host_snapshot_path(host_id: @host, id: ref), data: {confirm: _("Are you sure to delete this Snapshot?")}),
34
+ ) %>
35
+ </td>
36
+ </tr>
37
+ <% end %>
38
+ </tbody>
39
+ </table>
40
+ <% end %>
41
+
42
+ <script type="text/javascript">
43
+ //<![CDATA[
44
+ $(document).ready(function() {
45
+ $('.editable').editable({
46
+ params: {
47
+ authenticity_token: AUTH_TOKEN
48
+ },
49
+ error: function(response) {
50
+ return $.parseJSON(response.responseText).errors;
51
+ }
52
+ });
53
+ var hash = window.location.hash;
54
+ hash && $('ul.nav a[href="' + hash + '"]').tab('show');
55
+ });
56
+ //]]>
57
+ </script>