palletjack-tools 0.1.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1698a81a8f10fd0ae9b078c077a157d62185388d
4
+ data.tar.gz: 8e00cb7db1fe2051f3ebe34e015f7285dff5cc07
5
+ SHA512:
6
+ metadata.gz: 41a4e3b367d5f38d5b9514df1e6f8e01571046d46cd5d70ab38f1a4913c44fd9930986af3a4e72b1b9a9978b542a58e26cbe96947c2b39dd15b13505685fd17b
7
+ data.tar.gz: 1993440970282599886f25403198dc39c86e8f3128451f6a8d63dbef26f2b71b0e5be1b49051f3372ca09490b0dfbaccf72b49669cfbebafea1eda42d628197f
checksums.yaml.gz.sig ADDED
Binary file
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in palletjack.gemspec
4
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/exe/create_domain ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Create a domain and IPv4 network in a warehouse
4
+ #
5
+ # Data model assumptions:
6
+ # - Each domain corresponds uniquely to one IPv4 network
7
+
8
+ require 'palletjack/tool'
9
+
10
+ class CreateDomain < PalletJack::Tool
11
+ def parse_options(opts)
12
+ opts.banner =
13
+ "Usage: #{$PROGRAM_NAME} -w <warehouse> -d <domain> -n <IPv4 network>
14
+
15
+ Create domain and ipv4_network objects in a warehouse"
16
+
17
+ opts.on('-d DOMAIN', '--domain DOMAIN', 'domain name', String) {|domain|
18
+ options[:domain] = domain }
19
+ opts.on('-n NETWORK', '--network NETWORK',
20
+ 'IPv4 network, in CIDR format', String) {|network|
21
+ options[:network] = network.gsub(/\//, '_') }
22
+
23
+ required_option :domain
24
+ required_option :network
25
+ end
26
+ end
27
+
28
+ CreateDomain.run do
29
+
30
+ # Create the IPv4 Network object
31
+
32
+ pallet_dir 'ipv4_network', :network
33
+ pallet_box 'ipv4_network', :network, 'dhcp' do
34
+ { net:{ dhcp:{ 'tftp-server' => '', 'boot-file' => '' } } }
35
+ end
36
+ pallet_box 'ipv4_network', :network, 'identity' do
37
+ { net:{ ipv4:{ gateway:'' },
38
+ dns:{ resolver:[''] } } }
39
+ end
40
+
41
+ # Create the DNS domain object
42
+
43
+ pallet_dir 'domain', :domain
44
+ pallet_box 'domain', :domain, 'dns' do
45
+ { net:{ dns:{ ns:[''], 'soa-ns' => '', 'soa-contact' => '' } } }
46
+ end
47
+ pallet_box 'domain', :domain, 'services' do
48
+ { net:{ service:{ syslog:[{address:'', port:'514', protocol:'udp' } ] } } }
49
+ end
50
+ pallet_links 'domain', :domain, 'ipv4_network' => ['ipv4_network', :network]
51
+
52
+ end
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Create a physical and logical IPv4 network interface in a warehouse
4
+
5
+ require 'palletjack/tool'
6
+
7
+ class CreateIPv4Interface < PalletJack::Tool
8
+ def parse_options(opts)
9
+ opts.banner =
10
+ "Usage: #{$PROGRAM_NAME} -w <warehouse> -s <system> -d <domain> \\
11
+ -m <MAC address> -i <IPv4 address> -n <IPv4 network>
12
+
13
+ Create phy_nic and ipv4_interface objects in a warehouse"
14
+
15
+ opts.on('-s SYSTEM', '--system SYSTEM', 'system name', String) {|system|
16
+ options[:system] = system }
17
+ opts.on('-d DOMAIN', '--domain DOMAIN', 'domain name',String) {|domain|
18
+ options[:domain] = domain }
19
+ opts.on('-m MAC', '--mac MAC', 'MAC address', String) {|mac|
20
+ options[:mac] = mac }
21
+ opts.on('-i ADDR', '--ipv4 ADDR', 'IPv4 address', String) {|addr|
22
+ options[:addr] = addr }
23
+ opts.on('-n NETWORK', '--network NETWORK', 'IPv4 network, in CIDR format',
24
+ String) {|network| options[:network] = network.gsub(/\//, '_') }
25
+
26
+ required_option :system
27
+ required_option :domain
28
+ required_option :mac
29
+ required_option :addr
30
+ required_option :network
31
+ end
32
+ end
33
+
34
+ CreateIPv4Interface.run do
35
+
36
+ # Create Physical NIC object
37
+
38
+ pallet_dir 'phy_nic', :mac
39
+ pallet_box 'phy_nic', :mac, 'identity' do
40
+ { net:{ layer2:{ name:'' } } }
41
+ end
42
+
43
+ # Create IPv4 Interface object
44
+
45
+ pallet_dir 'ipv4_interface', :addr
46
+ pallet_links 'ipv4_interface', :addr,
47
+ 'ipv4_network' => ['ipv4_network', :network],
48
+ 'domain' => ['domain', :domain],
49
+ 'phy_nic' => ['phy_nic', :mac],
50
+ 'system' => ['system', :system]
51
+ end
data/exe/create_system ADDED
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Create a system in a warehouse
4
+
5
+ require 'palletjack/tool'
6
+
7
+ class CreateSystem < PalletJack::Tool
8
+ def parse_options(opts)
9
+ opts.banner =
10
+ "Usage: #{$PROGRAM_NAME} -w <warehouse> -s <system> -d <domain> \\
11
+ [ -o <os> | -n <netinstall> ]
12
+
13
+ Create system objects in a warehouse"
14
+
15
+ opts.on('-s SYSTEM', '--system SYSTEM', 'system name', String) {|system|
16
+ options[:system] = system }
17
+ opts.on('-d DOMAIN', '--domain DOMAIN', 'domain name', String) {|domain|
18
+ options[:domain] = domain }
19
+ opts.on('-o OS', '--os OS', 'operating system name', String) {|os|
20
+ options[:os] = os }
21
+ opts.on('-n OS', '--netinstall NETINSTALL', 'network installation profile',
22
+ String) {|netinstall| options[:netinstall] = netinstall }
23
+
24
+ required_option :system
25
+ required_option :domain
26
+ required_option :os, :netinstall
27
+ exclusive_options :os, :netinstall
28
+ end
29
+ end
30
+
31
+ CreateSystem.run do
32
+ pallet_dir 'system', :system
33
+ pallet_box 'system', :system, 'role' do
34
+ { system:{ role:[ '' ] } }
35
+ end
36
+ pallet_links 'system', :system, 'domain' => ['domain', :domain]
37
+ pallet_links 'system', :system,
38
+ if options[:os]
39
+ { 'os' => ['os', :os], 'netinstall' => [] }
40
+ elsif options[:netinstall]
41
+ { 'os' => [], 'netinstall' => ['netinstall', :netinstall] }
42
+ else
43
+ abort('Both --os and --netinstall were nil; should not happen!')
44
+ end
45
+ end
data/exe/dump_pallet ADDED
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Simple script for dumping a specified pallet as YAML.
4
+
5
+ require 'palletjack/tool'
6
+
7
+ class DumpPallet < PalletJack::Tool
8
+ def parse_options(parser)
9
+
10
+ parser.banner = "Usage: #{$PROGRAM_NAME} [options] [<name> ...]
11
+
12
+ Dump the YAML representation of all named pallets in a Palletjack warehouse
13
+
14
+ If a name is specified, dump only that pallet. Otherwise, dump all
15
+ pallets of the specified type."
16
+
17
+ # The preset value for options[:type] will be overridden when
18
+ # the parse! method of the parser is eventually run and finds
19
+ # a --type option. Keep declarations in that order for clarity.
20
+
21
+ options[:type]='system'
22
+ parser.on('-t TYPE', '--type TYPE',
23
+ 'type (default "system")',
24
+ String) {|type| options[:type] = type }
25
+ end
26
+
27
+ # Dump the yaml contents of all pallets given to STDOUT
28
+
29
+ def dump_pallets(pallets)
30
+ abort('No matching pallets found.') if pallets.empty?
31
+
32
+ pallets.each do |p|
33
+ puts "---"
34
+ puts "# #{p.kind}: #{p.name}"
35
+ puts p.to_yaml
36
+ end
37
+ end
38
+ end
39
+
40
+ DumpPallet.run do
41
+ # Since the Tool framework uses destructive opts.parse!, all options
42
+ # were removed from argv, leaving just the names to search for.
43
+ if argv.empty?
44
+ dump_pallets jack[kind: options[:type]]
45
+ else
46
+ argv.each do |name|
47
+ dump_pallets jack[kind: options[:type], name: name]
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Write configuration for the Kea DHCP server from a Palletjack warehouse
4
+
5
+ require 'palletjack/tool'
6
+ require 'json'
7
+
8
+ class PalletJack2Kea < PalletJack::Tool
9
+ def parse_options(opts)
10
+ opts.banner =
11
+ "Usage: #{$PROGRAM_NAME} [options] <name> ...
12
+
13
+ Write configuration for the Kea DHCP server from a Palletjack warehouse"
14
+
15
+ opts.on('-s SERVICE', '--service SERVICE',
16
+ 'service name for global configuration',
17
+ String) {|service| options[:service] = service }
18
+
19
+ required_option :service
20
+ end
21
+
22
+ def dhcp4_option(code, name, data)
23
+ {
24
+ 'code' => code,
25
+ 'name' => name,
26
+ 'space' => 'dhcp4',
27
+ 'csv-format' => true,
28
+ 'data' => data
29
+ }
30
+ end
31
+
32
+ # The internal representation of the generated configuration, ready
33
+ # to be tested or printed.
34
+
35
+ attr_reader :kea_config
36
+
37
+ def process
38
+ @kea_config = { 'Dhcp4' => {} }
39
+
40
+ jack.each(kind:'service', name:options[:service]) do |service_config|
41
+ @kea_config['Dhcp4'] = service_config['service.kea_v4']
42
+ end
43
+
44
+ @kea_config['Dhcp4']['subnet4'] = []
45
+
46
+ jack.each(kind:'ipv4_network') do |net|
47
+ net_config = {'subnet' => net['net.ipv4.cidr'],
48
+ 'reservations' => [],
49
+ 'option-data' => []}
50
+ if net['net.ipv4.gateway']
51
+ net_config['option-data'] <<
52
+ dhcp4_option(3, 'routers', net['net.ipv4.gateway'])
53
+ end
54
+
55
+ if net['net.dns.resolver']
56
+ resolvers = ''
57
+ net['net.dns.resolver'].each do |resolver|
58
+ resolvers << resolver << ', '
59
+ end
60
+
61
+ net_config['option-data'] <<
62
+ dhcp4_option(6, 'domain-name-servers', resolvers.chomp(', '))
63
+ end
64
+
65
+ if net['net.dhcp.tftp-server']
66
+ net_config['option-data'] <<
67
+ dhcp4_option(66, 'tftp-server-name', net['net.dhcp.tftp-server'])
68
+
69
+ # Option 66/tftp-server-name is the standard way of sending a
70
+ # TFTP server address, but some DHCP clients still want it in
71
+ # the next-server field.
72
+ net_config['next-server'] = net['net.dhcp.tftp-server']
73
+ end
74
+
75
+ if net['net.dhcp.boot-file']
76
+ net_config['option-data'] <<
77
+ dhcp4_option(67, 'boot-file-name', net['net.dhcp.boot-file'])
78
+ end
79
+
80
+ net.children(kind:'ipv4_interface',
81
+ none?:{ 'net.layer2.address' => nil }
82
+ ) do |interface|
83
+ net_config['reservations'] << {
84
+ 'hw-address' => interface['net.layer2.address'],
85
+ 'ip-address' => interface['net.ipv4.address'],
86
+ 'hostname' => interface['net.dns.fqdn'],
87
+ 'option-data' =>
88
+ [
89
+ dhcp4_option(15, 'domain-name',
90
+ interface.parents(kind:'system').first['net.dns.domain'])
91
+ ]
92
+ }
93
+ end
94
+
95
+ @kea_config['Dhcp4']['subnet4'] << net_config
96
+ end
97
+ end
98
+
99
+ def output
100
+ puts git_header('palletjack2kea')
101
+
102
+ jj @kea_config
103
+ end
104
+ end
105
+
106
+ if PalletJack2Kea.standalone?(__FILE__)
107
+ PalletJack2Kea.run
108
+ end
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Write DNS server zone file from a Palletjack warehouse
4
+ #
5
+ # Data model assumptions:
6
+ # - Each domain corresponds uniquely to one IPv4 network
7
+ #
8
+ # The YAML key "net.dns.alias" is used to create CNAME aliases for
9
+ # each interface. This means that if a "system" object specifies
10
+ # "net.dns.alias", each of its interfaces will get that alias in its
11
+ # own domain. Aliases explicitly specified on a single interface will
12
+ # override this.
13
+
14
+ require 'palletjack/tool'
15
+ require 'dns/zone'
16
+ require 'ip'
17
+
18
+ class PalletJack2Knot < PalletJack::Tool
19
+ def parse_options(opts)
20
+ opts.banner =
21
+ "Usage: #{$PROGRAM_NAME} -w <warehouse> -o <output directory>
22
+
23
+ Write DNS server zone files from a Palletjack warehouse"
24
+
25
+ opts.on('-o DIR', '--output DIR', 'output directory', String) {|dir|
26
+ options[:output] = dir
27
+ options[:zone_dir] = config_path(:output, "zones")
28
+ }
29
+
30
+ required_option :output
31
+ end
32
+
33
+ def zone_config(zone)
34
+ "
35
+ #{zone} {
36
+ file \"zones/#{zone}.zone\";
37
+ }
38
+ "
39
+ end
40
+ end
41
+
42
+ PalletJack2Knot.run do
43
+ config_dir :zone_dir
44
+
45
+ # Use Unix timestamp as serial number, and get it once so all zones get
46
+ # the same one
47
+ serial = Time.now.utc.to_i
48
+
49
+ config_file :output, 'zones.conf' do |conf_file|
50
+ conf_file << git_header('palletjack2knot')
51
+
52
+ jack.each(kind:'domain') do |domain|
53
+ absolute_domain_name = "#{domain['net.dns.domain']}."
54
+
55
+ zone = DNS::Zone.new
56
+
57
+ zone.origin = absolute_domain_name
58
+ zone.ttl = domain['net.dns.ttl']
59
+
60
+ zone.soa.serial = serial
61
+ zone.soa.label = absolute_domain_name
62
+ zone.soa.nameserver = domain['net.dns.soa-ns']
63
+ zone.soa.email = "#{domain['net.dns.soa-contact']}.".sub('@', '.')
64
+
65
+ if domain['net.dns.mx']
66
+ domain['net.dns.mx'].each do |server|
67
+ mx = DNS::Zone::RR::MX.new
68
+ mx.label = absolute_domain_name
69
+ mx.priority = server['priority']
70
+ mx.exchange = server['server']
71
+ zone.records << mx
72
+ end
73
+ end
74
+
75
+ domain['net.dns.ns'].each do |address|
76
+ ns = DNS::Zone::RR::NS.new
77
+ ns.label = absolute_domain_name
78
+ ns.nameserver = address
79
+ zone.records << ns
80
+ end
81
+
82
+ if domain['net.dns.cname']
83
+ domain['net.dns.cname'].each do |name, target|
84
+ cname = DNS::Zone::RR::CNAME.new
85
+ cname.label = name
86
+ cname.domainname = target
87
+ zone.records << cname
88
+ end
89
+ end
90
+
91
+ if domain['net.dns.srv']
92
+ domain['net.dns.srv'].each do |service|
93
+ srv = DNS::Zone::RR::SRV.new
94
+ srv.label = "_#{service['service']}._#{service['protocol']}"
95
+ srv.target = service['target']
96
+ srv.port = service['port']
97
+ service['priority'] ||= 0
98
+ srv.priority = service['priority']
99
+ service['weight'] ||= 0
100
+ srv.weight = service['weight']
101
+ zone.records << srv
102
+ end
103
+ end
104
+
105
+ domain.children(kind:'ipv4_interface') do |interface|
106
+ a = DNS::Zone::RR::A.new
107
+ a.label = interface['net.dns.name']
108
+ a.address = interface['net.ipv4.address']
109
+ zone.records << a
110
+
111
+ if interface['net.dns.alias']
112
+ interface['net.dns.alias'].each do |label|
113
+ cname = DNS::Zone::RR::CNAME.new
114
+ cname.label = label
115
+ cname.domainname = interface['net.dns.name']
116
+ zone.records << cname
117
+ end
118
+ end
119
+ end
120
+
121
+ config_file :zone_dir, "#{domain['net.dns.domain']}.zone" do |zonefile|
122
+ zonefile << git_header('palletjack2knot', comment_char: ';')
123
+ zonefile << zone.dump_pretty
124
+ end
125
+
126
+ conf_file << zone_config(domain['net.dns.domain'])
127
+
128
+ next unless domain['net.ipv4.cidr']
129
+
130
+ # Assume all delegations happen on octet boundaries for now.
131
+ # TODO: RFC 2317 classless in-addr.arpa delegation
132
+
133
+ reverse_zone = DNS::Zone.new
134
+
135
+ ip_net = IP.new(domain['net.ipv4.cidr'])
136
+ absolute_reverse_zone_name = ip_net.to_arpa
137
+
138
+ prefix_octets, _ = domain['net.ipv4.prefixlen'].to_i.divmod(8)
139
+ reverse_zone.origin = absolute_reverse_zone_name.split('.')[-(2 + prefix_octets) .. 5].join('.')
140
+
141
+ reverse_zone.ttl = domain['net.dns.ttl']
142
+
143
+ reverse_zone.soa.serial = serial
144
+ reverse_zone.soa.label = "#{reverse_zone.origin}."
145
+ reverse_zone.soa.nameserver = domain['net.dns.soa-ns']
146
+ reverse_zone.soa.email = "#{domain['net.dns.soa-contact']}.".sub('@', '.')
147
+
148
+ domain['net.dns.ns'].each do |address|
149
+ ns = DNS::Zone::RR::NS.new
150
+ ns.label = "#{reverse_zone.origin}."
151
+ ns.nameserver = address
152
+ reverse_zone.records << ns
153
+ end
154
+
155
+ domain.children(kind:'ipv4_interface') do |interface|
156
+ ptr = DNS::Zone::RR::PTR.new
157
+ ptr.label = IP.new(interface['net.ipv4.address']).to_arpa
158
+ ptr.name = "#{interface['net.dns.fqdn']}."
159
+ reverse_zone.records << ptr
160
+ end
161
+
162
+ config_file :zone_dir, "#{reverse_zone.origin}.zone" do |zonefile|
163
+ zonefile << git_header('palletjack2knot', comment_char: ';')
164
+ zonefile << reverse_zone.dump_pretty
165
+ end
166
+
167
+ conf_file << zone_config(reverse_zone.origin)
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Write PXELINUX boot menus from a Palletjack warehouse
4
+ #
5
+ # Data model assumptions:
6
+ #
7
+ # Each +os+ object will contain the keys:
8
+ # host:
9
+ # kickstart:
10
+ # baseurl: <URL to OS installation>
11
+ # pxelinux:
12
+ # config: <basename of pxelinux menu file w/o .menu>
13
+ #
14
+ # Each +netinstall+ object will contain the keys:
15
+ # host:
16
+ # kickstart:
17
+ # label: <human-friendly label for variant>
18
+ # ksurl: ["none"|<URL to kickstart file>]
19
+ # pxelinux:
20
+ # config: <basename of pxelinux menu file w/o .menu>
21
+ # kernel: <tftp path of pxelinux kernel>
22
+ # append: <pxelinux kernel parameters>
23
+ #
24
+ # PXELINUX directory tree:
25
+ #
26
+ # /var/lib/tftpboot/
27
+ # |- pxelinux.cfg
28
+ # | |- default -> ../config/default
29
+ # | |- 01-<MAC address> -> ../config/<OS>-<Variant>.menu
30
+ # | ...
31
+ # |- config
32
+ # | |- default
33
+ # | |- linux.menu
34
+ # | |- <OS>.menu
35
+ # | |- <OS>-<Variant>.menu
36
+ # | ...
37
+ # \- boot
38
+ # |- <OS>
39
+ # | |- vmlinuz
40
+ # | |- initrd
41
+ # | ...
42
+ # ...
43
+ #
44
+ # Point this script's output directory at /var/lib/tftpboot/, and it
45
+ # will create the MAC address symlinks, and menu tree for linux
46
+ # installations.
47
+ #
48
+ # The boot/... and config/default needs to be populated by other means.
49
+
50
+ require 'palletjack/tool'
51
+ require 'fileutils'
52
+
53
+ class PalletJack2PXELinux < PalletJack::Tool
54
+
55
+ def parse_options(opts)
56
+ opts.banner =
57
+ "Usage: #{$PROGRAM_NAME} -w <warehouse> -o <output directory>
58
+
59
+ Write PXELINUX boot configuration files from a Palletjack warehouse"
60
+
61
+ opts.on('-o DIR', '--output DIR', 'output directory (tftpboot/)',
62
+ String) {|dir| options[:output] = dir }
63
+
64
+ required_option :output
65
+ end
66
+
67
+ # Generate pxe menus for each OS configuration
68
+
69
+ def pxemenu_for_netinstall(netinstall)
70
+ config=netinstall['host.pxelinux.config']
71
+ os_label=netinstall['system.os'].gsub(/-/, ' ')
72
+ ks_label=(netinstall['host.kickstart.label'] ||
73
+ netinstall['host.kickstart.variant'] )
74
+ kernel=netinstall['host.pxelinux.kernel']
75
+ append=netinstall['host.pxelinux.append']
76
+
77
+ config_file :output, 'config', "#{config}.menu" do |menufile|
78
+ menufile << git_header('palletjack2pxelinux')
79
+ menufile << "
80
+ UI menu.c32
81
+ PROMPT 0
82
+ MENU INCLUDE /config/graphics.conf
83
+ MENU AUTOBOOT Installing #{os_label} (#{ks_label}) in # seconds
84
+ TIMEOUT 100
85
+
86
+ MENU TITLE Install #{os_label} (#{ks_label})
87
+
88
+ LABEL #{config}
89
+ MENU LABEL ^Install #{os_label} (#{ks_label})
90
+ MENU DEFAULT
91
+ KERNEL #{kernel}
92
+ APPEND #{append}
93
+
94
+ LABEL MainMenu
95
+ MENU LABEL ^Main Menu
96
+ KERNEL menu.c32
97
+ APPEND /config/default
98
+ "
99
+ end
100
+ end
101
+
102
+ # Generate pxe menus for each OS version
103
+
104
+ def pxemenu_for_operating_system(os)
105
+ config=os['host.pxelinux.config']
106
+ os_label=config.gsub(/-/, ' ')
107
+ kernel=os['host.pxelinux.kernel']
108
+ append=os['host.pxelinux.append']
109
+
110
+ config_file :output, 'config', "#{config}.menu" do |menufile|
111
+ menufile << git_header('palletjack2pxelinux')
112
+ menufile << "
113
+ UI menu.c32
114
+ PROMPT 0
115
+ MENU INCLUDE /config/graphics.conf
116
+
117
+ MENU TITLE #{os_label} Installation Menu
118
+ "
119
+
120
+ os.children(kind:'netinstall') do |netinstall|
121
+ config=netinstall['host.pxelinux.config']
122
+ ks_label=(netinstall['host.kickstart.label'] ||
123
+ netinstall['host.kickstart.variant'] )
124
+ kernel=netinstall['host.pxelinux.kernel']
125
+ append=netinstall['host.pxelinux.append']
126
+
127
+ pxemenu_for_netinstall(netinstall)
128
+
129
+ menufile << "
130
+ LABEL #{config}
131
+ MENU LABEL Install #{os_label} (#{ks_label})
132
+ KERNEL menu.c32
133
+ APPEND /config/#{config}.menu
134
+ "
135
+ end
136
+
137
+ menufile << "
138
+ LABEL MainMenu
139
+ MENU LABEL ^Main Menu
140
+ KERNEL menu.c32
141
+ APPEND /config/default
142
+ "
143
+ end
144
+ end
145
+ end
146
+
147
+ PalletJack2PXELinux.run do
148
+ config_dir :output, 'config'
149
+ config_dir :output, 'pxelinux.cfg'
150
+
151
+ config_file :output, 'config', 'linux.menu' do |menufile|
152
+ menufile << git_header('palletjack2pxelinux')
153
+ menufile << "
154
+ UI menu.c32
155
+ PROMPT 0
156
+ MENU INCLUDE /config/graphics.conf
157
+
158
+ MENU TITLE Linux Installation Menu
159
+ "
160
+
161
+ jack.each(kind:'os') do |os|
162
+ next unless config=os['host.pxelinux.config']
163
+
164
+ label=config.gsub(/-/, ' ')
165
+
166
+ pxemenu_for_operating_system(os)
167
+
168
+ menufile << "
169
+ LABEL #{config}
170
+ MENU LABEL Install #{label}
171
+ KERNEL menu.c32
172
+ APPEND /config/#{config}.menu
173
+ "
174
+ end
175
+ end
176
+
177
+ # Generate pxe default menu links for each system
178
+
179
+ jack.each(kind:'system') do |system|
180
+ system.children(kind:'ipv4_interface') do |nic|
181
+ if system['host.pxelinux.config']
182
+ menuname = "#{system['host.pxelinux.config']}.menu"
183
+ linkname = "01-#{nic['net.layer2.address'].gsub(':', '-').downcase}"
184
+
185
+ FileUtils.ln_s(config_path('..', 'config', menuname),
186
+ config_path(:output, 'pxelinux.cfg', linkname),
187
+ :force => true)
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Write YAML files containing Salt pillar data.
4
+ #
5
+ # Intended to be run from a Git post-update hook or similar, since
6
+ # running the entire Pallet Jack infrastructure once per minion for
7
+ # every pillar refresh is a bit excessive, writing YAML files which
8
+ # will be read by a Salt pillar, in one of two modes of operation:
9
+ #
10
+ # 1. Global pillar data
11
+ #
12
+ # Intended to be included in an ordinary Salt pillar root.
13
+ #
14
+ # Example Salt master configuration:
15
+ #
16
+ # pillar_roots:
17
+ # base:
18
+ # - /etc/salt/pillar
19
+ #
20
+ # Example /etc/salt/pillar/top.sls:
21
+ #
22
+ # base:
23
+ # '*':
24
+ # - palletjack.global
25
+ #
26
+ # 2. Per-minion pillar data
27
+ #
28
+ # Intended to be read by the `palletjack_yaml_file` external pillar
29
+ # (which you will have to install manually).
30
+ #
31
+ # Example Salt master configuration:
32
+ #
33
+ # ext_pillar:
34
+ # - palletjack_yaml_file: /var/cache/palletjack/{saltenv}/{minion}.yaml
35
+ #
36
+ # Data model assumptions:
37
+ # - Salt minion ID is FQDN
38
+
39
+ require 'palletjack/tool'
40
+ require 'yaml'
41
+
42
+ class PalletJack2Salt < PalletJack::Tool
43
+ def parse_options(opts)
44
+ opts.banner =
45
+ "Usage: #{$PROGRAM_NAME} -w <warehouse> [ -g <dir> ] [ -m <dir> ]
46
+
47
+ Write Salt pillar data from a Palletjack warehouse."
48
+
49
+ opts.on('-g DIR', '--global DIR', 'global pillar directory',
50
+ String ) {|dir| options[:global_pillar] = dir }
51
+ opts.on('-m DIR', '--minion DIR', 'per-minion pillar directory',
52
+ String) {|dir| options[:minion_pillar] = dir }
53
+
54
+ required_option :global_pillar, :minion_pillar
55
+ end
56
+
57
+ # Helpers for generating global pillars
58
+
59
+ def process_global_pillar
60
+ result = {}
61
+
62
+ config['pillar.global.each-pallet'].each do |dst_key, kind|
63
+ result[dst_key] ||= {}
64
+
65
+ jack.each(kind: kind) do |pallet|
66
+ result[dst_key][pallet.name] = pallet.to_hash.except('pallet')
67
+ end
68
+ end
69
+
70
+ result
71
+ end
72
+
73
+ # Helpers for generating per-minion pillars
74
+
75
+ def ipv4_interfaces(system)
76
+ result = Hash.new
77
+
78
+ system.children(kind:'ipv4_interface') do |interface|
79
+ result[interface['net.layer2.name']] = {
80
+ interface['net.ipv4.address'] =>
81
+ interface.filter('net.ipv4', 'net.layer2').to_hash
82
+ }
83
+ end
84
+
85
+ result
86
+ end
87
+
88
+ def process_minion_pillars
89
+ result = {}
90
+
91
+ jack.each(kind:'system') do |system|
92
+ yaml_output = {}
93
+ yaml_output['ipv4_interfaces'] = ipv4_interfaces(system)
94
+
95
+ config['pillar.per-minion.each-subtree'].each do |dst_key, src_key|
96
+ yaml_output[dst_key] = system.fetch(src_key)
97
+ end
98
+
99
+ result[system['net.dns.fqdn']] = { 'palletjack' => yaml_output }
100
+ end
101
+
102
+ result
103
+ end
104
+
105
+ # The internal representation of the generated configuration, ready
106
+ # to be tested or printed.
107
+
108
+ attr_reader :salt_config
109
+
110
+ def process
111
+ @salt_config = {}
112
+
113
+ @salt_config[:global] = process_global_pillar if options[:global_pillar]
114
+ @salt_config[:minion] = process_minion_pillars if options[:minion_pillar]
115
+ end
116
+
117
+ def output
118
+ if @salt_config[:global]
119
+ config_dir :global_pillar, 'palletjack'
120
+ config_file :global_pillar, 'palletjack', 'global.sls' do |slsfile|
121
+ slsfile << git_header('palletjack2salt')
122
+ slsfile <<
123
+ { 'palletjack' => { 'global' => @salt_config[:global] } }.to_yaml
124
+ end
125
+ end
126
+
127
+ if @salt_config[:minion]
128
+ @salt_config[:minion].each do |id, config|
129
+ config_file :minion_pillar, "#{id}.yaml" do |yamlfile|
130
+ yamlfile << git_header('palletjack2salt')
131
+ yamlfile << config.to_yaml
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ if PalletJack2Salt.standalone?(__FILE__)
139
+ PalletJack2Salt.run
140
+ end
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Write DNS resolver stub declarations from a Palletjack warehouse
4
+ #
5
+ # Data model assumptions:
6
+ #
7
+ # service:
8
+ # unbound:
9
+ # server:
10
+ # - interface: 0.0.0.0
11
+ # - access-control: 192.0.2.0/24 allow
12
+ # - ...
13
+ #
14
+ # Other configuration categories than +system+ are not supported.
15
+
16
+ require 'palletjack/tool'
17
+ require 'ip'
18
+
19
+ class PalletJack2Unbound < PalletJack::Tool
20
+ def parse_options(opts)
21
+ opts.banner =
22
+ "Usage: #{$PROGRAM_NAME} -w <warehouse> -s <service> -o <output directory>
23
+
24
+ Write DNS resolver stub declarations from a Palletjack warehouse
25
+ into Salt state configuration for unbound.
26
+
27
+ E.g.
28
+ palletjack2unbound -w /etc/salt/respository/warehouse \\
29
+ -o /etc/salt/repository/state/unbound/files"
30
+
31
+ opts.on('-o DIR', '--output DIR', 'output directory', String) {|dir|
32
+ options[:output] = dir
33
+ options[:conf_dir] = "#{dir}/conf.d"
34
+ options[:local_dir] = "#{dir}/local.d"
35
+ }
36
+ opts.on('-s SERVICE', '--service SERVICE',
37
+ 'service name for global configuration', String) {|service|
38
+ options[:service] = service
39
+ }
40
+
41
+ required_option :output
42
+ required_option :service
43
+ end
44
+
45
+ # Write a stub-zone declaration to a file in conf.d/
46
+ # If the +transparent+ option is true, also write
47
+ # a local-zone ... transparent, declaration e.g. to
48
+ # override the builtin RFC1918 blocking in unbound.
49
+
50
+ def stub_zone(zone, stub_addrs, transparent: false)
51
+ return if stub_addrs.empty?
52
+
53
+ config_file :conf_dir, "#{zone}.conf" do |stubfile|
54
+ stubfile << git_header('palletjack2unbound')
55
+ stubfile << "
56
+ stub-zone:
57
+ name: #{zone}\n"
58
+
59
+ stub_addrs.each do |addr|
60
+ stubfile << " stub-addr: #{addr}\n"
61
+ end
62
+
63
+ if transparent then
64
+ stubfile << "\nserver:\n local-zone: \"#{zone}\" transparent\n"
65
+ end
66
+ end
67
+ end
68
+
69
+ # Check if +ip+ belongs to some RFC1918 network
70
+
71
+ def rfc1918?(ip)
72
+ rfc1918_nets = [IP.new('10.0.0.0/8'),
73
+ IP.new('172.16.0.0/12'),
74
+ IP.new('192.168.0.0/16')]
75
+ rfc1918_nets.any? {|net| ip.is_in?(net)}
76
+ end
77
+
78
+ # Generate unbound service configuration
79
+
80
+ def unbound_config(service_name)
81
+ service_config = jack.fetch(kind:'service', name: service_name)
82
+
83
+ config_file :local_dir, "#{service_name}.conf" do |configfile|
84
+ configfile << git_header('palletjack2unbound')
85
+ service_config["service.unbound.server"].each do |config|
86
+ config.each do |key, value|
87
+ configfile << "#{key}: #{value}\n"
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ PalletJack2Unbound.run do
95
+ config_dir :conf_dir
96
+ config_dir :local_dir
97
+
98
+ jack.each(kind:'domain') do |domain|
99
+ zone = domain['net.dns.domain']
100
+ stub_addrs = []
101
+
102
+ domain['net.dns.ns'].each do |ns|
103
+ jack.each(kind:'ipv4_interface', all?:{'net.dns.fqdn' => ns}) do |ipv4|
104
+ stub_addrs << ipv4['net.ipv4.address']
105
+ end
106
+ end
107
+
108
+ stub_zone(zone, stub_addrs)
109
+
110
+ next unless domain['net.ipv4.cidr']
111
+
112
+ # Assume all delegations happen on octet boundaries for now.
113
+ # TODO: RFC 2317 classless in-addr.arpa delegation
114
+
115
+ ip_net = IP.new(domain['net.ipv4.cidr'])
116
+ reverse_zone = ip_net.to_arpa
117
+ prefix_octets, _ = domain['net.ipv4.prefixlen'].to_i.divmod(8)
118
+ reverse_zone =
119
+ ip_net.to_arpa.split('.')[-(2 + prefix_octets) .. 5].join('.')
120
+
121
+ # Make the same assumption that palletjack2knot does;
122
+ # reverse delegations are made to the same nameserver
123
+ # as forward delegations.
124
+
125
+ stub_zone(reverse_zone, stub_addrs, transparent: rfc1918?(ip_net))
126
+ end
127
+
128
+ unbound_config(options[:service])
129
+ end
@@ -0,0 +1,37 @@
1
+ #-*- ruby -*-
2
+ # coding: utf-8
3
+ lib = File.expand_path('../../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'palletjack/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'palletjack-tools'
9
+ spec.version = PalletJack::VERSION
10
+ spec.authors = ['Karl-Johan Karlsson']
11
+ spec.email = ['karl-johan.karlsson@saabgroup.com']
12
+ spec.summary = 'Tools for the Pallet Jack Lightweight Configuration Management Database'
13
+ spec.description = spec.summary
14
+ spec.homepage = 'https://github.com/saab-simc-admin/palletjack'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+
23
+ spec.platform = Gem::Platform::RUBY
24
+ spec.required_ruby_version = '~>2'
25
+
26
+ spec.add_runtime_dependency 'palletjack', PalletJack::VERSION
27
+ spec.add_runtime_dependency 'dns-zone', '~> 0.3'
28
+ spec.add_runtime_dependency 'ruby-ip', '~> 0.9'
29
+
30
+ spec.add_development_dependency "bundler", "~> 1.13"
31
+ spec.add_development_dependency "rake", "~> 10.0"
32
+ spec.add_development_dependency "rspec", "~> 3.0"
33
+ spec.add_development_dependency "rspec_structure_matcher", "~> 0.0.6"
34
+ spec.add_development_dependency "rspec-collection_matchers", "~> 1.1.2"
35
+
36
+ spec.has_rdoc = true
37
+ end
data.tar.gz.sig ADDED
Binary file
metadata ADDED
@@ -0,0 +1,200 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: palletjack-tools
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Karl-Johan Karlsson
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain:
11
+ - |
12
+ -----BEGIN CERTIFICATE-----
13
+ MIIDljCCAn6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBIMRYwFAYDVQQDDA1jYWxs
14
+ ZS5lbmdsdW5kMRkwFwYKCZImiZPyLGQBGRYJc2FhYmdyb3VwMRMwEQYKCZImiZPy
15
+ LGQBGRYDY29tMB4XDTE2MTEwMjA5MjYyN1oXDTE3MTEwMjA5MjYyN1owSDEWMBQG
16
+ A1UEAwwNY2FsbGUuZW5nbHVuZDEZMBcGCgmSJomT8ixkARkWCXNhYWJncm91cDET
17
+ MBEGCgmSJomT8ixkARkWA2NvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
18
+ ggEBAM7OxaztzD0LyOwK1mPcg3BhioX1EDVbD/qAFOAzBSGGlAhtmHMqAkyvJMvs
19
+ iiG7xvBidWUapxiEiBwamXiOTSrp2eW+XSXW9omdWHXjBZcwHqwb1VmAlYRDkSHf
20
+ dzcM/z4xlV+DJw/pFyMRWzqNdVBtWTbVXAFGjJSqQ6q21ACYJldV9U71AIpXo+oF
21
+ VEMf6PZS2uhB1G+FgAtnX/xmy7OM1Cy3qc/CaJbWSddpegxWJMUn2HNQxFwIe40g
22
+ WoEoiFA7qQg9DnR/5i3lW6QyfIaA5k9cv2su1VyjqKLbkFTTTjYw0P1BJmvfXjtc
23
+ rMl+3HCWYj6UunZwfZi2wDGsBkkCAwEAAaOBijCBhzAJBgNVHRMEAjAAMAsGA1Ud
24
+ DwQEAwIEsDAdBgNVHQ4EFgQUwHCMEKgrIMaiTkTVLKZn6yOD1SIwJgYDVR0RBB8w
25
+ HYEbY2FsbGUuZW5nbHVuZEBzYWFiZ3JvdXAuY29tMCYGA1UdEgQfMB2BG2NhbGxl
26
+ LmVuZ2x1bmRAc2FhYmdyb3VwLmNvbTANBgkqhkiG9w0BAQUFAAOCAQEAP9OnE0jP
27
+ 2vRHI/vnOkgCvLFNoOqK/YB4yDVVW69Pza+xIXcmUBvl7DQ+bBdF5AK0B1A7U0rp
28
+ Pbdj0bpQtWxmUmMIbnE1w6iuVCXAabsyUfHY4mlztToWXMVOXc1SPlJ/S2XXaRd5
29
+ fiNj/nBTb0YTQA0E4pZ0Aud80qZ2WLdc6FfzHUEMW91BL3bhLeDL40noHK5Lvk52
30
+ phzVHIrDjCowUMTnGiPZCXEo4KZW76KwYYV6oQ6LzcrYBw5mJ4XpdgQKZgnTnRBP
31
+ f8wtQllq82VF0AXUYeLtTh1f+DW3WW5BO1e2OCu5eOV7dbyaVPaNK/+rHjCN8kM/
32
+ DGZSwUoNADmVkQ==
33
+ -----END CERTIFICATE-----
34
+ date: 2016-11-02 00:00:00.000000000 Z
35
+ dependencies:
36
+ - !ruby/object:Gem::Dependency
37
+ name: palletjack
38
+ requirement: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - '='
41
+ - !ruby/object:Gem::Version
42
+ version: 0.1.2
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - '='
48
+ - !ruby/object:Gem::Version
49
+ version: 0.1.2
50
+ - !ruby/object:Gem::Dependency
51
+ name: dns-zone
52
+ requirement: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ~>
55
+ - !ruby/object:Gem::Version
56
+ version: '0.3'
57
+ type: :runtime
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ~>
62
+ - !ruby/object:Gem::Version
63
+ version: '0.3'
64
+ - !ruby/object:Gem::Dependency
65
+ name: ruby-ip
66
+ requirement: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ~>
69
+ - !ruby/object:Gem::Version
70
+ version: '0.9'
71
+ type: :runtime
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '0.9'
78
+ - !ruby/object:Gem::Dependency
79
+ name: bundler
80
+ requirement: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ~>
83
+ - !ruby/object:Gem::Version
84
+ version: '1.13'
85
+ type: :development
86
+ prerelease: false
87
+ version_requirements: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ~>
90
+ - !ruby/object:Gem::Version
91
+ version: '1.13'
92
+ - !ruby/object:Gem::Dependency
93
+ name: rake
94
+ requirement: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ~>
97
+ - !ruby/object:Gem::Version
98
+ version: '10.0'
99
+ type: :development
100
+ prerelease: false
101
+ version_requirements: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ~>
104
+ - !ruby/object:Gem::Version
105
+ version: '10.0'
106
+ - !ruby/object:Gem::Dependency
107
+ name: rspec
108
+ requirement: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ~>
111
+ - !ruby/object:Gem::Version
112
+ version: '3.0'
113
+ type: :development
114
+ prerelease: false
115
+ version_requirements: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ~>
118
+ - !ruby/object:Gem::Version
119
+ version: '3.0'
120
+ - !ruby/object:Gem::Dependency
121
+ name: rspec_structure_matcher
122
+ requirement: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ~>
125
+ - !ruby/object:Gem::Version
126
+ version: 0.0.6
127
+ type: :development
128
+ prerelease: false
129
+ version_requirements: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ~>
132
+ - !ruby/object:Gem::Version
133
+ version: 0.0.6
134
+ - !ruby/object:Gem::Dependency
135
+ name: rspec-collection_matchers
136
+ requirement: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ~>
139
+ - !ruby/object:Gem::Version
140
+ version: 1.1.2
141
+ type: :development
142
+ prerelease: false
143
+ version_requirements: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ~>
146
+ - !ruby/object:Gem::Version
147
+ version: 1.1.2
148
+ description: Tools for the Pallet Jack Lightweight Configuration Management Database
149
+ email:
150
+ - karl-johan.karlsson@saabgroup.com
151
+ executables:
152
+ - create_domain
153
+ - create_ipv4_interface
154
+ - create_system
155
+ - dump_pallet
156
+ - palletjack2kea
157
+ - palletjack2knot
158
+ - palletjack2pxelinux
159
+ - palletjack2salt
160
+ - palletjack2unbound
161
+ extensions: []
162
+ extra_rdoc_files: []
163
+ files:
164
+ - Gemfile
165
+ - Rakefile
166
+ - exe/create_domain
167
+ - exe/create_ipv4_interface
168
+ - exe/create_system
169
+ - exe/dump_pallet
170
+ - exe/palletjack2kea
171
+ - exe/palletjack2knot
172
+ - exe/palletjack2pxelinux
173
+ - exe/palletjack2salt
174
+ - exe/palletjack2unbound
175
+ - palletjack-tools.gemspec
176
+ homepage: https://github.com/saab-simc-admin/palletjack
177
+ licenses:
178
+ - MIT
179
+ metadata: {}
180
+ post_install_message:
181
+ rdoc_options: []
182
+ require_paths:
183
+ - lib
184
+ required_ruby_version: !ruby/object:Gem::Requirement
185
+ requirements:
186
+ - - ~>
187
+ - !ruby/object:Gem::Version
188
+ version: '2'
189
+ required_rubygems_version: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - '>='
192
+ - !ruby/object:Gem::Version
193
+ version: '0'
194
+ requirements: []
195
+ rubyforge_project:
196
+ rubygems_version: 2.0.14
197
+ signing_key:
198
+ specification_version: 4
199
+ summary: Tools for the Pallet Jack Lightweight Configuration Management Database
200
+ test_files: []
metadata.gz.sig ADDED
@@ -0,0 +1,3 @@
1
+ �.��۩'1����:�򙘹TF��TX̉�]]��ZY��bWـ�R}Jo{���$S1cǹ�%�M��W���� ��_����S���wE�ѣl �8!$ҩ��?]s�0=���c�Ud�ς�!;
2
+ ��L���;|�Y�9jq�
3
+ �V��h��5����s��Yw�ZEʰ�o~n�V~�N��v����!�T?XO5忊j��$R�O���AT�3��W<�lѯ*J=֥��vcg78F��A�3�,Q ��ڌn�