foreman_wds 0.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.
- checksums.yaml +7 -0
- data/.eslintrc.json +20 -0
- data/.gitignore +17 -0
- data/.rubocop.yml +66 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +33 -0
- data/Rakefile +10 -0
- data/app/assets/javascripts/foreman_wds/wds_servers.js +19 -0
- data/app/assets/javascripts/host_edit_extensions.js +50 -0
- data/app/controllers/concerns/foreman/controller/parameters/wds_server.rb +19 -0
- data/app/controllers/concerns/foreman_wds/hosts_controller_extensions.rb +18 -0
- data/app/controllers/concerns/foreman_wds/unattended_controller_extensions.rb +21 -0
- data/app/controllers/wds_servers_controller.rb +85 -0
- data/app/lib/foreman_wds/wds_boot_image.rb +11 -0
- data/app/lib/foreman_wds/wds_image.rb +89 -0
- data/app/lib/foreman_wds/wds_install_image.rb +14 -0
- data/app/models/concerns/foreman_wds/compute_resource_extensions.rb +7 -0
- data/app/models/concerns/foreman_wds/host_extensions.rb +109 -0
- data/app/models/concerns/foreman_wds/nic_extensions.rb +40 -0
- data/app/models/foreman_wds/wds_facet.rb +48 -0
- data/app/models/wds_server.rb +239 -0
- data/app/services/wds_image_cache.rb +66 -0
- data/app/views/foreman_wds/unattend_2016.xml.erb +220 -0
- data/app/views/foreman_wds/windows_ptable.xml.erb +43 -0
- data/app/views/hosts/provision_method/wds/_form.html.erb +12 -0
- data/app/views/wds_servers/_form.html.erb +28 -0
- data/app/views/wds_servers/_image_select.html.erb +27 -0
- data/app/views/wds_servers/_server_select.html.erb +12 -0
- data/app/views/wds_servers/clients/_list.html.erb +43 -0
- data/app/views/wds_servers/edit.html.erb +3 -0
- data/app/views/wds_servers/images/_list.html.erb +36 -0
- data/app/views/wds_servers/index.html.erb +25 -0
- data/app/views/wds_servers/new.html.erb +3 -0
- data/app/views/wds_servers/show.html.erb +46 -0
- data/config/routes.rb +26 -0
- data/db/migrate/20180426133700_add_wds_servers.rb +23 -0
- data/db/seeds.d/50_ptable_templates.rb +16 -0
- data/db/seeds.d/50_unattend_templates.rb +19 -0
- data/foreman_wds.gemspec +23 -0
- data/lib/foreman_wds.rb +4 -0
- data/lib/foreman_wds/engine.rb +70 -0
- data/lib/foreman_wds/version.rb +3 -0
- data/test/foreman_wds_test.rb +11 -0
- data/test/test_helper.rb +4 -0
- metadata +147 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 13cda19881b8df7132970cb27a73bea4bc834abbbff33e2831ed98625cffa48a
|
4
|
+
data.tar.gz: 74f4f4f15f7ca7c17a5453b01ac60883279add2cf37b015b4fae54b38d7d3511
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9deefe383a62458a87f288fc9b84825b236ccc10b05f25a983ed0a0e39467d66684ea2e093778d82e17c8756c6840f492069125be4b2208249e18ac9c4087792
|
7
|
+
data.tar.gz: 69e76584c0939c1a509d13774e936452bb5f38e79098491d81d6eb52e5136ca06813aeacbb70db357bb258cec2252baedb9d15247ce7f6715c0604104b26a576
|
data/.eslintrc.json
ADDED
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
---
|
2
|
+
AllCops:
|
3
|
+
TargetRubyVersion: 2.3
|
4
|
+
TargetRailsVersion: 5.1
|
5
|
+
Exclude:
|
6
|
+
- '*.spec'
|
7
|
+
- 'Rakefile'
|
8
|
+
|
9
|
+
Rails:
|
10
|
+
Enabled: true
|
11
|
+
|
12
|
+
# Don't enforce documentation
|
13
|
+
Style/Documentation:
|
14
|
+
Enabled: false
|
15
|
+
|
16
|
+
Metrics/MethodLength:
|
17
|
+
Max: 40
|
18
|
+
|
19
|
+
Metrics/LineLength:
|
20
|
+
Max: 190
|
21
|
+
|
22
|
+
Rails/SkipsModelValidations:
|
23
|
+
Enabled: false
|
24
|
+
|
25
|
+
Lint/BooleanSymbol:
|
26
|
+
Enabled: false
|
27
|
+
|
28
|
+
Style/MultilineBlockChain:
|
29
|
+
Enabled: false
|
30
|
+
|
31
|
+
Metrics/AbcSize:
|
32
|
+
Enabled: false
|
33
|
+
|
34
|
+
Metrics/CyclomaticComplexity:
|
35
|
+
Enabled: false
|
36
|
+
|
37
|
+
Metrics/PerceivedComplexity:
|
38
|
+
Enabled: false
|
39
|
+
|
40
|
+
Style/FormatStringToken:
|
41
|
+
Enabled: false
|
42
|
+
|
43
|
+
Naming/AccessorMethodName:
|
44
|
+
Enabled: false
|
45
|
+
|
46
|
+
Performance/FixedSize:
|
47
|
+
Exclude:
|
48
|
+
- 'test/**/*'
|
49
|
+
|
50
|
+
Metrics/BlockLength:
|
51
|
+
Max: 35
|
52
|
+
Exclude:
|
53
|
+
- 'test/**/*'
|
54
|
+
|
55
|
+
Metrics/ClassLength:
|
56
|
+
Max: 200
|
57
|
+
Exclude:
|
58
|
+
- 'test/**/*'
|
59
|
+
|
60
|
+
Lint/AmbiguousBlockAssociation:
|
61
|
+
Enabled: false
|
62
|
+
|
63
|
+
Style/ClassAndModuleChildren:
|
64
|
+
Exclude:
|
65
|
+
- 'test/**/*'
|
66
|
+
- 'app/controllers/concerns/foreman/**/*'
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Alexander Olofsson
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# Foreman Windows Deployment Service
|
2
|
+
|
3
|
+
This plugin adds extended support to Foreman for querying and orchestrating WDS servers.
|
4
|
+
|
5
|
+
*Nota Bene*; to support locally booting your installation, you need to have a regular DHCP/TFTP setup in place, that can serve PXE files for the PXE loader chosen for the WDS host.
|
6
|
+
If you don't want the lifecycle management features of Foreman, you can ignore this requirement and only PXE boot the WDS host when it is to be built.
|
7
|
+
|
8
|
+
### Not yet implemented:
|
9
|
+
|
10
|
+
- Install image orchestration
|
11
|
+
- Unattend file deployment
|
12
|
+
- Recommended to build a http client (curl/wget/etc) into your boot image and download `https://foreman.example.com/unattend/wds_unattend` to drive the setup.
|
13
|
+
|
14
|
+
## Compatibility
|
15
|
+
|
16
|
+
| Foreman Version | Plugin Version |
|
17
|
+
| --------------- | -------------- |
|
18
|
+
| >= 1.17 | any |
|
19
|
+
|
20
|
+
## Installation
|
21
|
+
|
22
|
+
See the [Plugins install instructions, advanced installation from gems](https://theforeman.org/plugins/#2.3AdvancedInstallationfromGems) for information on how to install this plugins.
|
23
|
+
|
24
|
+
This plugin has JavaScript assets that require precompilation if installed into a packaged Foreman install.
|
25
|
+
You will need to install the `foreman-assets` package and run `foreman-rake plugin:assets:precompile[foreman_wds]` after installing it from gem.
|
26
|
+
|
27
|
+
## Contributing
|
28
|
+
|
29
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ananace/foreman_wds
|
30
|
+
|
31
|
+
## License
|
32
|
+
|
33
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
function wds_load(element){
|
2
|
+
var url = $(element).attr('data-url');
|
3
|
+
tfm.tools.showSpinner();
|
4
|
+
$.ajax({
|
5
|
+
type:'get',
|
6
|
+
url: url,
|
7
|
+
complete: function(){
|
8
|
+
reloadOnAjaxComplete(element);
|
9
|
+
},
|
10
|
+
success: function(request) {
|
11
|
+
element.html(request);
|
12
|
+
}
|
13
|
+
});
|
14
|
+
}
|
15
|
+
|
16
|
+
$(function() {
|
17
|
+
wds_load($('#images'));
|
18
|
+
wds_load($('#clients'));
|
19
|
+
})();
|
@@ -0,0 +1,50 @@
|
|
1
|
+
function wds_server_selected(element){
|
2
|
+
var url = $(element).attr('data-url');
|
3
|
+
var type = $(element).attr('data-type');
|
4
|
+
var attrs = {};
|
5
|
+
attrs[type] = attribute_hash(['architecture_id', 'operatingsystem_id', 'wds_server_id']);
|
6
|
+
tfm.tools.showSpinner();
|
7
|
+
$.ajax({
|
8
|
+
data: attrs,
|
9
|
+
type:'post',
|
10
|
+
url: url,
|
11
|
+
complete: function(){
|
12
|
+
reloadOnAjaxComplete(element);
|
13
|
+
},
|
14
|
+
success: function(request) {
|
15
|
+
$('#wds_image_select').html(request);
|
16
|
+
}
|
17
|
+
});
|
18
|
+
}
|
19
|
+
|
20
|
+
var old_os_selected = os_selected;
|
21
|
+
os_selected = function(element){
|
22
|
+
old_os_selected(element);
|
23
|
+
|
24
|
+
if ($('#os_select select').val() === '') {
|
25
|
+
$('#wds_server_select select').val('');
|
26
|
+
$('#wds_image_select select').val('');
|
27
|
+
$('#wds_server_select select').prop('disabled', true);
|
28
|
+
$('#wds_image_select select').prop('disabled', true);
|
29
|
+
} else {
|
30
|
+
$('#wds_server_select select').prop('disabled', false);
|
31
|
+
}
|
32
|
+
};
|
33
|
+
|
34
|
+
|
35
|
+
function wds_provision_method_selected() {
|
36
|
+
build_provision_method_selected();
|
37
|
+
$('#wds_provisioning').show();
|
38
|
+
|
39
|
+
if ($('#wds_image_select select').val() === '')
|
40
|
+
$('#wds_image_select select').attr('disabled', true);
|
41
|
+
}
|
42
|
+
$(document).on('change', '#host_provision_method_wds', wds_provision_method_selected);
|
43
|
+
|
44
|
+
$(function() {
|
45
|
+
var caps = $('#capabilities').val() || $('#bare_metal_capabilities').val();
|
46
|
+
update_capabilities(caps);
|
47
|
+
$('#provisioning_method input[checked]').click();
|
48
|
+
|
49
|
+
$('#wds_provisioning').detach().insertBefore('#media_select');
|
50
|
+
});
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Foreman::Controller::Parameters::WdsServer
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
class_methods do
|
5
|
+
def wds_server_params_filter
|
6
|
+
Foreman::ParameterFilter.new(::WdsServer).tap do |filter|
|
7
|
+
filter.permit :name,
|
8
|
+
:description,
|
9
|
+
:url,
|
10
|
+
:user,
|
11
|
+
:password
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def wds_server_params
|
17
|
+
self.class.wds_server_params_filter.filter_params(params, parameter_filter_context, :wds_server)
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ForemanWds
|
2
|
+
module HostsControllerExtensions
|
3
|
+
def wds_server_selected
|
4
|
+
host = @host || item_object
|
5
|
+
wds_facet = host.wds_facet || host.build_wds_facet
|
6
|
+
wds_facet.wds_server_id ||= (params[:wds_facet] || params[:host])[:wds_server_id]
|
7
|
+
|
8
|
+
render partial: 'wds_servers/image_select', locals: { item: wds_facet }
|
9
|
+
end
|
10
|
+
|
11
|
+
def host_params(top_level_hash = controller_name.singularize)
|
12
|
+
# Don't create a WDS facet unless provisioning with it
|
13
|
+
params[:host].delete :wds_facet_attributes if params[:host] && params[:host][:provision_method] != 'wds'
|
14
|
+
|
15
|
+
super(top_level_hash)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ForemanWds
|
2
|
+
module UnattendedControllerExtensions
|
3
|
+
def render_template(kind)
|
4
|
+
return super unless kind == 'wds_localboot'
|
5
|
+
|
6
|
+
iface = @host.provision_interface
|
7
|
+
|
8
|
+
# Deploy regular DHCP and local boot TFTP
|
9
|
+
@host.provision_method = 'build'
|
10
|
+
@host.build = false
|
11
|
+
iface.send :rebuild_tftp
|
12
|
+
iface.send :rebuild_dhcp
|
13
|
+
|
14
|
+
render inline: "Success. Local boot template was deployed successfully.\n"
|
15
|
+
rescue StandardError => e
|
16
|
+
message = format('Failed to set local boot template: %{error}', error: e)
|
17
|
+
logger.error message
|
18
|
+
render text: message, status: :error, content_type: 'text/plain'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
class WdsServersController < ::ApplicationController
|
2
|
+
include Foreman::Controller::AutoCompleteSearch
|
3
|
+
include Foreman::Controller::Parameters::WdsServer
|
4
|
+
|
5
|
+
before_action :find_server, except: %i[index new create]
|
6
|
+
|
7
|
+
def index
|
8
|
+
@wds_servers = resource_base_search_and_page
|
9
|
+
end
|
10
|
+
|
11
|
+
def show; end
|
12
|
+
|
13
|
+
def new
|
14
|
+
@wds_server = WdsServer.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def edit; end
|
18
|
+
|
19
|
+
def create
|
20
|
+
@wds_server = WdsServer.new(wds_server_params)
|
21
|
+
if @wds_server.save
|
22
|
+
process_success success_redirect: wds_server_path(@wds_server)
|
23
|
+
else
|
24
|
+
process_error
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def update
|
29
|
+
if @wds_server.update(wds_server_params)
|
30
|
+
process_success
|
31
|
+
else
|
32
|
+
process_error
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def destroy
|
37
|
+
if @wds_server.destroy
|
38
|
+
process_success
|
39
|
+
else
|
40
|
+
process_error
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_connection
|
45
|
+
# wds_id is posted from AJAX function. wds_id is nil if new
|
46
|
+
if params[:wds_id].present?
|
47
|
+
@wds_server = WdsServer.authorized(:edit_wds_server).find(params[:wds_id])
|
48
|
+
@wds_server.attributes = wds_server_params.reject { |k, v| k == :password && v.blank? }
|
49
|
+
else
|
50
|
+
@wds_server = WdsServer.new(wds_server_params)
|
51
|
+
end
|
52
|
+
|
53
|
+
@wds_server.test_connection
|
54
|
+
render partial: 'form', locals: { wds_server: @wds_server }
|
55
|
+
end
|
56
|
+
|
57
|
+
def refresh_cache
|
58
|
+
@wds_server.refresh_cache
|
59
|
+
|
60
|
+
render partial: 'form', locals: { wds_server: @wds_server }
|
61
|
+
end
|
62
|
+
|
63
|
+
def wds_clients
|
64
|
+
@clients = @wds_server.clients
|
65
|
+
|
66
|
+
render partial: 'wds_servers/clients/list'
|
67
|
+
end
|
68
|
+
|
69
|
+
def wds_images
|
70
|
+
@images = @wds_server.boot_images + @wds_server.install_images
|
71
|
+
|
72
|
+
render partial: 'wds_servers/images/list'
|
73
|
+
end
|
74
|
+
|
75
|
+
def delete_wds_client
|
76
|
+
host = Host::Managed.find(params[:client])
|
77
|
+
client = @wds_server.client(host)
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def find_server
|
83
|
+
@wds_server = WdsServer.find(params[:id])
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
class ForemanWds::WdsImage
|
2
|
+
WDS_IMAGE_ARCHES = [nil, /^i.86|x86$/i, /^ia64$/i, /^x86_64|x64$/i, /^arm$/i].freeze
|
3
|
+
WDS_ARCH_NAMES = [nil, 'x86', 'ia64', 'x64', 'arm'].freeze
|
4
|
+
WDS_PROCESSOR_NAMES = [nil, 'x86', 'ia64', 'amd64', 'arm'].freeze
|
5
|
+
|
6
|
+
attr_accessor :id, :name, :description, :enabled, :file_name,
|
7
|
+
:architecture, :product_family, :product_name, :version,
|
8
|
+
:wds_server
|
9
|
+
attr_reader :creation_time, :last_modification_time
|
10
|
+
|
11
|
+
def inspect
|
12
|
+
Kernel.format('#<%<class>s:%<id>x %<variables>s>',
|
13
|
+
class: self.class,
|
14
|
+
id: (object_id << 1),
|
15
|
+
variables: instance_variables
|
16
|
+
.reject { |v| %i[@json @wds_server].include?(v) }
|
17
|
+
.map { |v| "#{v}=#{instance_variable_get(v).inspect}" }
|
18
|
+
.join(' '))
|
19
|
+
end
|
20
|
+
|
21
|
+
def type_name
|
22
|
+
self.class
|
23
|
+
.name
|
24
|
+
.demodulize
|
25
|
+
.underscore
|
26
|
+
.split('_')
|
27
|
+
.map.with_index { |v, i| i.zero? ? v.upcase : v.capitalize }
|
28
|
+
.join ' '
|
29
|
+
end
|
30
|
+
|
31
|
+
def architecture_name
|
32
|
+
return architecture unless architecture.is_a?(Integer) && WDS_ARCH_NAMES[architecture]
|
33
|
+
WDS_ARCH_NAMES[architecture]
|
34
|
+
end
|
35
|
+
|
36
|
+
def creation_time=(time)
|
37
|
+
@creation_time = parse_time time
|
38
|
+
end
|
39
|
+
|
40
|
+
def last_modification_time=(time)
|
41
|
+
@last_modification_time = parse_time time
|
42
|
+
end
|
43
|
+
|
44
|
+
def matches_architecture?(architecture)
|
45
|
+
return nil unless WDS_IMAGE_ARCHES[self.architecture]
|
46
|
+
architecture = architecture.name if architecture.is_a? Architecture
|
47
|
+
!(WDS_IMAGE_ARCHES[self.architecture] =~ architecture).nil?
|
48
|
+
end
|
49
|
+
|
50
|
+
def marshal_dump
|
51
|
+
@json
|
52
|
+
end
|
53
|
+
|
54
|
+
def marshal_load(json)
|
55
|
+
@json = json
|
56
|
+
load!
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
60
|
+
|
61
|
+
def initialize(json = {})
|
62
|
+
@json = json if json.is_a? Hash
|
63
|
+
@wds_server = self.json.delete(:wds_server)
|
64
|
+
load!
|
65
|
+
end
|
66
|
+
|
67
|
+
def json
|
68
|
+
@json || {}
|
69
|
+
end
|
70
|
+
|
71
|
+
def parse_time(time)
|
72
|
+
return time unless time.is_a?(String) && time =~ /\/Date\(.*\)\//
|
73
|
+
|
74
|
+
time = Time.at(time.scan(/\((.*)\)/).flatten.first.to_i / 1000)
|
75
|
+
return time unless wds_server
|
76
|
+
|
77
|
+
time.utc
|
78
|
+
sec_diff = wds_server.timezone - Time.at(0).utc_offset
|
79
|
+
time += sec_diff
|
80
|
+
time.localtime
|
81
|
+
end
|
82
|
+
|
83
|
+
def load!
|
84
|
+
json.each do |k, v|
|
85
|
+
sym = "#{k.to_s.underscore}=".to_sym
|
86
|
+
send sym, v if respond_to? sym
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|