foreman_wds 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|