palletjack-tools 0.1.2

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