foreman_setup 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.
Files changed (31) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES.md +4 -0
  3. data/LICENSE +619 -0
  4. data/README.md +47 -0
  5. data/app/assets/javascripts/foreman_setup/provisioner.js +13 -0
  6. data/app/controllers/foreman_setup/provisioners_controller.rb +206 -0
  7. data/app/helpers/concerns/foreman_setup/home_helper_ext.rb +15 -0
  8. data/app/helpers/foreman_setup/provisioner_helper.rb +14 -0
  9. data/app/models/foreman_setup/provisioner.rb +77 -0
  10. data/app/overrides/add_dashboard_provisioners_link.rb +12 -0
  11. data/app/overrides/remove_subnet_domains.rb +9 -0
  12. data/app/overrides/remove_subnet_proxies.rb +9 -0
  13. data/app/views/foreman_setup/provisioners/_step1.html.erb +45 -0
  14. data/app/views/foreman_setup/provisioners/_step2.html.erb +19 -0
  15. data/app/views/foreman_setup/provisioners/_step3.html.erb +58 -0
  16. data/app/views/foreman_setup/provisioners/_step4.html.erb +49 -0
  17. data/app/views/foreman_setup/provisioners/_step5.html.erb +22 -0
  18. data/app/views/foreman_setup/provisioners/index.html.erb +23 -0
  19. data/app/views/foreman_setup/provisioners/new.html.erb +3 -0
  20. data/app/views/foreman_setup/provisioners/step2.html.erb +3 -0
  21. data/app/views/foreman_setup/provisioners/step3.html.erb +3 -0
  22. data/app/views/foreman_setup/provisioners/step4.html.erb +3 -0
  23. data/app/views/foreman_setup/provisioners/step5.html.erb +3 -0
  24. data/config/routes.rb +15 -0
  25. data/db/migrate/20131023140100_add_provisioners.rb +11 -0
  26. data/db/migrate/20131025180600_add_provisioners_hostgroup.rb +5 -0
  27. data/db/migrate/20131028161000_add_provisioners_domain.rb +5 -0
  28. data/lib/foreman_setup.rb +7 -0
  29. data/lib/foreman_setup/engine.rb +37 -0
  30. data/lib/foreman_setup/version.rb +3 -0
  31. metadata +89 -0
data/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # foreman_setup
2
+
3
+ Setting up Foreman for provisioning can be daunting at first, as there are
4
+ lots of parameters to configure DHCP and DNS for the installer, plus for setup
5
+ of subnets, domains, installation media etc for Foreman.
6
+
7
+ # Installation
8
+
9
+ Please see the Foreman wiki for appropriate instructions:
10
+
11
+ * [Foreman: How to Install a Plugin](http://projects.theforeman.org/projects/foreman/wiki/How_to_Install_a_Plugin)
12
+
13
+ The gem name is "foreman_setup". Run `foreman-rake db:migrate` after
14
+ installation.
15
+
16
+ RPM users can install the "ruby193-rubygem-foreman_setup" or
17
+ "rubygem-foreman_setup" packages.
18
+
19
+ # Areas this should help
20
+
21
+ * take input of subnet and domain information
22
+ * output foreman-installer command with appropriate DHCP, DNS and TFTP parameters
23
+ * add foreman-installer modules to the Foreman host with appropriate parameters
24
+ * create a host group with appropriate parameters
25
+ * create hosts (proxies/nodes) using created host groups
26
+ * ensure provided templates and OSes are fully associated
27
+ * default templates should be properly associated in core
28
+ * when using Katello, its Foreman plugin helps associate
29
+ * add appropriate installation media
30
+ * add appropriate Spacewalk/redhat_register parameters
31
+
32
+ # Copyright
33
+
34
+ Copyright (c) 2013 Red Hat Inc.
35
+
36
+ This program is free software: you can redistribute it and/or modify
37
+ it under the terms of the GNU General Public License as published by
38
+ the Free Software Foundation, either version 3 of the License, or
39
+ (at your option) any later version.
40
+
41
+ This program is distributed in the hope that it will be useful,
42
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
43
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
44
+ GNU General Public License for more details.
45
+
46
+ You should have received a copy of the GNU General Public License
47
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
@@ -0,0 +1,13 @@
1
+ function setup_media_change(ele) {
2
+ if ($(ele).val() == 'spacewalk') {
3
+ $('[id$="medium_path"]').prop('disabled', true)
4
+ $('#spacewalk_hostname').prop('disabled', false)
5
+ } else {
6
+ $('#spacewalk_hostname').prop('disabled', true)
7
+ $('[id$="medium_path"]').prop('disabled', false)
8
+ }
9
+ }
10
+
11
+ function setup_media_create_change(ele) {
12
+ $('[id$="medium_id"]').val('')
13
+ }
@@ -0,0 +1,206 @@
1
+ require 'facter'
2
+
3
+ module ForemanSetup
4
+ class ProvisionersController < ::ApplicationController
5
+ include Foreman::Renderer
6
+
7
+ before_filter :find_myself, :only => [:new, :create]
8
+ before_filter :find_resource, :except => [:index, :new, :create]
9
+
10
+ def index
11
+ @provisioners = Provisioner.all.paginate :page => params[:page]
12
+ redirect_to new_foreman_setup_provisioner_path unless @provisioners.any?
13
+ end
14
+
15
+ def destroy
16
+ @provisioner = Provisioner.find(params[:id])
17
+ if @provisioner.destroy
18
+ process_success :success_redirect => foreman_setup_provisioners_path
19
+ else
20
+ process_error
21
+ end
22
+ end
23
+
24
+ def new
25
+ @provisioner = Provisioner.new(:host => @host, :smart_proxy => @proxy)
26
+ end
27
+
28
+ def create
29
+ @provisioner = Provisioner.new(params['foreman_setup_provisioner'])
30
+ if @provisioner.save
31
+ redirect_to step2_foreman_setup_provisioner_path(@provisioner)
32
+ else
33
+ @provisioner.host = @host
34
+ @provisioner.smart_proxy = @proxy
35
+ process_error :render => 'foreman_setup/provisioners/new', :object => @provisioner
36
+ end
37
+ end
38
+
39
+ # Basic model created, now fill in nested subnet/domain info using selected interface
40
+ def step2
41
+ network = @provisioner.provision_interface_data
42
+ @provisioner.subnet ||= Subnet.find_by_network(network[:network])
43
+ @provisioner.subnet ||= Subnet.new(network.slice(:network, :mask).merge(
44
+ :dns_primary => @provisioner.provision_interface_data[:ip],
45
+ ))
46
+
47
+ @provisioner.domain ||= @provisioner.host.domain
48
+ @provisioner.domain ||= Domain.new(:name => 'example.com')
49
+ end
50
+
51
+ def step2_update
52
+ @provisioner.hostgroup ||= Hostgroup.find_or_create_by_name(_("Provision from %s") % @provisioner.fqdn)
53
+ @provisioner.subnet ||= Subnet.find_by_id(params['foreman_setup_provisioner']['subnet_attributes']['id'])
54
+ domain_name = params['foreman_setup_provisioner'].delete('domain_name')
55
+ @provisioner.domain = Domain.find_by_name(domain_name)
56
+ @provisioner.domain ||= Domain.new(:name => domain_name)
57
+
58
+ if @provisioner.update_attributes(params['foreman_setup_provisioner'])
59
+ @provisioner.subnet.domains << @provisioner.domain unless @provisioner.subnet.domains.include? @provisioner.domain
60
+ process_success :success_msg => _("Successfully updated subnet %s.") % @provisioner.subnet.name, :success_redirect => step3_foreman_setup_provisioner_path
61
+ else
62
+ process_error :render => 'foreman_setup/provisioners/step2', :object => @provisioner, :redirect => step2_foreman_setup_provisioner_path
63
+ end
64
+ end
65
+
66
+ # foreman-installer info screen
67
+ def step3
68
+ end
69
+
70
+ # Installer completed, start associating data, begin install media setup
71
+ def step4
72
+ # Refresh proxy features (1.4 versus 1.3 techniques)
73
+ proxy = @provisioner.smart_proxy
74
+ proxy.respond_to?(:refresh) ? proxy.refresh : proxy.ping
75
+ proxy.save!
76
+
77
+ # Associate as much as possible
78
+ if proxy.features.include? Feature.find_by_name('DNS')
79
+ @provisioner.domain.dns_id ||= proxy.id
80
+ @provisioner.subnet.dns_id ||= proxy.id
81
+ end
82
+ if proxy.features.include? Feature.find_by_name('DHCP')
83
+ @provisioner.subnet.dhcp_id ||= proxy.id
84
+ end
85
+ if proxy.features.include? Feature.find_by_name('TFTP')
86
+ @provisioner.subnet.tftp_id ||= proxy.id
87
+ end
88
+ @provisioner.save!
89
+
90
+ # Helpful fix to work around #3210
91
+ url = Setting.find_by_name('foreman_url')
92
+ url.value ||= Facter.fqdn
93
+ url.save!
94
+
95
+ # Build default PXE menu
96
+ status, msg = ConfigTemplate.build_pxe_default(self)
97
+ warning msg unless status == 200
98
+
99
+ @provisioner.hostgroup.medium ||= @provisioner.host.os.media.first
100
+ @medium = Medium.new(params['foreman_setup_provisioner'].try(:[], 'medium_attributes'))
101
+
102
+ parameters = @provisioner.hostgroup.group_parameters
103
+ @activation_key = parameters.where(:name => 'activation_key').first || parameters.new(:name => 'activation_key')
104
+ @satellite_type = parameters.where(:name => 'satellite_type').first || parameters.new(:name => 'satellite_type')
105
+ end
106
+
107
+ def step4_update
108
+ if params['medium_type'] == 'spacewalk'
109
+ spacewalk_hostname = params['spacewalk_hostname']
110
+ if spacewalk_hostname.blank?
111
+ @provisioner.errors.add(:base, _("Spacewalk hostname is missing"))
112
+ process_error :render => 'foreman_setup/provisioners/step4', :object => @provisioner, :redirect => step4_foreman_setup_provisioner_path
113
+ return
114
+ end
115
+ end
116
+
117
+ medium_id = params['foreman_setup_provisioner']['hostgroup_attributes']['medium_id']
118
+ if medium_id.to_i > 0
119
+ @medium = Medium.find(medium_id) || raise('unable to find medium')
120
+ else
121
+ @provisioner.hostgroup.medium_id = nil
122
+ @medium = Medium.new(params['foreman_setup_provisioner']['create_medium'].slice(:name, :path))
123
+ end
124
+ @medium.path = "http://#{spacewalk_hostname}/ks/dist/ks-rhel-$arch-server-$major-$version" unless spacewalk_hostname.blank?
125
+
126
+ parameters = @provisioner.hostgroup.group_parameters
127
+ @activation_key = parameters.where(:name => 'activation_key').first
128
+ if @activation_key
129
+ @activation_key.assign_attributes(params['foreman_setup_provisioner']['activation_key'])
130
+ elsif params['foreman_setup_provisioner']['activation_key']['value'].present?
131
+ @activation_key = parameters.new(params['foreman_setup_provisioner']['activation_key'].merge(:name => 'activation_key'))
132
+ end
133
+
134
+ @satellite_type = parameters.where(:name => 'satellite_type').first
135
+ if @satellite_type
136
+ @satellite_type.assign_attributes(params['foreman_setup_provisioner']['satellite_type'])
137
+ elsif params['foreman_setup_provisioner']['satellite_type']['value'].present?
138
+ @satellite_type = parameters.new(params['foreman_setup_provisioner']['satellite_type'].merge(:name => 'satellite_type'))
139
+ end
140
+
141
+ # Associate medium with the host OS
142
+ @medium.os_family ||= @provisioner.host.os.type
143
+ @medium.operatingsystems << @provisioner.host.os unless @medium.operatingsystems.include? @provisioner.host.os
144
+ unless @medium.save
145
+ process_error :render => 'foreman_setup/provisioners/step4', :object => @provisioner, :redirect => step4_foreman_setup_provisioner_path
146
+ return
147
+ end
148
+
149
+ # Associate templates with OS and vice-versa
150
+ if @provisioner.host.os.family == 'Redhat'
151
+ tmpl_name = 'Kickstart'
152
+ provision_tmpl_name = @provisioner.host.os.name == 'Redhat' ? 'RHEL Kickstart' : tmpl_name
153
+ gpxe_tmpl_name = 'Kickstart'
154
+ ptable_name = 'RedHat default'
155
+ elsif @provisioner.host.os.family == 'Debian'
156
+ tmpl_name = provision_tmpl_name = 'Preseed'
157
+ ptable_name = 'Ubuntu default'
158
+ end
159
+
160
+ {'provision' => provision_tmpl_name, 'PXELinux' => tmpl_name, 'gPXE' => gpxe_tmpl_name}.each do |kind_name, tmpl_name|
161
+ next if tmpl_name.blank?
162
+ kind = TemplateKind.find_by_name(kind_name)
163
+ tmpls = ConfigTemplate.where('name LIKE ?', "#{tmpl_name}%").where(:template_kind_id => kind.id)
164
+ tmpls.any? || raise("cannot find template for #{@provisioner.host.os}")
165
+
166
+ # prefer foreman_bootdisk templates
167
+ tmpl = tmpls.where('name LIKE "boot disk"').first || tmpls.first
168
+
169
+ tmpl.operatingsystems << @provisioner.host.os unless tmpl.operatingsystems.include? @provisioner.host.os
170
+ tmpl.save!
171
+
172
+ unless @provisioner.host.os.os_default_templates.where(:template_kind_id => kind.id).any?
173
+ @provisioner.host.os.os_default_templates.build(:template_kind_id => kind.id, :config_template_id => tmpl.id)
174
+ end
175
+ end
176
+
177
+ @provisioner.host.os.architectures << @provisioner.host.architecture unless @provisioner.host.os.architectures.include? @provisioner.host.architecture
178
+ @provisioner.host.os.save!
179
+
180
+ ptable = Ptable.where('name LIKE ?', "#{ptable_name}%").first || raise("cannot find ptable for #{@provisioner.host.os}")
181
+ ptable.operatingsystems << @provisioner.host.os unless ptable.operatingsystems.include? @provisioner.host.os
182
+ ptable.save!
183
+
184
+ @provisioner.hostgroup.medium_id = @medium.id
185
+ @provisioner.hostgroup.ptable_id ||= ptable.id
186
+ @provisioner.hostgroup.save!
187
+
188
+ process_success :success_msg => _("Successfully associated OS %s.") % @provisioner.host.os.to_s, :success_redirect => step5_foreman_setup_provisioner_path
189
+ end
190
+
191
+ private
192
+
193
+ # foreman_setup manages only itself at the moment, so ensure we always have a reference to
194
+ # the Host and SmartProxy on this server
195
+ def find_myself
196
+ fqdn = Facter.fqdn
197
+ @host = Host.find_by_name(fqdn)
198
+ @proxy = SmartProxy.all.find { |p| URI.parse(p.url).host == fqdn }
199
+ end
200
+
201
+ def find_resource
202
+ @provisioner = Provisioner.find(params[:id]) or raise('unknown id')
203
+ end
204
+
205
+ end
206
+ end
@@ -0,0 +1,15 @@
1
+ module ForemanSetup
2
+ module HomeHelperExt
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ alias_method_chain :settings_menu_items, :provisioners_link
7
+ end
8
+
9
+ def settings_menu_items_with_provisioners_link
10
+ menu_items = settings_menu_items_without_provisioners_link
11
+ menu_items[2][2].insert(7, [_('Provisioning Setup'), :'foreman_setup/provisioners']) if menu_items[2]
12
+ menu_items
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ module ForemanSetup
2
+ module ProvisionerHelper
3
+ def provisioner_wizard(step)
4
+ wizard_header(
5
+ step,
6
+ _("Pre-requisites"),
7
+ _("Network config"),
8
+ _("Foreman installer"),
9
+ _("Installation media"),
10
+ _("Completion")
11
+ )
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,77 @@
1
+ require 'ipaddr'
2
+
3
+ module ForemanSetup
4
+ class Provisioner < ActiveRecord::Base
5
+ include ::Authorization
6
+ include ::Host::Hostmix
7
+
8
+ before_save :populate_hostgroup
9
+
10
+ belongs_to_host
11
+ belongs_to :domain
12
+ belongs_to :hostgroup, :autosave => true
13
+ belongs_to :smart_proxy
14
+ belongs_to :subnet
15
+ has_one :architecture, :through => :host
16
+ has_one :medium, :through => :hostgroup
17
+ has_one :operatingsystem, :through => :host
18
+
19
+ accepts_nested_attributes_for :hostgroup
20
+ # TODO: further validation on the subnet's (usually optional) attributes
21
+ accepts_nested_attributes_for :subnet
22
+
23
+ validates :host_id, :presence => true, :uniqueness => true
24
+ validates :smart_proxy_id, :presence => true
25
+
26
+ def to_s
27
+ host.try(:to_s)
28
+ end
29
+
30
+ def fqdn
31
+ Facter.fqdn
32
+ end
33
+
34
+ def interfaces
35
+ facts = host.facts_hash
36
+ (facts['interfaces'] || '').split(',').reject { |i| i == 'lo' }.inject({}) do |ifaces,i|
37
+ ip = facts["ipaddress_#{i}"]
38
+ network = facts["network_#{i}"]
39
+ netmask = facts["netmask_#{i}"]
40
+ if ip && network && netmask
41
+ cidr = "#{network}/#{IPAddr.new(netmask).to_i.to_s(2).count("1")}"
42
+ ifaces[i] = {:ip => ip, :mask => netmask, :network => network, :cidr => cidr}
43
+ end
44
+ ifaces
45
+ end
46
+ end
47
+
48
+ def provision_interface_data
49
+ interfaces[provision_interface]
50
+ end
51
+
52
+ def rdns_zone
53
+ netmask_octets = subnet.mask.split('.').reverse
54
+ subnet.network.split('.').reverse.drop_while { |i| netmask_octets.shift == '0' }.join('.') + '.in-addr.arpa'
55
+ end
56
+
57
+ def dns_forwarders
58
+ File.open('/etc/resolv.conf', 'r').each_line.map do |r|
59
+ $1 if r =~ /^nameserver\s+(\S+)/
60
+ end.compact
61
+ end
62
+
63
+ private
64
+
65
+ # Ensures our nested hostgroup has as much data as possible
66
+ def populate_hostgroup
67
+ return unless hostgroup.present?
68
+ hostgroup.architecture_id ||= domain.id
69
+ hostgroup.domain_id ||= domain.id
70
+ hostgroup.operatingsystem_id ||= operatingsystem.id
71
+ hostgroup.puppet_ca_proxy_id = smart_proxy.id if smart_proxy.features.include? Feature.find_by_name('Puppet CA')
72
+ hostgroup.puppet_proxy_id = smart_proxy.id if smart_proxy.features.include? Feature.find_by_name('Puppet')
73
+ hostgroup.subnet_id ||= subnet.id
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,12 @@
1
+ Deface::Override.new(:virtual_path => 'dashboard/index',
2
+ :name => 'add_dashboard_provisioners_link',
3
+ :surround_contents => 'code[erb-loud]:contains("title_actions")',
4
+ :original => '35e276b7102eaa4feb8a4a84f66fd4812216fd48',
5
+ :text => <<EOS
6
+ if ForemanSetup::Provisioner.any?
7
+ <%= render_original %>
8
+ else
9
+ title_actions link_to_if_authorized(_("Set up provisioning"), hash_for_new_foreman_setup_provisioner_path)
10
+ end
11
+ EOS
12
+ )
@@ -0,0 +1,9 @@
1
+ Deface::Override.new(:virtual_path => 'subnets/_fields',
2
+ :name => 'remove_subnet_domains',
3
+ :surround_contents => 'code[erb-loud]:contains("multiple_checkboxes f, :domain")',
4
+ :text => <<EOS
5
+ unless controller.controller_name == 'provisioners'
6
+ <%= render_original %>
7
+ end
8
+ EOS
9
+ )
@@ -0,0 +1,9 @@
1
+ Deface::Override.new(:virtual_path => 'subnets/_fields',
2
+ :name => 'remove_subnet_proxies',
3
+ :surround_contents => 'code[erb-loud]:contains("SmartProxy.")',
4
+ :text => <<EOS
5
+ unless controller.controller_name == 'provisioners'
6
+ <%= render_original %>
7
+ end
8
+ EOS
9
+ )
@@ -0,0 +1,45 @@
1
+
2
+ <%= form_for provisioner do |f| %>
3
+ <%= base_errors_for provisioner %>
4
+ <%= provisioner_wizard 1 %>
5
+
6
+ <p>
7
+ <%= _("This wizard will help set up Foreman for full host provisioning. Before we begin, a few requirements will be verified.") %>
8
+ </p>
9
+
10
+ <h4><%= _("Pre-requisites") %></h4>
11
+ <div>
12
+ <%= f.hidden_field :host_id %>
13
+ <% if f.object.host.present? %>
14
+ <%= checked_icon f.object.host.present? %> <%= _("Found registered host %s") % f.object.host.name %>
15
+ <% else %>
16
+ <%= image_tag 'false.png' %> <%= _("Missing registered host %s, please ensure it is checking in") % f.object.fqdn %>
17
+ <% end %>
18
+ </div>
19
+ <div>
20
+ <%= f.hidden_field :smart_proxy_id %>
21
+ <% if f.object.smart_proxy.present? %>
22
+ <%= checked_icon f.object.smart_proxy.present? %> <%= _("Found registered smart proxy %s") % f.object.smart_proxy.name %>
23
+ <% else %>
24
+ <%= image_tag 'false.png' %> <%= _("Missing registered smart proxy %s, please ensure it is registered") % f.object.fqdn %>
25
+ <% end %>
26
+ </div>
27
+ <div>
28
+ <% if f.object.host.present? && f.object.interfaces.any? %>
29
+ <%= checked_icon f.object.host.present? %> <%= _("Host %s has at least one network interface") % f.object.host.name %>
30
+ <% else %>
31
+ <%= image_tag 'false.png' %> <%= _("No network interfaces listed in $interfaces fact") %>
32
+ <% end %>
33
+ </div>
34
+
35
+ <h4><%= _("Network selection") %></h4>
36
+ <div>
37
+ <% if f.object.host.present? && f.object.interfaces.any? %>
38
+ <%= selectable_f f, :provision_interface, f.object.interfaces.map { |i| ["#{i[1][:cidr]} (#{i[0]})", i[0]] }, {}, :label => _("Provisioning network") %>
39
+ <% else %>
40
+ <%= _("Not available until pre-requisites satisified.") %>
41
+ <% end %>
42
+ </div>
43
+
44
+ <%= submit_or_cancel f, false, {:cancel_path => foreman_setup_provisioners_path, :disabled => !(f.object.host.present? && f.object.smart_proxy.present?)} %>
45
+ <% end %>