foreman_wds 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.eslintrc.json +20 -0
  3. data/.gitignore +17 -0
  4. data/.rubocop.yml +66 -0
  5. data/.travis.yml +5 -0
  6. data/Gemfile +6 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +33 -0
  9. data/Rakefile +10 -0
  10. data/app/assets/javascripts/foreman_wds/wds_servers.js +19 -0
  11. data/app/assets/javascripts/host_edit_extensions.js +50 -0
  12. data/app/controllers/concerns/foreman/controller/parameters/wds_server.rb +19 -0
  13. data/app/controllers/concerns/foreman_wds/hosts_controller_extensions.rb +18 -0
  14. data/app/controllers/concerns/foreman_wds/unattended_controller_extensions.rb +21 -0
  15. data/app/controllers/wds_servers_controller.rb +85 -0
  16. data/app/lib/foreman_wds/wds_boot_image.rb +11 -0
  17. data/app/lib/foreman_wds/wds_image.rb +89 -0
  18. data/app/lib/foreman_wds/wds_install_image.rb +14 -0
  19. data/app/models/concerns/foreman_wds/compute_resource_extensions.rb +7 -0
  20. data/app/models/concerns/foreman_wds/host_extensions.rb +109 -0
  21. data/app/models/concerns/foreman_wds/nic_extensions.rb +40 -0
  22. data/app/models/foreman_wds/wds_facet.rb +48 -0
  23. data/app/models/wds_server.rb +239 -0
  24. data/app/services/wds_image_cache.rb +66 -0
  25. data/app/views/foreman_wds/unattend_2016.xml.erb +220 -0
  26. data/app/views/foreman_wds/windows_ptable.xml.erb +43 -0
  27. data/app/views/hosts/provision_method/wds/_form.html.erb +12 -0
  28. data/app/views/wds_servers/_form.html.erb +28 -0
  29. data/app/views/wds_servers/_image_select.html.erb +27 -0
  30. data/app/views/wds_servers/_server_select.html.erb +12 -0
  31. data/app/views/wds_servers/clients/_list.html.erb +43 -0
  32. data/app/views/wds_servers/edit.html.erb +3 -0
  33. data/app/views/wds_servers/images/_list.html.erb +36 -0
  34. data/app/views/wds_servers/index.html.erb +25 -0
  35. data/app/views/wds_servers/new.html.erb +3 -0
  36. data/app/views/wds_servers/show.html.erb +46 -0
  37. data/config/routes.rb +26 -0
  38. data/db/migrate/20180426133700_add_wds_servers.rb +23 -0
  39. data/db/seeds.d/50_ptable_templates.rb +16 -0
  40. data/db/seeds.d/50_unattend_templates.rb +19 -0
  41. data/foreman_wds.gemspec +23 -0
  42. data/lib/foreman_wds.rb +4 -0
  43. data/lib/foreman_wds/engine.rb +70 -0
  44. data/lib/foreman_wds/version.rb +3 -0
  45. data/test/foreman_wds_test.rb +11 -0
  46. data/test/test_helper.rb +4 -0
  47. 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
@@ -0,0 +1,20 @@
1
+ {
2
+ "rules": {
3
+ "indent": [
4
+ "error",
5
+ 2
6
+ ],
7
+ "linebreak-style": [
8
+ "error",
9
+ "unix"
10
+ ],
11
+ "quotes": [
12
+ "error",
13
+ "single"
14
+ ],
15
+ "semi": [
16
+ "error",
17
+ "always"
18
+ ]
19
+ }
20
+ }
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /log/*.log
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /vendor/
11
+ /test/dummy
12
+ /locale/*.mo
13
+ /locale/*/*.edit.po
14
+ /locale/*/*.po.time_stamp
15
+ /locale/*/*.pox
16
+
17
+ Gemfile.lock
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
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.6
5
+ before_install: gem install bundler -v 1.16.1
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in foreman_wds.gemspec
6
+ gemspec
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,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
@@ -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,11 @@
1
+ class ForemanWds::WdsBootImage < ForemanWds::WdsImage
2
+ def initialize(json = {})
3
+ super json
4
+ end
5
+
6
+ def reload
7
+ return false if wds_server.nil?
8
+ @json = wds_server.boot_image(name)
9
+ load!
10
+ end
11
+ 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