diff --git a/.circleci/config.yml b/.circleci/config.yml index 8af810ef..93f73246 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,3 +8,16 @@ workflows: - kitchen/danger: name: danger context: Danger-Minimal + kitchen-v6: + jobs: + - kitchen/dokken-single: + enable-ipv6: true + matrix: + parameters: + suite: + - default-centos-6 + - default-centos-7 + - default-centos-8 + - default-fedora-31 + - default-debian-10 + - default-ubuntu-1804 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f3b35e97..341684fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,11 +40,15 @@ jobs: strategy: matrix: os: - - 'debian-8' + - 'debian-10' + - 'centos-6' - 'centos-7' - - 'ubuntu-1604' + - 'centos-8' + - 'fedora-31' + - 'ubuntu-1804' suite: - 'default' + - 'delete' fail-fast: false steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index f1e46ecd..c05aab8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,18 @@ # CHANGELOG -## Unreleased - -- Migrated to github actions +## 7.0.0 (2020-04-13) + +Version 7.0.0 is a **major** change! Please see [UPGRADING.md](./UPGRADING.md). + +- DHCPv6 server configuration support - [@bmhughes](https://github.com/bmhughes) +- Migrated to github actions - [@Xorima](https://github.com/Xorima) +- Remodel cookbook as resource library - [@bmhughes](https://github.com/bmhughes) +- Remove - [@bmhughes](https://github.com/bmhughes) + - Attributes + - Recipes + - Data bag functionality +- Add resources to manage install and services for dhcpd/dhcpd6 - [@bmhughes](https://github.com/bmhughes) +- Rewrite resources to current standard removing pure ruby code - [@bmhughes](https://github.com/bmhughes) ## 6.1.0 (2019-10-19) diff --git a/README.md b/README.md index 2b1d07d2..01708079 100644 --- a/README.md +++ b/README.md @@ -6,446 +6,96 @@ [![OpenCollective](https://opencollective.com/sous-chefs/sponsors/badge.svg)](#sponsors) [![License](https://img.shields.io/badge/License-Apache%202.0-green.svg)](https://opensource.org/licenses/Apache-2.0) -## Description +Installs and configuration ISC DHCP server for both DHCP and DHCPv6. -Data bag and Attribute driven DHCP server. - -- Supports setting up Master/Master ISC DHCP failover. +- Supports setting up Master/Master ISC DHCP failover (IPv4 only). - Includes Support for DDNS -- Includes LWRPs for managing hosts, groups and subnets. -- Use databags or attributes + wrapper cookbooks to manage DHCP server [Example](examples/attribute_based.rb) +- Includes resources for managing hosts, groups and subnets. -Large parts were borrowed from work initially done by Dell, extended by Atalanta Systems and reworked by Matt Ray and Chef. Big thanks to all of them. +Version 7.0.0 constitutes a major change and rewrite, please see [UPGRADING.md](./UPGRADING.md). ## Maintainers This cookbook is maintained by the Sous Chefs. The Sous Chefs are a community of Chef cookbook maintainers working together to maintain important cookbooks. If you’d like to know more please visit [sous-chefs.org](https://sous-chefs.org/) or come chat with us on the Chef Community Slack in [#sous-chefs](https://chefcommunity.slack.com/messages/C2V7B88SF). -## Requirements +## Platforms - Debian / Ubuntu -- CentOS and derivatives +- RHEL/CentOS and derivatives - Fedora and derivatives -## Chef-client - -- 13+ - -## Limitations - -- only set up to handle ipv4 - -## Recipes - -### server - -The node will install and configure the `dhcp-server` application. Configuration is through various dhcp_X bags, and the current role/environment - -## Node Attributes - -Almost every one of the parameters and options can be over-ridden in the host/group/network scope. These are general defaults for the server. - -Check out the [man page][1] for details on dhcpd.conf options/params. - -attribute | Type | Default | description -:---------------------------------- | :-------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------- -`node[:dhcp][:use_bags]` | `Boolean` | true | When false we won't attempt to load data from bags. -`node[:dhcp][:hosts]` | `Array` | `[]` | The list of hosts items that this server should load -`node[:dhcp][:groups]` | `Array` | `[]` | The list of group items this server should load -`node[:dhcp][:networks]` | `Array` | `[]` | The list of network items this node should load -`node[:dhcp][:interfaces]` | `Array` | `[]` | The Network Interface(s) to listen on. If an empty list then we will listen on all interfaces. -`node[:dhcp][:rndc_keys]` | `Array` | Attribute based representation of rndc keys you want to use. -`node[:dhcp][:hosts_bag]` | `String` | dhcp_hosts | The name of the data bag that holds host items. -`node[:dhcp][:groups_bag]` | `String` | dhcp_groups | The name of the data bag that holds group items -`node[:dhcp][:networks_bag]` | `String` | dhcp_networks | The name of the data bag that holds network items -`node[:dhcp][:host_data]` | `Hash` | `{}` | Hash of hosts data. The `node[dhcp][:hosts]` entries should have a corresponding entry here when not using bags -`node[:dhcp][:group_data]` | `Hash` | `{}` | Same as host_data, but for groups -`node[:dhcp][:network_data]` | `Hash` | `{}` | Same as host_data, but for networks -`node[:dhcp][:failover]` | `Boolean` | `false` | Enable Failover support buy setting to `true` -`node[:dhcp][:failover_lease_hack]` | `Boolean` | `false` | Force partner-down state on start by rewriting lease file -`node[:dhcp][:allows]` | `Array` | ["booting", "bootp", "unknown-clients"] | Global Dhcpd allow entries -`node[:dhcp][:my_ip]` | `String` | Nil | Set host IP address in failover setup. node[:ipaddress] is used if empty. -`node[:dhcp][:masters]` | `Array` | Array of hashes to override node search for masters. Must have :ipaddress key. -`node[:dhcp][:slaves]` | `Array` | Array of hashes to over-ride node search for slaves. -`node['dhcp']['extra_files']` | `Array` | Array of file paths to also include as configs in `node['dhcp']['config_file']` -`node['dhcp']['hooks']` | `Hash` | Ability to run scripts when events occur. More info @ and [example](spec/_config_spec.rb) - -### DHCP Global Options - -attribute | Type | Default | description -:--------------------------------------------- | :------: | :-------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -`node[:dhcp][:options]['domain-name-servers']` | `String` | "8.8.8.8, 8.8.4.4" | List of DNS servers to send to clients in the global scope -`node[:dhcp][:options]['host-name']` | `String` | " = binary-to-ascii (16, 8, \"-\", substring (hardware, 1, 6))" | Options for global scope host-name settings. The default here will generate a host as MAC address if the node doesn't provide a hostname or is not defined in dns/hosts/groups. -`node[:dhcp][:options]['domain-name']` | `String` | "\"#{domain}\"" | Set domainname in the global scope to this nodes domain - -### DHCP Global Parameters - -These are all just k/v entries in the global params hash. All values are type: `String` - -attribute | Type | Default | description -:------------------------------------------------- | :------: | :--------------: | :---------------------------------------------------------------------------------------------------------- -`node[:dhcp][:parameters]["default-lease-time"]` | `String` | "6400" | Set the default lease time in the global scope -`node[:dhcp][:parameters]["ddns-domainname"]` | `String` | `"#{domain}"` | Set the DDNS domain to this nodes domain -`node[:dhcp][:parameters]["ddns-update-style"]` | `String` | "interim" | DDNS Update style -`node[:dhcp][:parameters]["max-lease-time"]` | `String` | "86400" | Max Lease time -`node[:dhcp][:parameters]["update-static-leases"]` | `String` | "true" | Make sure we push static IP adresses defined in groups/hosts to the dns server -`node[:dhcp][:parameters]["one-lease-per-client"]` | `String` | "true" | When a client requests an IP it will release any held leases. -`node[:dhcp][:parameters]["authoritative"]` | `String` | "" | This setting has no value on purpose: that's how isc-dhcpd wants it. -`node[:dhcp][:parameters]["ping-check"]` | `String` | "true" | Enable Address collision checking -`node[:dhcp][:parameters]["next-server"]` | `String` | `"#{ipaddress}"` | Set this server as the global next-server -`node[:dhcp][:parameters]["filename"]` | `String` | `'"pxelinux.0"'` | For TFTP file name **Note:** the quotes are necessary for this option in the fall-through to dhcpd config. - -### Failover parameters - -attribute | Type | Default | description -:--------------------------------------------------- | :------: | :-----: | :-------------------------------------------------------------------- -`node[:dhcp][:failover_params]["mclt"]` | `String` | "3600" | Set Maximum Client Lead Time -`node[:dhcp][:failover_params]["port"]` | `String` | "647" | TCP port to listen for connections from peer -`node[:dhcp][:failover_params]["peer_port"]` | `String` | "647" | TCP port to connect to failover peer -`node[:dhcp][:failover_params][auto_partner_down""]` | `String` | "0" | Timeout in seconds to enter partner-down state if peer is unavailable - -### Platform Default Attributes - -#### RHEL Platforms - -attribute | Type | Default -:--------------------------- | :------: | :----------------------: -`node[:dhcp][:dir]` | `String` | `"/etc/dhcpd"` -`node[:dhcp][:package_name]` | `String` | `"dhcp"` -`node[:dhcp][:service_name]` | `String` | `"dhcpd"` -`node[:dhcp][:config_file]` | `String` | `"/etc/dhcp/dhcpd.conf"` -`node[:dhcp][:init_config]` | `String` | `"/etc/sysconfig/dhcpd"` - -#### Debian Platforms - -attribute | Type | Default -:--------------------------- | :------: | :----------------------------: -`node[:dhcp][:dir]` | `String` | "/etc/dhcpd" -`node[:dhcp][:package_name]` | `String` | "isc-dhcp-server" -`node[:dhcp][:service_name]` | `String` | "isc-dhcp-server" -`node[:dhcp][:config_file]` | `String` | "/etc/dhcp/dhcpd.conf" -`node[:dhcp][:init_config]` | `String` | "/etc/default/isc-dhcp-server" - -## Data Bags - -Data bags drive the lion's share of the DHCP configuration, beyond the global settings. It is not required to configure any bags other than dhcp_networks to get up and running but, if you want to statically map a network or have handy Host names provisioned by DHCP, you will have to add either `dhcp_groups/dhcp_hosts` bags and items. - -You can generate example bags by using these handy commands - -```bash -knife data bag create dhcp_networks -knife data bag create dhcp_groups -knife data bag create dhcp_hosts -knife data bag from file dhcp_networks examples/data_bags/dhcp_networks -knife data bag from file dhcp_groups examples/data_bags/dhcp_groups -knife data bag from file dhcp_hosts examples/data_bags/dhcp_hosts -``` - -### dhcp_networks - -Looked up via `node[:dhcp][:networks_bag]`. Describes networks this dhcp server should be configured to provide services for. Per-network options can be provided as an array of strings where each string is a DHCP option. Make sure you escape `"`s properly as dhcpd is touchy about the format of values. - -```json -{ - "id": "192-168-1-0_24", - "routers": [ "192.168.1.1" ], - "address": "192.168.1.0", - "netmask": "255.255.255.0", - "broadcast": "192.168.1.255", - "range": "192.168.1.50 192.168.1.240", - "options": [ "time-offset 10" ], - "next_server": "192.168.1.11" -} -``` - -### dhcp_groups - -Looked up via `node[:dhcp][:grougroups]` Items for this bag are group entries as per the [man page][1]. Groups are sets of machines that can be configured with common parameters. A group can be bare, i.e. Contains no host or parameters entries whatsover, and just defines options, if desired. - -The only required key in a host def is the `"mac":` key. Everything else is optional - -Example Group Bag - -```json -{ - "id": "test", - "pxe": "ubuntu-precise", - "parameters": [ - "use-host-decl-names on", - "max-lease-time 300", - "default-lease-time 120", - "next-server \"someplace\"" - ], - "hosts": { - "some-vm": { - "parameters": [ ], - "mac": "11:22:33:44:55:66", - "ip": "192.168.1.111" - }, - "another-host": { - "mac": "22:33:44:55:66:77" - } - } -} -``` - -There are a few keys that merit more discussion: - -## `"hosts":` key dhcp_group Items - -Groups in isc-dhcp can define lists of hosts. In this example we are using the `use-host-decl-names on` tell dhcp to use the "some-vm" and "another-host" entries as the host-name for these clients, as well as setting other per-group parameters. - -Each Host in the hosts Hash can have a number of settings, but the only required setting is `"mac":` the MAC address for the host. - -Key | Description -:-------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------ -`"mac":` | MAC address of this host -`"ip":` | IP address of this host -`"parameters":` | an array of isc-dhcpd parameters as-per the global parameter setting. -`"pxe":` | This key is used by my PXE cookbook to figure out what PXE item you want this group to boot with. That PXE cookbook should be released soon (hopefully). - -### dhcp_hosts - -DHCP hosts bag is looked up via `node[:dhcp][:hosts_bag]`, and contains Host Items. - -Example Most Basic host: - -```json -{ - "id": "vagrant-vm", - "hostname": "vagrant.vm", - "mac": "08:00:27:f1:1f:b6", -} -``` - -Example Complex host: - -```json -{ - "id": "pxe_test-vm", - "hostname": "pxe_test.vm", - "mac": "08:00:27:8f:7b:db", - "ip": "192.168.1.31", - "parameters": [ ], - "options": [ ], - "pxe": "ubuntu-precise" -} -``` - -## Resources - -### dhcp_host - -Manipulate host entries in dhcpd - -#### Actions - -Action | Description -:------- | :------------------------------- -`add` | `_default_` Add this host record -`remove` | Delete this host record - -#### Paramaters - -Param | Type | Default -:----------- | :------- | :------------ -`hostname` | `String` -`macaddress` | `String` -`ipaddress` | `String` -`options` | `Array` | `[]` -`parameters` | `Array` | `[]` -`conf_dir` | `String` | `"/etc/dhcp"` - -#### Example - -Add a Node - -```ruby -dhcp_host "myhost" do - hostname "somebox.foobar.com" - macaddress "08:00:27:f1:1f:b6" - ipaddress "192.168.1.22" - options [ "domain-name-servers 8.8.8.8" ] -end -``` - -Remove a node - -```ruby -dhcp_host "myhost" do - action :remove - hostname "somebox.foobar.com" -end -``` - -If you undefine an entry, it will also get removed. - -### dhcp_group - -#### Actions +## Requirements -Action | Description -:------- | :------------------------------- -`add` | `_default_` Add this host record -`remove` | Delete this host record +- Chef 14+ -#### Paramaters +## Usage -Param | Type | Default | Desciption -:----------- | :------- | :---------------- | :-------------------------------------------------------------------------------------------------------------- -`name` | `String` | `:name_attribute` -`hosts` | `Hash` | `{}` | This is a hash of host entries that follow the host-databag format. See the example entry in examples directory -`parameters` | `Array` | `[]` -`evals` | `Array` | `[]` | This is an array of multiline strings of eval -`conf_dir` | `String` | `"/etc/dhcp"` | The directory where the config files are stored +It is recommended to create a project or organization specific [wrapper cookbook](https://www.chef.io/blog/2013/12/03/doing-wrapper-cookbooks-right/) and add the desired custom resources to the run list of a node. -#### Example +Example of a basic server listening on and issuing leases for the subnet `192.0.2.0/24`. ```ruby -hosts_data = { - "some-vm"=> {"parameters"=>[], "mac"=>"11:22:33:44:55:66", "ip"=>"192.168.1.111"}, - "another-host"=>{"mac"=>"22:33:44:55:66:77"}} -} +dhcp_install 'isc-dhcp-server' -dhcp_group "some_group" do - parameters [ "default-lease-time 120", "next-server \"someplace\""] - hosts hosts_data +dhcp_service 'dhcpd' do + ip_version :ipv4 + action [:create, :enable, :start] end -``` -### dhcp_subnet - -Action | Description -:------- | :------------------------------- -`add` | `_default_` Add this host record -`remove` | Delete this host record - -#### Paramaters - -Param | Type | Default | Desciption -:------------ | :------- | :---------------- | :------------------------------------------------------------------------------------------------------ -`subnet` | `String` | `:name_attribute` | The network subnet -`broadcast` | `String` | `nil` | The broadcast address for the subnet -`netmask` | `String` | `nil` | The netmask address for the subnet -`routers` | `Array` | `[]` | Gateways for the subnet -`options` | `Array` | `[]` | DHCP options to set for the subnet -`pool` | `block` | `nil` | Define a pool block for a `dhcp_subnet`. See 'Parameters for pool' below. -`next_server` | `String` | `nil` | Next server for TFTP/PXE -`evals` | `Array` | `[]` | This is an array of multiline strings of eval -`ddns` | `String` | `nil` | Domain name that will be appended to the client's hostname to form a fully-qualified domain-name (FQDN) -`key` | `Hash` | `{}` | Shared secret key for DDNS -`zones` | `Array` | `[]` | _NOTE: Please help update with a good description_ -`conf_dir` | `String` | `"/etc/dhcp"` | Main dhcpd config directory - -##### Parameters for pool - -Param | Type | Default | Desciption -:------ | :------------------ | :------ | :---------------------------------------------------------------------------------- -`peer` | `String` | `nil` | Peer server for this segment -`range` | `String` or `Array` | `[]` | Range of IPs to make available for DHCP in the subnet -`allow` | `String` or `Array` | `[]` | Only those clients that match any entries on the allow list will be eligible. -`deny` | `String` or `Array` | `[]` | Only those clients that do not match any entries on the deny list will be eligible. - -#### Example - -```ruby -dhcp_subnet "192.168.1.0" do - pool do - range ["192.168.1.100 192.168.1.200", "10.33.66.10 10.33.66.100"] - end - netmask "255.255.255.0" - broadcast "192.168.1.255" - options [ "time-offset 10" ] - next_server "192.168.1.11" - routers "192.168.1.1" - evals [ %q| - if exists user-class and option user-class = "iPXE" { - filename "bootstrap.ipxe"; - } else { - filename "undionly.kpxe"; - } - | ] +dhcp_config '/etc/dhcp/dhcpd.conf' do + allow %w(booting bootp unknown-clients) + parameters( + 'default-lease-time' => 7200, + 'max-lease-time' => 86400, + 'update-static-leases' => true, + 'one-lease-per-client' => true, + 'authoritative' => '', + 'ping-check' => true + ) + options( + 'domain-name' => '"test.domain.local"', + 'domain-name-servers' => '8.8.8.8, 8.8.4.4' + ) + action :create end -``` - -### dhcp_shared_network - -Action | Description -:------- | :---------------------------------- -`add` | `_default_` Add this shared network -`remove` | Delete this shared network record - -#### Paramaters - -Param | Type | Default | Desciption -:------- | :------------ | :------ | :--------------------------------------------------------------------------- -`subnet` | `dhcp_subnet` | `nil` | The network subnet to define inside the shared network. Can define multiples. -### Example - -```ruby -dhcp_shared_network 'mysharednet' do - subnet '192.168.1.0' do - pool do - range ['192.168.1.100 192.168.1.200', '10.33.66.10 10.33.66.100'] - end - netmask '255.255.255.0' - broadcast '192.168.1.255' - next_server '192.168.1.11' - routers '192.168.1.1' - evals [ %q| - if exists user-class and option user-class = "iPXE" { - filename "bootstrap.ipxe"; - } else { - filename "undionly.kpxe"; - } - | ] - end - subnet '10.0.2.0' do - broadcast '10.0.2.254' - netmask '255.255.255.0' - pool do - range ['10.0.2.50 10.0.2.240'] - end - end +dhcp_subnet '192.0.2.0' do + comment 'Basic Subnet Declaration' + subnet '192.0.2.0' + netmask '255.255.255.0' + options [ + 'routers 192.168.1.1', + ] + pool( + 'peer' => '192.168.0.2', + 'range' => '192.168.1.100 192.168.1.200' + ) + parameters( + 'ddns-domainname' => '"test.domain"' + ) end ``` -### dhcp_class - -Manage DHCP classes - -#### Actions - -Action | Description -:----- | :------------------------------- -`add` | `_default_` Add this host record - -#### Paramaters +## External Documentation -Param | Type | Default -:----------- | :------- | :------ -`match` | `String` | -`subclass` | `String` | -`subclasses` | `Array` | `[]` +- +- +- -#### Example +## Examples -Add a Node +Please check for more varied working examples in [TEST](./test/cookbooks/test/) -```ruby -dhcp_class 'ignorehosts' do - match 'hardware' - subclass '1:10:bf:48:42:55:01' - subclass '1:10:bf:48:42:55:02' -end -``` - -If you undefine an entry it will also get removed. - -## Testing +## Resources -Testing uses Chef Workstation +- [dhcp_class](documentation/dhcp_class.md) +- [dhcp_config](documentation/dhcp_config.md) +- [dhcp_group](documentation/dhcp_group.md) +- [dhcp_host](documentation/dhcp_host.md) +- [dhcp_package](documentation/dhcp_package.md) +- [dhcp_service](documentation/dhcp_service.md) +- [dhcp_shared_network](documentation/dhcp_shared_network.md) +- [dhcp_subnet](documentation/dhcp_subnet.md) ## Contributors diff --git a/UPGRADING.md b/UPGRADING.md new file mode 100644 index 00000000..9d47bb5f --- /dev/null +++ b/UPGRADING.md @@ -0,0 +1,65 @@ +# Upgrading + +This document will give you help on upgrading major versions of dhcp + +## 7.0.0 + +Version 7.0.0 is a major rewrite of the cookbook to current standards, remodelling as a resource library cookbook and the addition of DHCPv6 support. + +### Removed + +- All attributes +- All recipes + - Enumeration of resources from data bags with this cookbook is not longer supported, you will have to write your own wrapper cookbook if you + depend on this functionality. +- Resource `dhcp_pool` + - Pool verification is now provided by the `dhcp_subnet` resource + +### Added + +- Resource `dhcp_config` - [Documentation](./documentation/dhcp_config.md) + - Replacement for the `_config` recipe + - Global configuration + - DHCP failover configuration + - Configuration of service environment file + +- Resource `dhcp_package` - [Documentation](./documentation/dhcp_package.md) + - Replacement for the `_package` recipe + - Installs the relevant packages for platform + +- Resource `dhcp_service` - [Documentation](./documentation/dhcp_service.md) + - Replacement for the `_service` recipe + - Creates a systemd unit file for the platform + - Controls service activation + +### Changed + +- Common + - Custom resources have been rewritten in current style + - Relevant resources have `options` and `parameters` properties to allow more flexible user defined configuration + - Template files are overrideable with `cookbook` and `template` parameters to allow user template selection + - `owner`, `group` and `mode` properties to control the resultant permissions and mode of the generated files + - DHCPv6 support for all resources, use `ip_version` to select + +- Resource `dhcp_class` - [Documentation](./documentation/dhcp_class.md) + - `:delete` action added + - `subclasses` are now specified via `Hash` rather than a `Block` + +- Resource `dhcp_group` - [Documentation](./documentation/dhcp_group.md) + - `:delete` action added + +- Resource `dhcp_host` - [Documentation](./documentation/dhcp_host.md) + - `:delete` action added + - `hostname` property removed, specify it via `options` + - `macaddress` property is renamed to `identifier` + - `ipaddress` property is renamed to `address` + +- Resource `dhcp_shared_network` - [Documentation](./documentation/dhcp_shared_network.md) + - `:delete` action added + - Subnets are provided via included nested `dhcp_subnet` resources + - `subnets` are now specified via `Hash` rather than a `Block` + +- Resource `dhcp_subnet` - [Documentation](./documentation/dhcp_subnet.md) + - `:delete` action added + - `broadcast`, `routers`, `ddns` and `next-server` properties have been removed, specify them via `options` and `parameters` + - `pools` are now specified via `Hash` rather than a `Block` diff --git a/attributes/default.rb b/attributes/default.rb deleted file mode 100644 index df63006c..00000000 --- a/attributes/default.rb +++ /dev/null @@ -1,73 +0,0 @@ -default['dhcp']['failover'] = nil -default['dhcp']['failover_lease_hack'] = false -default['dhcp']['failover_params']['mclt'] = 3600 -default['dhcp']['failover_params']['port'] = 647 -default['dhcp']['failover_params']['peer_port'] = 647 -default['dhcp']['failover_params']['auto_partner_down'] = 0 - -default['dhcp']['allows'] = %w(booting bootp unknown-clients) - -# these are the arrays that dispatch to bags or attributes for actual data -default['dhcp']['hosts'] = [] -default['dhcp']['groups'] = [] -default['dhcp']['networks'] = [] -default['dhcp']['shared_networks'] = [] -default['dhcp']['interfaces'] = [] - -# turn bag support on/off -default['dhcp']['use_bags'] = true - -# extra config files -default['dhcp']['extra_files'] = [] - -# these are the lookup keys for bag names -default['dhcp']['hosts_bag'] = 'dhcp_hosts' -default['dhcp']['groups_bag'] = 'dhcp_groups' -default['dhcp']['networks_bag'] = 'dhcp_networks' - -# these are the attribute keys used for records when not using bags -default['dhcp']['host_data'] = {} -default['dhcp']['group_data'] = {} -default['dhcp']['network_data'] = {} -default['dhcp']['shared_network_data'] = {} - -default['domain'] = 'local' - -default['dhcp']['parameters']['default-lease-time'] = '6400' -default['dhcp']['parameters']['ddns-domainname'] = "\"#{node['domain']}\"" -default['dhcp']['parameters']['ddns-update-style'] = 'interim' -default['dhcp']['parameters']['max-lease-time'] = '86400' -default['dhcp']['parameters']['update-static-leases'] = 'true' -default['dhcp']['parameters']['one-lease-per-client'] = 'true' -default['dhcp']['parameters']['authoritative'] = '' -default['dhcp']['parameters']['ping-check'] = 'true' -default['dhcp']['parameters']['next-server'] = node['ipaddress'] -default['dhcp']['parameters']['filename'] = '"pxelinux.0"' - -default['dhcp']['options']['domain-name'] = "\"#{node['domain']}\"" -default['dhcp']['options']['domain-name-servers'] = '8.8.8.8' -default['dhcp']['options']['host-name'] = ' = binary-to-ascii (16, 8, "-", substring (hardware, 1, 6))' - -default['dhcp']['dir'] = '/etc/dhcp' -default['dhcp']['init_config'] = '/etc/sysconfig/dhcpd' -default['dhcp']['dhcpd_leases'] = '/var/lib/dhcpd/dhcpd.leases' -default['dhcp']['config_file'] = '/etc/dhcp/dhcpd.conf' - -case node['platform_family'] -when 'rhel', 'fedora' - default['dhcp']['package_name'] = 'dhcp' - default['dhcp']['service_name'] = 'dhcpd' - default['dhcp']['init_config'] = '/etc/sysconfig/dhcpd' - default['dhcp']['init_iface'] = 'DHCPDARGS' - default['dhcp']['dhcpd_leases'] = '/var/lib/dhcpd/dhcpd.leases' - -when 'debian' - default['dhcp']['package_name'] = 'isc-dhcp-server' - default['dhcp']['service_name'] = 'isc-dhcp-server' - - default['dhcp']['init_config'] = '/etc/default/isc-dhcp-server' - default['dhcp']['dhcpd_leases'] = '/var/lib/dhcp/dhcpd.leases' - default['dhcp']['init_iface'] = 'INTERFACES' -end - -default['dhcp']['hooks'] = {} diff --git a/attributes/dns.rb b/attributes/dns.rb deleted file mode 100644 index 629e784b..00000000 --- a/attributes/dns.rb +++ /dev/null @@ -1,3 +0,0 @@ -default['dns']['master'] = '' -default['dns']['zones'] = [] -default['dns']['rndc_key'] = '' diff --git a/documentation/dhcp_class.md b/documentation/dhcp_class.md new file mode 100644 index 00000000..7443df44 --- /dev/null +++ b/documentation/dhcp_class.md @@ -0,0 +1,60 @@ +# dhcp_class + +[Back to resource list](../README.md#resources) + +Create a DHCPD class configuration. () + +Introduced: v7.0.0 + +## Actions + +- `:create` +- `:delete` + +## Properties + +| Name | Type | Default | Description | Allowed Values | +| ---------------------- | ------------- | -------------------------------- | ------------------------------------------------------------------- | ------------------- | +| `comment` | String | `nil` | Comment to add to the configuration file | | +| `ip_version` | Symbol | `:ipv4` | Select DHCP or DHCPv6 server to configure | `:ipv4`, `:ipv6` | +| `conf_dir` | String | `/etc/dhcp/dhcpd(6).d/classes.d` | Directory to create configuration file in | | +| `cookbook` | String | `/etc/dhcp/dhcpd(6).d/classes.d` | Cookbook to source configuration file template from | | +| `template` | String | `/etc/dhcp/dhcpd(6).d/classes.d` | Template to use to generate the configuration file | | +| `owner` | String | Platform dependant | Owner of the generated configuration file | | +| `group` | String | Platform dependant | Group of the generated configuration file | | +| `mode` | String | `'0640'` | Filemode of the generated configuration file | | +| `match` | String | `nil` | DHCPD match statement | | +| `options` | Array, Hash | `nil` | DHCPD options for the class | | +| `parameters` | Array, Hash | `nil` | DHCPD parameters for the class | | +| `subclass` | Array | `nil` | Subclasses to include within the class | | + +## Examples + +```ruby +dhcp_class 'BlankClass' do + match 'hardware' +end +``` + +```ruby +dhcp_class 'RegisteredHosts' do + match 'hardware' + subclass [ + '1:10:bf:48:42:55:01', + '1:10:bf:48:42:55:02', + ] +end +``` + +```ruby +dhcp_class 'SpecialHosts' do + match 'hardware' + subclass [ + '1:10:bf:48:42:56:01', + '1:10:bf:48:42:56:02', + ] + option( + 'special-option' => 'value' + ) +end +``` diff --git a/documentation/dhcp_config.md b/documentation/dhcp_config.md new file mode 100644 index 00000000..49152128 --- /dev/null +++ b/documentation/dhcp_config.md @@ -0,0 +1,94 @@ +# dhcp_config + +[Back to resource list](../README.md#resources) + +Create and manage the main DHCPD configuration. () + +Introduced: v7.0.0 + +## Actions + +- `:create` +- `:delete` + +## Properties + +| Name | Type | Default | Description | Allowed Values | +| ---------------------- | ------------- | -------------------------------- | ------------------------------------------------------------------- | ------------------- | +| `comment` | String | `nil` | Comment to add to the configuration file | | +| `ip_version` | Symbol | `:ipv4` | Select DHCP or DHCPv6 server to configure | `:ipv4`, `:ipv6` | +| `conf_dir` | String | `/etc/dhcp/dhcpd(6).d/classes.d` | Directory to create configuration file in | | +| `cookbook` | String | `/etc/dhcp/dhcpd(6).d/classes.d` | Cookbook to source configuration file template from | | +| `template` | String | `/etc/dhcp/dhcpd(6).d/classes.d` | Template to use to generate the configuration file | | +| `owner` | String | Platform dependant | Owner of the generated configuration file | | +| `group` | String | Platform dependant | Group of the generated configuration file | | +| `mode` | String | `'0640'` | Filemode of the generated configuration file | | +| `config_failover_file` | String | `/etc/dhcp/dhcpd.failover.conf` | DHCP failover configuration file path | | +| `config_includes_directory` | String | `/etc/dhcp/dhcpd(6).d` | Directory to create included configuration files in | | +| `lib_dir` | String | Platform dependant | DHCPD lib directory path | | +| `lease_file` | String | Platform dependant | DHCPD lease file path | | +| `allow` | Array | `nil` | | | +| `deny` | Array | `nil` | | | +| `ignore` | Array | `nil` | | | +| `parameters` | Array, Hash | `nil` | DHCPD global parameters | | +| `options` | Array, Hash | `nil` | DHCPD global options | | +| `evals` | Array | `nil` | DHCPD conditional statements (see dhcp-eval(5)) | | +| `keys` | Hash | `nil` | TSIG keys configuration | | +| `zones` | Hash | `nil` | Dynamic DNS zone configuration | | +| `hooks` | Hash | `nil` | Server event action configuration | | +| `failover` | Hash | `nil` | DHCP failover configuration | | +| `include_files` | Array | `nil` | Additional configuration files to include | | +| `extra_lines` | String, Array | `nil` | Extra lines to append to the configuration file | | + +## Examples + +```ruby +dhcp_config '/etc/dhcp/dhcpd.conf' do + allow %w(booting bootp unknown-clients) + parameters( + 'default-lease-time' => 7200, + 'ddns-update-style' => 'interim', + 'max-lease-time' => 86400, + 'update-static-leases' => true, + 'one-lease-per-client' => true, + 'authoritative' => '', + 'ping-check' => true + ) + options( + 'domain-name' => '"test.domain.local"', + 'domain-name-servers' => '8.8.8.8', + 'host-name' => ' = binary-to-ascii (16, 8, "-", substring (hardware, 1, 6))' + ) + hooks( + 'commit' => ['use-host-decl-names on'], + 'release' => ['use-host-decl-names on'] + ) + include_files [ + '/etc/dhcp/extra1.conf', + '/etc/dhcp/extra2.conf', + '/etc/dhcp_override/list.conf', + ] + action :create +end +``` + +```ruby +dhcp_config '/etc/dhcp/dhcpd6.conf' do + ip_version :ipv6 + deny %w(duplicates) + parameters( + 'default-lease-time' => 7200, + 'ddns-updates' => 'on', + 'ddns-update-style' => 'interim', + 'max-lease-time' => 86400, + 'update-static-leases' => true, + 'one-lease-per-client' => 'on', + 'authoritative' => '', + 'ping-check' => true + ) + options( + 'dhcp6.name-servers' => '2001:4860:4860::8888, 2001:4860:4860::8844' + ) + action :create +end +``` diff --git a/documentation/dhcp_group.md b/documentation/dhcp_group.md new file mode 100644 index 00000000..79e3d28b --- /dev/null +++ b/documentation/dhcp_group.md @@ -0,0 +1,39 @@ +# dhcp_group + +[Back to resource list](../README.md#resources) + +Create a DHCPD group configuration. ( - *The **group** statement*) + +Introduced: v7.0.0 + +## Actions + +- `:create` +- `:delete` + +## Properties + +| Name | Type | Default | Description | Allowed Values | +| ---------------------- | ------------- | -------------------------------- | ------------------------------------------------------------------- | ------------------- | +| `comment` | String | `nil` | Comment to add to the configuration file | | +| `ip_version` | Symbol | `:ipv4` | Select DHCP or DHCPv6 server to configure | `:ipv4`, `:ipv6` | +| `conf_dir` | String | `/etc/dhcp/dhcpd(6).d/groups.d` | Directory to create configuration file in | | +| `cookbook` | String | `/etc/dhcp/dhcpd(6).d/groups.d` | Cookbook to source configuration file template from | | +| `template` | String | `/etc/dhcp/dhcpd(6).d/groups.d` | Template to use to generate the configuration file | | +| `owner` | String | Platform dependant | Owner of the generated configuration file | | +| `group` | String | Platform dependant | Group of the generated configuration file | | +| `mode` | String | `'0640'` | Filemode of the generated configuration file | | +| `parameters` | Array, Hash | `nil` | DHCPD parameters for the group | | +| `options` | Array, Hash | `nil` | DHCPD options for the group | | +| `evals` | Array | `nil` | DHCPD conditional statements for the group (see dhcp-eval(5)) | | +| `hosts` | Hash | `nil` | Hosts configuration to include within the group | | + +## Examples + +```ruby +dhcp_group 'IPPgones' do + options( + 'tftp-server-name' => '192.0.2.10' + ) +end +``` diff --git a/documentation/dhcp_host.md b/documentation/dhcp_host.md new file mode 100644 index 00000000..55cecabb --- /dev/null +++ b/documentation/dhcp_host.md @@ -0,0 +1,52 @@ +# dhcp_host + +[Back to resource list](../README.md#resources) + +Create a DHCPD host configuration. ( - *The **host** statement*) + +Introduced: v7.0.0 + +## Actions + +- `:create` +- `:delete` + +## Properties + +| Name | Type | Default | Description | Allowed Values | +| ---------------------- | ------------- | -------------------------------- | ------------------------------------------------------------------- | ------------------- | +| `comment` | String | `nil` | Comment to add to the configuration file | | +| `ip_version` | Symbol | `:ipv4` | Select DHCP or DHCPv6 server to configure | `:ipv4`, `:ipv6` | +| `conf_dir` | String | `/etc/dhcp/dhcpd(6).d/hosts.d` | Directory to create configuration file in | | +| `cookbook` | String | `/etc/dhcp/dhcpd(6).d/hosts.d` | Cookbook to source configuration file template from | | +| `template` | String | `/etc/dhcp/dhcpd(6).d/hosts.d` | Template to use to generate the configuration file | | +| `owner` | String | Platform dependant | Owner of the generated configuration file | | +| `group` | String | Platform dependant | Group of the generated configuration file | | +| `mode` | String | `'0640'` | Filemode of the generated configuration file | | +| `identifier` | String | `nil` | DHCPD host identifier (MAC or DHCID) | | +| `address` | String | `nil` | DHCPD address to issue | | +| `parameters` | Array, Hash | `nil` | DHCPD parameters for the host | | +| `options` | Array, Hash | `nil` | DHCPD options for the host | | + +## Examples + +```ruby +dhcp_host 'IPv4-Host' do + identifier 'hardware ethernet 00:53:00:00:00:01' + address '192.168.0.10' + options( + 'host-name' => 'test-ipv4-host' + ) +end +``` + +```ruby +dhcp_host 'IPv6-Host' do + ip_version :ipv6 + identifier 'host-identifier option dhcp6.client-id 00:53:00:00:00:01:a4:65:b7:c8' + address '2001:db8:1:1:0:0:1:10' + options( + 'host-name' => 'test-ipv6-host' + ) +end +``` diff --git a/documentation/dhcp_package.md b/documentation/dhcp_package.md new file mode 100644 index 00000000..d3524555 --- /dev/null +++ b/documentation/dhcp_package.md @@ -0,0 +1,30 @@ +# dhcp_package + +[Back to resource list](../README.md#resources) + +Install ISC DHCPD server from package. + +Introduced: v7.0.0 + +## Actions + +- `:install` +- `:remove` + +## Properties + +| Name | Type | Default | Description | Allowed Values | +| ---------------------- | ------------- | ----------------------------- | ---------------------------------------------------------------------- | ------------------- | +| `packages` | String, Array | Correct packages for platform | List of packages to install for server operation | | + +## Examples + +```ruby +dhcp_package +``` + +```ruby +dhcp_package 'dhcpd' do + packages 'isc-dhcp-server' + end +``` diff --git a/documentation/dhcp_service.md b/documentation/dhcp_service.md new file mode 100644 index 00000000..829c77e4 --- /dev/null +++ b/documentation/dhcp_service.md @@ -0,0 +1,41 @@ +# dhcp_service + +[Back to resource list](../README.md#resources) + +Manage the DHCPD and DHCPD6 services. + +Introduced: v7.0.0 + +## Actions + +- `:create` +- `:delete` +- `:start` +- `:stop` +- `:reload` +- `:enable` +- `:disable` + +## Properties + +| Name | Type | Default | Description | Allowed Values | +| ---------------------- | ------------- | ----------------------------- | ---------------------------------------------------------------------- | ------------------- | +| `ip_version` | Symbol | `:ipv4` | Select DHCP or DHCPv6 server to configure | `:ipv4`, `:ipv6` | +| `service_name` | String | `nil` | Custom service name | | +| `systemd_unit_content` | String, Hash | Platform dependant | systemd unit file content for service | | + +## Examples + +```ruby +dhcp_service 'dhcpd' do + ip_version :ipv4 + action [:create, :enable, :start] +end +``` + +```ruby +dhcp_service 'dhcpd6' do + ip_version :ipv6 + action [:create, :enable, :start] +end +``` diff --git a/documentation/dhcp_shared_network.md b/documentation/dhcp_shared_network.md new file mode 100644 index 00000000..0591f11f --- /dev/null +++ b/documentation/dhcp_shared_network.md @@ -0,0 +1,78 @@ +# dhcp_shared_network + +[Back to resource list](../README.md#resources) + +Create a DHCPD shared network configuration. ( - *The **shared-network** statement*) + +Introduced: v7.0.0 + +## Actions + +- `:create` +- `:delete` + +## Properties + +| Name | Type | Default | Description | Allowed Values | +| ---------------------- | ------------- | -------------------------------- | ------------------------------------------------------------------- | ------------------- | +| `comment` | String | `nil` | Comment to add to the configuration file | | +| `ip_version` | Symbol | `:ipv4` | Select DHCP or DHCPv6 server to configure | `:ipv4`, `:ipv6` | +| `conf_dir` | String | `/etc/dhcp/dhcpd(6).d/hosts.d` | Directory to create configuration file in | | +| `cookbook` | String | `/etc/dhcp/dhcpd(6).d/hosts.d` | Cookbook to source configuration file template from | | +| `template` | String | `/etc/dhcp/dhcpd(6).d/hosts.d` | Template to use to generate the configuration file | | +| `owner` | String | Platform dependant | Owner of the generated configuration file | | +| `group` | String | Platform dependant | Group of the generated configuration file | | +| `mode` | String | `'0640'` | Filemode of the generated configuration file | | +| `subnets` | Hash | `nil` | Subnets for the DHCPD shared network | | + +## Examples + +```ruby +dhcp_shared_network 'single' do + subnets( + '192.168.1.0' => { + 'subnet' => '192.168.1.0', + 'netmask' => '255.255.255.0', + 'options' => { + 'broadcast-address' => '192.168.1.255', + 'routers' => '192.168.1.1', + }, + 'pool' => { + 'range' => '192.168.1.20 192.168.1.30', + }, + } + ) +end +``` + +```ruby +dhcp_shared_network 'multiple' do + subnets( + '192.168.2.0' => { + 'subnet' => '192.168.2.0', + 'netmask' => '255.255.255.0', + 'options' => { + 'broadcast-address' => '192.168.2.255', + 'routers' => '192.168.2.1', + }, + 'pool' => { + 'range' => '192.168.2.20 192.168.2.30', + }, + }, + '192.168.3.0' => { + 'subnet' => '192.168.3.0', + 'netmask' => '255.255.255.0', + 'options' => { + 'broadcast-address' => '192.168.3.255', + 'routers' => '192.168.3.1', + }, + 'pool' => { + 'range' => [ + '192.168.3.20 192.168.3.30', + '192.168.3.40 192.168.3.50', + ], + }, + } + ) +end +``` diff --git a/documentation/dhcp_subnet.md b/documentation/dhcp_subnet.md new file mode 100644 index 00000000..bd5ca0e5 --- /dev/null +++ b/documentation/dhcp_subnet.md @@ -0,0 +1,88 @@ +# dhcp_subnet + +[Back to resource list](../README.md#resources) + +Create and manage the main DHCPD configuration. () + +Introduced: v7.0.0 + +## Actions + +- `:create` +- `:delete` + +## Properties + +| Name | Type | Default | Description | Allowed Values | +| ---------------------- | ------------- | -------------------------------- | ------------------------------------------------------------------- | ------------------- | +| `comment` | String | `nil` | Comment to add to the configuration file | | +| `ip_version` | Symbol | `:ipv4` | Select DHCP or DHCPv6 server to configure | `:ipv4`, `:ipv6` | +| `conf_dir` | String | `/etc/dhcp/dhcpd(6).d/classes.d` | Directory to create configuration file in | | +| `cookbook` | String | `/etc/dhcp/dhcpd(6).d/classes.d` | Cookbook to source configuration file template from | | +| `template` | String | `/etc/dhcp/dhcpd(6).d/classes.d` | Template to use to generate the configuration file | | +| `owner` | String | Platform dependant | Owner of the generated configuration file | | +| `group` | String | Platform dependant | Group of the generated configuration file | | +| `mode` | String | `'0640'` | Filemode of the generated configuration file | | +| `shared_network` | True, False | `false` | DHCP failover configuration file path | | +| `subnet` | String | `nil` | Subnet address | | +| `netmask` | String | `nil` | Subnet network for IPv4 subnets | | +| `prefix` | String | `nil` | Subnet prefix for IPv6 subnets | | +| `subnet` | String | `nil` | Subnet address | | +| `subnet` | String | `nil` | Subnet address | | +| `parameters` | Array, Hash | `nil` | DHCPD parameters for the subnet | | +| `options` | Array, Hash | `nil` | DHCPD options for the subnet | | +| `evals` | Array | `nil` | DHCPD conditional statements for the subnet (see dhcp-eval(5)) | | +| `keys` | Hash | `nil` | TSIG keys configuration | | +| `zones` | Hash | `nil` | Dynamic DNS zone configuration | | +| `allow` | Array | `nil` | | | +| `deny` | Array | `nil` | | | +| `extra_lines` | String, Array | `nil` | Extra lines to append to the configuration file | | +| `pools` | Hash, Array | `nil` | Pool configuration hash(es), accepts most properties (see dhcpd.conf(5))| | + +## Examples + +```ruby +dhcp_subnet '192.168.9.0' do + comment 'Listen Subnet Declaration' + subnet '192.168.9.0' + netmask '255.255.255.0' +end + +dhcp_subnet 'basic' do + comment 'Basic Subnet Declaration' + subnet '192.168.0.0' + netmask '255.255.255.0' + options [ + 'routers 192.168.0.1' + 'time-offset 10', + ] + pool 'range' => '192.168.0.100 192.168.0.200' +end +``` + +```ruby +dhcp_subnet 'dhcpv6_listen' do + ip_version :ipv6 + comment 'Testing DHCPv6 Basic Subnet' + subnet '2001:db8:1::' + prefix 64 +end + +dhcp_subnet 'dhcpv6_basic' do + ip_version :ipv6 + comment 'Testing DHCPv6 Basic Subnet' + subnet '2001:db8:2:1::' + prefix 64 + options( + 'domain-name' => '"test.domain.local"', + 'dhcp6.name-servers' => '2001:4860:4860::8888, 2001:4860:4860::8844' + ) + parameters( + 'ddns-domainname' => '"test.domain.local"', + 'default-lease-time' => 28800 + ) + range [ + '2001:db8:2:1::1:0/112', + ] +end +``` diff --git a/kitchen.dokken.yml b/kitchen.dokken.yml index db9f7ce1..5c3a22f5 100644 --- a/kitchen.dokken.yml +++ b/kitchen.dokken.yml @@ -1,10 +1,8 @@ --- driver: name: dokken - # because Docker and SystemD/Upstart privileged: true - chef_version: <%= ENV['CHEF_VERSION'] || 'current' %> - env: [CHEF_LICENSE=accept] + chef_version: <%= ENV['CHEF_VERSION'] || 'latest' %> transport: name: dokken @@ -13,28 +11,53 @@ provisioner: name: dokken platforms: + - name: centos-6 + driver: + image: dokken/centos-6 + pid_one_command: /sbin/init + - name: centos-7 + driver: + image: dokken/centos-7 + pid_one_command: /usr/lib/systemd/systemd + - name: centos-8 + driver: + image: dokken/centos-8 + pid_one_command: /usr/lib/systemd/systemd + - name: fedora-31 + driver: + image: dokken/fedora-31 + pid_one_command: /usr/lib/systemd/systemd + - name: amazonlinux-2 + driver: + image: dokken/amazonlinux-2 + pid_one_command: /usr/lib/systemd/systemd - name: debian-8 driver: image: dokken/debian-8 pid_one_command: /bin/systemd intermediate_instructions: - RUN /usr/bin/apt-get update - - - name: centos-7 + - name: debian-10 driver: - image: dokken/centos-7 - pid_one_command: /usr/lib/systemd/systemd - + image: dokken/debian-10 + pid_one_command: /bin/systemd + intermediate_instructions: + - RUN /usr/bin/apt-get update - name: ubuntu-16.04 driver: image: dokken/ubuntu-16.04 pid_one_command: /bin/systemd intermediate_instructions: - RUN /usr/bin/apt-get update - - - name: fedora-30 + - name: ubuntu-18.04 driver: - image: dokken/fedora-30 - pid_one_command: /usr/lib/systemd/systemd + image: dokken/ubuntu-18.04 + pid_one_command: /bin/systemd + intermediate_instructions: + - RUN /usr/bin/apt-get update + - name: ubuntu-19.10 + driver: + image: dokken/ubuntu-19.10 + pid_one_command: /bin/systemd intermediate_instructions: - - RUN /usr/bin/yum install libselinux-utils -y + - RUN /usr/bin/apt-get update diff --git a/kitchen.yml b/kitchen.yml index c46bc5e0..8f42f2d9 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -1,24 +1,36 @@ --- driver: name: vagrant - chef_version: <%= ENV['CHEF_VERSION'] || 'current' %> - env: [CHEF_LICENSE=accept] provisioner: name: chef_zero deprecations_as_errors: true chef_license: accept + product_name: chef + product_version: <%= ENV['CHEF_VERSION'] || 'latest' %> + install_strategy: always + chef_log_level: <%= ENV['CHEF_LOG_LEVEL'] || 'auto' %> verifier: name: inspec platforms: + - name: centos-6 - name: centos-7 + - name: centos-8 + - name: fedora-31 + - name: amazonlinux-2 - name: debian-8 + - name: debian-10 - name: ubuntu-16.04 - - name: fedora-30 + - name: ubuntu-18.04 + - name: ubuntu-19.10 suites: - name: default run_list: - recipe[test::default] + - name: delete + run_list: + - recipe[test::default] + - recipe[test::config_delete] diff --git a/libraries/dynadns.rb b/libraries/dynadns.rb deleted file mode 100644 index 447e9a95..00000000 --- a/libraries/dynadns.rb +++ /dev/null @@ -1,108 +0,0 @@ -module DHCP - # methods for managing rndc key data and dynadns bind masters - module DynaDns - class << self - include Chef::DSL::DataQuery - - attr_accessor :node - attr_accessor :zones - - def load(node) - @node = node - load_zones - end - - # - # @return [Hash] of zone_name => master_addr - # - def masters - @zones ||= load_zones - masters ||= {} - return unless @zones - @zones.each do |zone| - name = zone['zone_name'] - masters[name] ||= {} - - # set to global master by default - if node['dns'].key?(:master) && !node['dns']['master'].empty? - masters[name]['master'] = node['dns']['master'] - end - - if node['dns'].key?(:rndc_key) && !node['dns']['rndc_key'].empty? - masters[name]['key'] = node['dns']['rndc_key'] - end - - # use zone bag override if it exists - if zone.key?('master_address') && !zone['master_address'].empty? - masters[name]['master'] = zone['master_address'] - end - - if zone.key?('rndc_key') && !zone['rndc_key'].empty? - masters[name]['key'] = zone['rndc_key'] - end - - # validate - unless masters[name].key?('key') && masters[name].key?('master') - masters.delete(name) - end - end - - masters - end - - # - # Fetch all keys this node requests - # - # @return [Hash] of key-names containing bag data for each key - # - def keys - k ||= {} - @zones ||= load_zones - return if @zones.nil? || @zones.empty? - - # global default keys if they exist - # TODO: need to work out the namespace on dns stuff here. - # TODO: be good to support knife-vault/encrypted bags for keys - if node.key?(:dns) && node['dns'].key?(:rndc_key) - default_key = node['dns']['rndc_key'] - k[default_key] = get_key(default_key) - end - - @zones.each { |zone| k[zone['rndc_key']] = get_key zone['rndc_key'] if zone.key? 'rndc_key' } - k - end - - # - # Get a key from bag or attributes - # - def get_key(name) - return data_bag_item('rndc_keys', name).to_hash if node['dhcp']['use_bags'] - node['dhcp']['rndc_keys'].fetch name, '' - end - - # - # Should we load zones? - # We only want to load zones if configured to use databags and node attribute node['dns']['zones'] is populated - # - def load_zones? - return false unless node['dhcp']['use_bags'] - has_zones_attr = node['dns'] && node['dns']['zones'] - return true if has_zones_attr && !node['dns']['zones'].empty? - end - - # - # Load all zone bags this node calls out - # - def load_zones - return nil unless load_zones? - - @zones = [] - node['dns']['zones'].each do |zone| - bag_name = node['dns']['bag_name'] || 'dns_zones' - zones << data_bag_item(bag_name, Dhcp::Helpers.escape(zone)).to_hash - end - @zones - end - end - end -end diff --git a/libraries/failover.rb b/libraries/failover.rb deleted file mode 100644 index 060ce018..00000000 --- a/libraries/failover.rb +++ /dev/null @@ -1,62 +0,0 @@ -module DHCP - # methods for detecthing and reporting on failover role/peers - module Failover - class << self - include Chef::DSL::DataQuery - - attr_reader :node - - def load(node) - @node = node - end - - def enabled? - case role - when 'primary' - return slaves.empty? ? false : true - when 'secondary' - return masters.empty? ? false : true - end - false - end - - def role - if node['dhcp'].key?(:slave) && node['dhcp']['slave'] == true - 'secondary' - elsif node['dhcp'].key?(:master) && node['dhcp']['master'] == true - 'primary' - end - end - - def peer_node - if node['dhcp'].key?(:slave) && node['dhcp']['slave'] - masters.first - elsif node['dhcp'].key?(:master) && node['dhcp']['master'] - slaves.first - end - end - - def peer - Chef::Log.info "Dhcp Peer: #{peer_node}" - return nil if peer_node.nil? || peer_node.empty? - peer_node['ipaddress'] - end - - def slaves - if node['dhcp'].key?(:slaves) && !node['dhcp']['slaves'].empty? - node['dhcp']['slaves'] - else - search(:node, "domain:#{node['domain']} AND dhcp_slave:true") - end - end - - def masters - if node['dhcp'].key?(:masters) && !node['dhcp']['masters'].empty? - node['dhcp']['masters'] - else - search(:node, "domain:#{node['domain']} AND dhcp_master:true") - end - end - end - end -end diff --git a/libraries/helpers.rb b/libraries/helpers.rb index b610902d..263976bd 100644 --- a/libraries/helpers.rb +++ b/libraries/helpers.rb @@ -1,66 +1,268 @@ module Dhcp - # - # Helper methods that are used in multiple providers - # - module Helpers - # - # Determine if the resource matches this resource's type - # - # @param resource the resource to check - # - # @return [TrueClass, FalseClass] wether or not the resource matches - # - def resource_match?(resource) - # Check for >= Chef 12 - return true if Gem::Version.new(Chef::VERSION) >= Gem::Version.new('12.0.0') && resource.declared_type == new_resource.declared_type - - false - end + module Cookbook + module Helpers + def dhcpd_user + 'root' + end + + def dhcpd_group + return 'root' if node['platform'].eql?('debian') - # - # List of files to include in list.conf for a collection of subconfigs (e.g. hosts, subnets, groups) - # - # @param [String] sub_dir - The subconfig directory for the config - # - # @return [Array] An array of config files for the subconfig - def includes(sub_dir) - with_run_context :root do - run_context.resource_collection.map do |resource| - next unless resource_match? resource - next unless resource.action == :add || resource.action.include?(:add) - "#{resource.conf_dir}/#{sub_dir}/#{resource.name}.conf" - end.compact + 'dhcpd' end - end - # - # Defined resource for list.conf to include all subconfig files - # - # @param [String] sub_dir - The subconfig directory for the config - # - def write_include(sub_dir, name) - template "#{new_resource.conf_dir}/#{sub_dir}/list.conf #{name}" do - path "#{new_resource.conf_dir}/#{sub_dir}/list.conf" - cookbook 'dhcp' - source 'list.conf.erb' - owner 'root' - group 'root' - mode '0644' - variables(files: includes(sub_dir)) - notifies :restart, "service[#{node['dhcp']['service_name']}]", :delayed + def dhcpd_packages + case node['platform_family'] + when 'rhel', 'amazon' + return %w(dhcp) if node['platform_version'].to_i < 8 + + %w(dhcp-server) + when 'fedora' + %w(dhcp-server) + when 'debian' + %w(isc-dhcp-server isc-dhcp-server-ldap) + end + end + + def dhcpd_service_name(ip_version) + case node['platform_family'] + when 'amazon', 'rhel', 'fedora' + { + ipv4: 'dhcpd', + ipv6: 'dhcpd6', + } + when 'debian' + { + ipv4: 'isc-dhcp-server', + ipv6: 'isc-dhcp-server6', + } + end.fetch(ip_version) + end + + def dhcpd_config_dir + '/etc/dhcp' + end + + def dhcpd_config_file(ip_version) + case ip_version + when :ipv4 + "#{dhcpd_config_dir}/dhcpd.conf" + when :ipv6 + "#{dhcpd_config_dir}/dhcpd6.conf" + else + raise "IP version #{ip_version} is unknown." + end + end + + def dhcpd_failover_config_file(ip_version) + return '/etc/dhcp/dhcpd.failover.conf' if ip_version.eql?(:ipv4) + + nil + end + + def dhcpd_env_file + case node['platform_family'] + when 'rhel', 'amazon', 'fedora' + '/etc/sysconfig/dhcpd' + when 'debian' + '/etc/default/isc-dhcp-server' + else + raise "dhcpd_env_file: Unsupported platform family #{node['platform_family']}." + end end - end - module_function + def dhcpd_config_includes_directory(ip_version) + case ip_version + when :ipv4 + "#{dhcpd_config_dir}/dhcpd.d" + when :ipv6 + "#{dhcpd_config_dir}/dhcpd6.d" + else + raise "IP version #{ip_version} is unknown." + end + end + + def dhcpd_config_resource_directory(ip_version, resource_type) + case resource_type + when :dhcp_class + "#{dhcpd_config_includes_directory(ip_version)}/classes.d" + when :dhcp_group + "#{dhcpd_config_includes_directory(ip_version)}/groups.d" + when :dhcp_host + "#{dhcpd_config_includes_directory(ip_version)}/hosts.d" + when :dhcp_shared_network + "#{dhcpd_config_includes_directory(ip_version)}/shared_networks.d" + when :dhcp_subnet + "#{dhcpd_config_includes_directory(ip_version)}/subnets.d" + else + raise "Invalid resource type #{resource_type}." + end + end + + def dhcpd_config_includes_directories + %w(groups.d hosts.d subnets.d shared_networks.d classes.d) + end + + def dhcpd_lib_dir + if platform_family?('debian') + '/var/lib/dhcp' + else + '/var/lib/dhcpd' + end + end + + def dhcpd_lib_dir_options + case node['platform'] + when 'amazon', 'centos', 'fedora', 'rhel' + { + 'owner' => 'dhcpd', + 'group' => 'dhcpd', + 'mode' => '0755', + } + when 'ubuntu' + { + 'owner' => 'root', + 'group' => 'dhcpd', + 'mode' => '0775', + } + when 'debian' + { + 'owner' => 'root', + 'group' => 'root', + 'mode' => '0755', + } + end + end - # - # Escape some special characters - # - # escape . -> - and / -> _ - # so 1.1/3 -> 1-1_3 - # - def escape(name) - name.tr('.', '-').tr('/', '_') + def dhcpd_lease_file(ip_version) + case ip_version + when :ipv4 + "#{dhcpd_lib_dir}/dhcpd.leases" + when :ipv6 + "#{dhcpd_lib_dir}/dhcpd6.leases" + else + raise "#{ip_version} is unknown." + end + end + + def dhcpd_lease_file_options + case node['platform_family'] + when 'amazon', 'centos', 'fedora', 'rhel' + { + 'owner' => 'dhcpd', + 'group' => 'dhcpd', + 'mode' => '0644', + 'action' => :create_if_missing, + } + when 'ubuntu' + { + 'owner' => 'root', + 'group' => 'dhcpd', + 'mode' => '0664', + 'action' => :create_if_missing, + } + when 'debian' + { + 'owner' => 'root', + 'group' => 'root', + 'mode' => '0644', + 'action' => :create_if_missing, + } + end + end + + def dhcpd_use_systemd? + if (platform_family?('rhel') && node['platform_version'].to_i < 7) || + (platform?('ubuntu') && node['platform_version'].to_i < 14) || + (platform_family?('amazon') && node['platform_version'].to_i < 2) + false + else + true + end + end + + def dhcpd_systemd_unit_content(ip_version) + raise 'Invalid ip_version' unless ip_version.is_a?(Symbol) && %i(ipv4 ipv6).include?(ip_version) + dhcp6 = ip_version.eql?(:ipv6) + + case node['platform'] + when 'amazon', 'centos', 'fedora', 'rhel' + { + 'Unit' => { + 'Description' => "DHCP#{dhcp6 ? 'v6' : 'v4'} Server Daemon", + 'Documentation' => 'man:dhcpd(8) man:dhcpd.conf(5)', + 'Wants' => 'network-online.target', + 'After' => [ + 'network-online.target', + 'time-sync.target', + ], + 'ConditionPathExists' => [ + '/etc/sysconfig/dhcpd', + "|/etc/dhcp/#{dhcp6 ? 'dhcpd6' : 'dhcpd'}.conf", + ], + }, + 'Service' => { + 'Type' => 'notify', + 'EnvironmentFile' => '-/etc/sysconfig/dhcpd', + 'ExecStart' => "/usr/sbin/dhcpd -f #{dhcp6 ? '-6' : '-4'} -cf /etc/dhcp/#{dhcp6 ? 'dhcpd6' : 'dhcpd'}.conf -user dhcpd -group dhcpd --no-pid $DHCPDARGS", + 'StandardError' => 'null', + }, + 'Install' => { + 'WantedBy' => 'multi-user.target', + }, + } + when 'debian' + { + 'Unit' => { + 'Description' => "ISC DHCP IP#{dhcp6 ? 'v6' : 'v4'} Server", + 'Documentation' => 'man:dhcpd(8) man:dhcpd.conf(5)', + 'Wants' => 'network-online.target', + 'After' => [ + 'network-online.target', + 'time-sync.target', + ], + 'ConditionPathExists' => [ + '/etc/default/isc-dhcp-server', + "|/etc/dhcp/#{dhcp6 ? 'dhcpd6' : 'dhcpd'}.conf", + ], + }, + 'Service' => { + 'EnvironmentFile' => '/etc/default/isc-dhcp-server', + 'RuntimeDirectory' => 'dhcp-server', + 'ExecStart' => "/usr/sbin/dhcpd -f #{dhcp6 ? '-6' : '-4'} -cf /etc/dhcp/#{dhcp6 ? 'dhcpd6' : 'dhcpd'}.conf -pf /run/dhcp-server/#{dhcp6 ? 'dhcpd6' : 'dhcpd'}.pid $INTERFACES", + }, + 'Install' => { + 'WantedBy' => 'multi-user.target', + }, + } + when 'ubuntu' + { + 'Unit' => { + 'Description' => "ISC DHCP IP#{dhcp6 ? 'v6' : 'v4'} Server", + 'Documentation' => 'man:dhcpd(8) man:dhcpd.conf(5)', + 'Wants' => 'network-online.target', + 'After' => [ + 'network-online.target', + 'time-sync.target', + ], + 'ConditionPathExists' => [ + '/etc/default/isc-dhcp-server', + "|/etc/dhcp/#{dhcp6 ? 'dhcpd6' : 'dhcpd'}.conf", + ], + }, + 'Service' => { + 'EnvironmentFile' => '/etc/default/isc-dhcp-server', + 'RuntimeDirectory' => 'dhcp-server', + 'ExecStart' => "/usr/sbin/dhcpd -f #{dhcp6 ? '-6' : '-4'} -cf /etc/dhcp/#{dhcp6 ? 'dhcpd6' : 'dhcpd'}.conf -user dhcpd -group dhcpd -pf /run/dhcp-server/#{dhcp6 ? 'dhcpd6' : 'dhcpd'}.pid $INTERFACES", + }, + 'Install' => { + 'WantedBy' => 'multi-user.target', + }, + } + else + raise "Platform #{node['platform']} is not a supported systemd OS." + end + end end end end diff --git a/libraries/resource.rb b/libraries/resource.rb new file mode 100644 index 00000000..7cdd0cd2 --- /dev/null +++ b/libraries/resource.rb @@ -0,0 +1,52 @@ +module Dhcp + module Cookbook + module ResourceHelpers + def create_list_resource(directory) + with_run_context(:root) do + edit_resource(:directory, directory) + edit_resource(:template, "#{directory}/list.conf") do + cookbook 'dhcp' + source 'list.conf.erb' + + owner new_resource.owner + group new_resource.group + mode new_resource.mode + + variables['files'] ||= [] + + action :nothing + delayed_action :create + end + end + end + + def add_to_list_resource(directory, config_file) + manage_list_resource(directory, config_file, :add) + end + + def remove_from_list_resource(directory, config_file) + manage_list_resource(directory, config_file, :remove) + end + + private + + def manage_list_resource(directory, config_file, action) + begin + + list = find_resource!(:template, "#{directory}/list.conf") + rescue Chef::Exceptions::ResourceNotFound + list = create_list_resource(directory) + end + + files = list.variables['files'] + + case action + when :add + files.push(config_file) + when :remove + files.delete(config_file) if files.include?(config_file) + end + end + end + end +end diff --git a/libraries/template.rb b/libraries/template.rb new file mode 100644 index 00000000..615dd513 --- /dev/null +++ b/libraries/template.rb @@ -0,0 +1,25 @@ +module Dhcp + module Cookbook + module TemplateHelpers + def nil_or_empty?(property) + return true if property.nil? || (property.respond_to?(:empty?) && property.empty?) + + false + end + + def property_array(property) + return property if property.is_a?(Array) + + [property] + end + + def property_collection(property) + property.map { |p| p.is_a?(Array) ? p : p.split(' ', 2) } + end + + def property_collection_sorted(property) + property.sort.map { |p| p.is_a?(Array) ? p : p.split(' ', 2) } + end + end + end +end diff --git a/metadata.rb b/metadata.rb index 5ea24236..3d6b9a39 100644 --- a/metadata.rb +++ b/metadata.rb @@ -3,10 +3,10 @@ maintainer_email 'help@sous-chefs.org' license 'Apache-2.0' description 'Installs and configures DHCP' -chef_version '>= 13.0' +chef_version '>= 14.0' source_url 'https://github.com/sous-chefs/dhcp' issues_url 'https://github.com/sous-chefs/dhcp/issues' -version '6.1.0' +version '7.0.0' supports 'debian' supports 'ubuntu' @@ -15,3 +15,4 @@ supports 'fedora' supports 'scientific' supports 'oracle' +supports 'amazon' diff --git a/recipes/_config.rb b/recipes/_config.rb deleted file mode 100644 index f65dd631..00000000 --- a/recipes/_config.rb +++ /dev/null @@ -1,61 +0,0 @@ -include_recipe 'dhcp::_service' - -DHCP::Failover.load(node) -DHCP::DynaDns.load(node) - -# -# Global DHCP config settings -# -template node['dhcp']['config_file'] do - owner 'root' - group 'root' - mode '0644' - source 'dhcpd.conf.erb' - variables( - allows: node['dhcp']['allows'] || [], - parameters: node['dhcp']['parameters'] || [], - options: node['dhcp']['options'] || [], - hooks: node['dhcp']['hooks'], - masters: DHCP::DynaDns.masters, - keys: DHCP::DynaDns.keys, - my_ip: node['dhcp']['my_ip'] || node['ipaddress'], - role: DHCP::Failover.role, - peer_ip: DHCP::Failover.peer, - failover: DHCP::Failover.enabled? - ) - action :create - notifies :restart, "service[#{node['dhcp']['service_name']}]", :delayed -end - -if node['dhcp']['failover_lease_hack'] - template node['dhcp']['dhcpd_leases'] do - owner 'root' - group 'root' - mode '0644' - source 'dhcpd.leases-hack.erb' - action :create - notifies :restart, "service[#{node['dhcp']['service_name']}]" - not_if { ::File.exist?("#{node['dhcp']['dhcpd_leases']}-hack.lock") } - end - - file "#{node['dhcp']['dhcpd_leases']}-hack.lock" do - owner 'root' - group 'root' - mode '0755' - action :touch - end -end - -# -# Create the dirs and stub files for each resource type -# -%w(groups.d hosts.d subnets.d shared_networks.d classes.d).each do |dir| - directory "#{node['dhcp']['dir']}/#{dir} initial creation" do - path "#{node['dhcp']['dir']}/#{dir}" - end - - file "#{node['dhcp']['dir']}/#{dir}/list.conf" do - action :create_if_missing - content '' - end -end diff --git a/recipes/_groups.rb b/recipes/_groups.rb deleted file mode 100644 index ad4cff2d..00000000 --- a/recipes/_groups.rb +++ /dev/null @@ -1,19 +0,0 @@ -# Setup Groups -# -unless node['dhcp']['groups'].empty? - node['dhcp']['groups'].each do |group| - group_data = if node['dhcp']['use_bags'] == true - data_bag_item(node['dhcp']['groups_bag'], group) - else - node['dhcp']['group_data'].fetch group, nil - end - - next unless group_data - dhcp_group group do - parameters group_data['parameters'] || [] - evals group_data['evals'] || [] - hosts group_data['hosts'] || {} - conf_dir node['dhcp']['dir'] - end - end -end diff --git a/recipes/_hosts.rb b/recipes/_hosts.rb deleted file mode 100644 index 70acffb9..00000000 --- a/recipes/_hosts.rb +++ /dev/null @@ -1,44 +0,0 @@ -# Hosts -# -unless node['dhcp']['hosts'].empty? - - # special key to just use all hosts in dhcp_hosts databag - # figure which hosts to load - # TODO: this should be refactored into libs - # rubocop:disable BlockNesting - host_list = node['dhcp']['hosts'] - if host_list.respond_to?(:downcase) && host_list.casecmp('all') == 0 - host_list = node['dhcp']['host_data'].keys - if node['dhcp']['use_bags'] == true - begin - host_list = data_bag(node['dhcp']['hosts_bag']) - # TODO: Make this more specific to the non existing bag - rescue - return - end - end - elsif node['dhcp']['use_bags'] == true - search(node['dhcp']['hosts_bag'], "id:#{node['dhcp']['hosts_bag']}").each do |host| - host_list << host['id'] - end - end - # rubocop:enable BlockNesting - - host_list.each do |host| - host_data = if node['dhcp']['use_bags'] == true - data_bag_item(node['dhcp']['hosts_bag'], Dhcp::Helpers.escape(host)) - else - node['dhcp']['host_data'].fetch host, nil - end - - next unless host_data - dhcp_host host do - hostname host_data['hostname'] - macaddress host_data['mac'] - ipaddress host_data['ip'] - parameters host_data['parameters'] || [] - options host_data['options'] || [] - conf_dir node['dhcp']['dir'] - end - end -end diff --git a/recipes/_networks.rb b/recipes/_networks.rb deleted file mode 100644 index 12b9c223..00000000 --- a/recipes/_networks.rb +++ /dev/null @@ -1,96 +0,0 @@ -# Setup subnets -# - -include_recipe 'dhcp::_service' - -node['dhcp']['networks'].each do |net| - data = if node['dhcp']['use_bags'] == true - data_bag_item(node['dhcp']['networks_bag'], Dhcp::Helpers.escape(net)) - else - node['dhcp']['network_data'].fetch net, nil - end - - next unless data - # run the lwrp with the bag data - dhcp_subnet data['address'] do - broadcast data['broadcast'] if data.key? 'broadcast' - netmask data['netmask'] if data.key? 'netmask' - routers data['routers'] || [] - options data['options'] || [] - if data.key?('range') || node['dhcp']['failover'] - pool do - range data['range'] if data.key? 'range' - peer node['domain'] if node['dhcp']['failover'] - allow data['allow'] if data.key? 'allow' - extra_pool_lines data['extra_pool_lines'] if data.key? 'extra_pool_lines' - end - end - ddns data['ddns'] if data.key? 'ddns' - conf_dir node['dhcp']['dir'] - evals data['evals'] || [] - key data['key'] || {} - zones data['zones'] || [] - next_server data['next_server'] if data.key? 'next_server' - end -end - -# Load databag data -if node['dhcp']['use_bags'] - node['dhcp']['shared_networks'].each do |shared_net| - network_data = data_bag_item(node['dhcp']['networks_bag'], Dhcp::Helpers.escape(shared_net)) - dhcp_shared_network shared_net do - network_data['subnets'].each do |_net, data| - subnet data['address'] do - broadcast data['broadcast'] if data.key? 'broadcast' - netmask data['netmask'] if data.key? 'netmask' - routers data['routers'] || [] - options data['options'] || [] - if data.key?('range') || node['dhcp']['failover'] - pool do - range data['range'] if data.key? 'range' - peer node['domain'] if node['dhcp']['failover'] - allow data['allow'] if data.key? 'allow' - extra_pool_lines data['extra_pool_lines'] if data.key? 'extra_pool_lines' - end - end - ddns data['ddns'] if data.key? 'ddns' - conf_dir node['dhcp']['dir'] - evals data['evals'] || [] - key data['key'] || {} - zones data['zones'] || [] - next_server data['next_server'] if data.key? 'next_server' - end - end - end - end -end - -# Load node attribute data -unless node['dhcp']['use_bags'] - node['dhcp']['shared_network_data'].each do |shared_net, network_data| - dhcp_shared_network shared_net do - network_data['subnets'].each do |_net, data| - subnet data['address'] do - broadcast data['broadcast'] if data.key? 'broadcast' - netmask data['netmask'] if data.key? 'netmask' - routers data['routers'] || [] - options data['options'] || [] - if data.key?('range') || node['dhcp']['failover'] - pool do - range data['range'] if data.key? 'range' - peer node['domain'] if node['dhcp']['failover'] - allow data['allow'] if data.key? 'allow' - extra_pool_lines data['extra_pool_lines'] if data.key? 'extra_pool_lines' - end - end - ddns data['ddns'] if data.key? 'ddns' - conf_dir node['dhcp']['dir'] - evals data['evals'] || [] - key data['key'] || {} - zones data['zones'] || [] - next_server data['next_server'] if data.key? 'next_server' - end - end - end - end -end diff --git a/recipes/_package.rb b/recipes/_package.rb deleted file mode 100644 index 49e581db..00000000 --- a/recipes/_package.rb +++ /dev/null @@ -1,4 +0,0 @@ -apt_update 'update' - -package node['dhcp']['package_name'] -directory node['dhcp']['dir'] diff --git a/recipes/_service.rb b/recipes/_service.rb deleted file mode 100644 index 4f60012e..00000000 --- a/recipes/_service.rb +++ /dev/null @@ -1,16 +0,0 @@ -service node['dhcp']['service_name'] do - supports restart: true, status: true, reload: true - action [:enable] -end - -template node['dhcp']['init_config'] do - owner 'root' - group 'root' - mode '0644' - source 'init_config.erb' - variables( - interfaces: node['dhcp']['interfaces'], - var: node['dhcp']['init_iface'] - ) - not_if { platform_family?('fedora') } -end diff --git a/recipes/library.rb b/recipes/library.rb deleted file mode 100644 index 2e5df19d..00000000 --- a/recipes/library.rb +++ /dev/null @@ -1,2 +0,0 @@ -# encoding: UTF-8 -# stub so you can load just the library/LWRP diff --git a/resources/class.rb b/resources/class.rb index 615a875c..be4aaa4f 100644 --- a/resources/class.rb +++ b/resources/class.rb @@ -1,4 +1,3 @@ -# frozen_string_literal: true # # Cookbook:: dhcp # Resource:: class @@ -16,40 +15,85 @@ # limitations under the License. # -default_action :add +include Dhcp::Cookbook::Helpers -property :match, String, required: true -property :conf_dir, String, default: '/etc/dhcp' +property :comment, String, + description: 'Unparsed comment to add to the configuration file' -attr_accessor :subclasses +property :ip_version, Symbol, + equal_to: %i(ipv4 ipv6), + default: :ipv4, + description: 'The IP version, 4 or 6' -def subclass(value) - @subclasses ||= [] - @subclasses << value -end +property :conf_dir, String, + default: lazy { dhcpd_config_resource_directory(ip_version, declared_type) }, + description: 'Directory to create configuration file in' + +property :cookbook, String, + default: 'dhcp', + description: 'Template source cookbook' + +property :template, String, + default: 'class.conf.erb', + description: 'Template source file' + +property :owner, String, + default: lazy { dhcpd_user }, + description: 'Generated file owner' + +property :group, String, + default: lazy { dhcpd_group }, + description: 'Generated file group' + +property :mode, String, + default: '0640', + description: 'Generated file mode' + +property :match, String, + description: 'Client match statement' + +property :parameters, [Hash, Array], + description: 'Class client parameters' + +property :options, [Hash, Array], + description: 'Class client options' + +property :subclass, Array, + description: 'Class subclass match statements' action_class do - include Dhcp::Helpers + include Dhcp::Cookbook::ResourceHelpers end -action :add do - with_run_context :root do - run_context.include_recipe 'dhcp::_service' - - directory "#{new_resource.conf_dir}/classes.d #{new_resource.name}" do - path "#{new_resource.conf_dir}/classes.d" - end - - template "#{new_resource.conf_dir}/classes.d/#{new_resource.name}.conf" do - cookbook 'dhcp' - source 'class.conf.erb' - variables name: new_resource.name, match: new_resource.match, subclasses: new_resource.subclasses - owner 'root' - group 'root' - mode '0644' - notifies :restart, "service[#{node['dhcp']['service_name']}]", :delayed - end - - write_include 'classes.d', new_resource.name +action :create do + template "#{new_resource.conf_dir}/#{new_resource.name}.conf" do + cookbook new_resource.cookbook + source new_resource.template + + owner new_resource.owner + group new_resource.group + mode new_resource.mode + + variables( + name: new_resource.name, + comment: new_resource.comment, + match: new_resource.match, + parameters: new_resource.parameters, + options: new_resource.options, + subclasses: new_resource.subclass + ) + helpers(Dhcp::Cookbook::TemplateHelpers) + + action :create end + + add_to_list_resource(new_resource.conf_dir, "#{new_resource.conf_dir}/#{new_resource.name}.conf") +end + +action :delete do + file "#{new_resource.conf_dir}/#{new_resource.name}.conf" do + action :delete + end + + remove_from_list_resource(new_resource.conf_dir, "#{new_resource.conf_dir}/#{new_resource.name}.conf") end diff --git a/resources/config.rb b/resources/config.rb new file mode 100644 index 00000000..92d77574 --- /dev/null +++ b/resources/config.rb @@ -0,0 +1,202 @@ +# +# Cookbook:: dhcp +# Resource:: config +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +include Dhcp::Cookbook::Helpers + +property :ip_version, Symbol, + equal_to: %i(ipv4 ipv6), + default: :ipv4, + description: 'The IP version, 4 or 6' + +property :config_file, String, + default: lazy { dhcpd_config_file(ip_version) }, + description: 'The full path to the DHCP server configuration on disk' + +property :cookbook, String, + default: 'dhcp' + +property :template, String, + default: 'dhcpd.conf.erb' + +property :owner, String, + default: lazy { dhcpd_user } + +property :group, String, + default: lazy { dhcpd_group } + +property :mode, String, + default: '0640' + +property :config_failover_file, String, + default: lazy { dhcpd_failover_config_file(ip_version) }, + description: 'The full path to the DHCP server failover configuration on disk' + +property :config_includes_directory, String, + default: lazy { dhcpd_config_includes_directory(ip_version) } + +property :lib_dir, String, + default: lazy { dhcpd_lib_dir }, + description: 'The full path to the DHCP lib directory on disk' + +property :lease_file, String, + default: lazy { dhcpd_lease_file(ip_version) }, + description: 'The full path to the DHCP server leases file on disk' + +property :allow, Array, + description: 'Allow access control' + +property :deny, Array, + description: 'Deny access control' + +property :ignore, Array, + description: 'Ignore access control' + +property :parameters, [Hash, Array], + description: 'Global parameters' + +property :options, [Hash, Array], + description: 'Global options' + +property :evals, Array, + description: 'Global conditional eval statements' + +property :keys, Hash, + description: 'TSIG keys' + +property :zones, Hash, + description: 'Dynamic DNS zones' + +property :hooks, Hash, + description: 'Server event action configuration' + +property :failover, Hash, + description: 'DHCP failover configuration' + +property :include_files, Array, + description: 'Additional configuration files to include' + +property :extra_lines, [String, Array], + description: 'Extra lines to append to the main configuration file' + +property :env_file, String, + default: lazy { dhcpd_env_file }, + description: 'Configuration file lines for the DHCP environment' + +property :env_file_lines, [String, Array], + description: 'Configuration file lines for the DHCP environment file' + +action_class do + include Dhcp::Cookbook::ResourceHelpers +end + +action :create do + raise 'DHCP failover is only supported for IPv4' if new_resource.failover && new_resource.ip_version.eql?(:ipv6) + + directory new_resource.config_includes_directory do + owner new_resource.owner + group new_resource.group + + action :create + end + + dhcpd_config_includes_directories.each do |dir| + directory "#{new_resource.config_includes_directory}/#{dir}" do + owner new_resource.owner + group new_resource.group + + action :create + end + + create_list_resource("#{new_resource.config_includes_directory}/#{dir}") + end + + # Pre-condition DHCPd lib directory and lease file for distros that don't take care of this + dhcpd_lib_dir_options.each { |property, value| edit_resource(:directory, new_resource.lib_dir).send(property, value) } + dhcpd_lease_file_options.each { |property, value| edit_resource(:file, new_resource.lease_file).send(property, value) } + + template new_resource.config_file do + cookbook new_resource.cookbook + source new_resource.template + + owner new_resource.owner + group new_resource.group + mode new_resource.mode + + variables( + includes_dir: dhcpd_config_includes_directory(new_resource.ip_version), + allow: new_resource.allow, + deny: new_resource.deny, + ignore: new_resource.ignore, + parameters: new_resource.parameters, + options: new_resource.options, + evals: new_resource.evals, + keys: new_resource.keys, + zones: new_resource.zones, + hooks: new_resource.hooks, + failover: new_resource.failover, + include_files: new_resource.include_files, + extra_lines: new_resource.extra_lines + ) + helpers(Dhcp::Cookbook::TemplateHelpers) + + action :create + end + + template new_resource.env_file do + cookbook 'dhcp' + source 'dhcpd-env.erb' + + owner new_resource.owner + group new_resource.group + mode new_resource.mode + + variables( + lines: new_resource.env_file_lines + ) + helpers(Dhcp::Cookbook::TemplateHelpers) + + action :create + end if new_resource.env_file + + if new_resource.failover + template new_resource.config_failover_file do + cookbook 'dhcp' + source 'dhcpd.failover.conf.erb' + + owner new_resource.owner + group new_resource.group + mode new_resource.mode + + variables( + failover: new_resource.failover + ) + helpers(Dhcp::Cookbook::TemplateHelpers) + + action :create + end + end +end + +action :delete do + file new_resource.config_file do + action :delete + end + + file new_resource.config_failover_file do + action :delete + end if new_resource.ip_version.eql?(:ipv4) +end diff --git a/resources/group.rb b/resources/group.rb index f81a30e2..fe08a2d6 100644 --- a/resources/group.rb +++ b/resources/group.rb @@ -1,4 +1,3 @@ -# frozen_string_literal: true # # Cookbook:: dhcp # Resource:: group @@ -16,49 +15,106 @@ # limitations under the License. # -default_action :add +include Dhcp::Cookbook::Helpers -property :parameters, Array, default: [] -property :evals, Array, default: [] -property :hosts, Hash, default: {} -property :conf_dir, String, default: '/etc/dhcp' +property :comment, String, + description: 'Unparsed comment to add to the configuration file' + +property :ip_version, Symbol, + equal_to: %i(ipv4 ipv6), + default: :ipv4, + description: 'The IP version, 4 or 6' + +property :conf_dir, String, + default: lazy { dhcpd_config_resource_directory(ip_version, declared_type) }, + description: 'Directory to create configuration file in' + +property :cookbook, String, + default: 'dhcp', + description: 'Template source cookbook' + +property :template, String, + default: 'group.conf.erb', + description: 'Template source file' + +property :owner, String, + default: lazy { dhcpd_user }, + description: 'Generated file owner' + +property :group, String, + default: lazy { dhcpd_group }, + description: 'Generated file group' + +property :mode, String, + default: '0640', + description: 'Generated file mode' + +property :parameters, [Hash, Array], + description: 'Group client parameters' + +property :options, [Hash, Array], + description: 'Group client options' + +property :evals, Array, + description: 'Group conditional eval statements' + +property :hosts, Hash, + description: 'Group host members' action_class do - include Dhcp::Helpers + include Dhcp::Cookbook::ResourceHelpers end -action :add do - with_run_context :root do - directory "#{new_resource.conf_dir}/groups.d #{new_resource.name}" do - path "#{new_resource.conf_dir}/groups.d" +action :create do + hosts_include = [] + + new_resource.hosts.each do |host, properties| + hr = edit_resource(:dhcp_host, "#{new_resource.name}_grouphost_#{host}") do + owner new_resource.owner + group new_resource.group + mode new_resource.mode + + ip_version new_resource.ip_version + conf_dir new_resource.conf_dir + group_host true end - template "#{new_resource.conf_dir}/groups.d/#{new_resource.name}.conf" do - cookbook 'dhcp' - source 'group.conf.erb' - variables( - name: new_resource.name, - parameters: new_resource.parameters, - evals: new_resource.evals, - hosts: new_resource.hosts - ) - owner 'root' - group 'root' - mode '0644' - notifies :restart, "service[#{node['dhcp']['service_name']}]", :delayed + properties.each do |property, value| + hr.send(property, value) end - write_include 'groups.d', new_resource.name + hosts_include.push("#{new_resource.conf_dir}/#{new_resource.name}_grouphost_#{host}.conf") end -end -action :remove do - with_run_context :root do - file "#{new_resource.conf_dir}/groups.d/#{new_resource.name}.conf" do - action :delete - notifies :restart, "service[#{node['dhcp']['service_name']}]", :delayed - end + template "#{new_resource.conf_dir}/#{new_resource.name}.conf" do + cookbook new_resource.cookbook + source new_resource.template + + owner new_resource.owner + group new_resource.group + mode new_resource.mode + + variables( + name: new_resource.name, + comment: new_resource.comment, + ip_version: new_resource.ip_version, + parameters: new_resource.parameters, + options: new_resource.options, + evals: new_resource.evals, + hosts: hosts_include + ) + helpers(Dhcp::Cookbook::TemplateHelpers) - write_include 'groups.d', new_resource.name + action :create end + + add_to_list_resource(new_resource.conf_dir, "#{new_resource.conf_dir}/#{new_resource.name}.conf") +end + +action :delete do + file "#{new_resource.conf_dir}/#{new_resource.name}.conf" do + action :delete + end + + remove_from_list_resource(new_resource.conf_dir, "#{new_resource.conf_dir}/#{new_resource.name}.conf") end diff --git a/resources/host.rb b/resources/host.rb index bd0c6c5f..6637d76c 100644 --- a/resources/host.rb +++ b/resources/host.rb @@ -1,4 +1,3 @@ -# frozen_string_literal: true # # Cookbook:: dhcp # Resource:: host @@ -16,52 +15,90 @@ # limitations under the License. # -default_action :add +include Dhcp::Cookbook::Helpers -property :hostname, String -property :macaddress, String -property :ipaddress, String -property :options, Array, default: [] -property :parameters, Array, default: [] -property :conf_dir, String, default: '/etc/dhcp' +property :comment, String, + description: 'Unparsed comment to add to the configuration file' + +property :ip_version, Symbol, + equal_to: %i(ipv4 ipv6), + default: :ipv4, + description: 'The IP version, 4 or 6' + +property :conf_dir, String, + default: lazy { dhcpd_config_resource_directory(ip_version, declared_type) }, + description: 'Directory to create configuration file in' + +property :cookbook, String, + default: 'dhcp', + description: 'Template source cookbook' + +property :template, String, + default: 'host.conf.erb', + description: 'Template source file' + +property :owner, String, + default: lazy { dhcpd_user }, + description: 'Generated file owner' + +property :group, String, + default: lazy { dhcpd_group }, + description: 'Generated file group' + +property :mode, String, + default: '0640', + description: 'Generated file mode' + +property :group_host, [true, false], + default: false, + description: 'Flag to indicate host is used inside a group resource and should not be added to list.conf' + +property :identifier, String, + description: 'Host identifier, usually MAC or DHCID' + +property :address, String, + description: 'Host assigned address' + +property :parameters, [Hash, Array], + description: 'Host parameters' + +property :options, [Hash, Array], + description: 'Host options' action_class do - include Dhcp::Helpers + include Dhcp::Cookbook::ResourceHelpers end -action :add do - with_run_context :root do - directory "#{new_resource.conf_dir}/hosts.d #{new_resource.name}" do - path "#{new_resource.conf_dir}/hosts.d" - end - - template "#{new_resource.conf_dir}/hosts.d/#{new_resource.name}.conf" do - cookbook 'dhcp' - source 'host.conf.erb' - variables( - name: new_resource.name, - hostname: new_resource.hostname, - macaddress: new_resource.macaddress, - ipaddress: new_resource.ipaddress, - options: new_resource.options, - parameters: new_resource.parameters - ) - owner 'root' - group 'root' - mode '0644' - notifies :restart, "service[#{node['dhcp']['service_name']}]", :delayed - end - write_include 'hosts.d', new_resource.name +action :create do + template "#{new_resource.conf_dir}/#{new_resource.name}.conf" do + cookbook new_resource.cookbook + source new_resource.template + + owner new_resource.owner + group new_resource.group + mode new_resource.mode + + variables( + name: new_resource.name, + comment: new_resource.comment, + ip_version: new_resource.ip_version, + identifier: new_resource.identifier, + address: new_resource.address, + parameters: new_resource.parameters, + options: new_resource.options + ) + helpers(Dhcp::Cookbook::TemplateHelpers) + + action :create end -end -action :remove do - with_run_context :root do - file "#{new_resource.conf_dir}/hosts.d/#{new_resource.name}.conf" do - action :delete - notifies :restart, "service[#{node['dhcp']['service_name']}]", :delayed - end + add_to_list_resource(new_resource.conf_dir, "#{new_resource.conf_dir}/#{new_resource.name}.conf") unless new_resource.group_host +end - write_include 'hosts.d', new_resource.name +action :delete do + file "#{new_resource.conf_dir}/#{new_resource.name}.conf" do + action :delete end + + remove_from_list_resource(new_resource.conf_dir, "#{new_resource.conf_dir}/#{new_resource.name}.conf") unless new_resource.group_host end diff --git a/recipes/server.rb b/resources/package.rb similarity index 59% rename from recipes/server.rb rename to resources/package.rb index 810db1ec..c02ef86c 100644 --- a/recipes/server.rb +++ b/resources/package.rb @@ -1,12 +1,6 @@ -# encoding: UTF-8 # -# Author:: Jesse Nelson -# Author:: Matt Ray # Cookbook:: dhcp -# Recipe:: server -# -# Copyright:: 2012-2017, Jesse Nelson -# Copyright:: 2011-2017, Chef Software, Inc +# Resource:: package # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -21,9 +15,25 @@ # limitations under the License. # -include_recipe 'dhcp::_package' -include_recipe 'dhcp::_service' -include_recipe 'dhcp::_config' -include_recipe 'dhcp::_networks' -include_recipe 'dhcp::_groups' -include_recipe 'dhcp::_hosts' +include Dhcp::Cookbook::Helpers + +property :packages, [String, Array], + default: lazy { dhcpd_packages } + +action_class do + def do_action(package_action) + package 'ISC DHCPD' do + package_name new_resource.packages + + action package_action + end + end +end + +action :install do + do_action(action) +end + +action :remove do + do_action(action) +end diff --git a/resources/pool.rb b/resources/pool.rb deleted file mode 100644 index 191836ae..00000000 --- a/resources/pool.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true -# -# Cookbook:: dhcp -# Resource:: pool -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -default_action :nothing - -attribute :range, kind_of: [Array, String] -attribute :peer, kind_of: String, default: nil -attribute :deny, kind_of: [Array, String], default: [], coerce: proc { |prop| - Array(prop).flatten -} -attribute :allow, kind_of: [Array, String], default: [], coerce: proc { |prop| - Array(prop).flatten -} -attribute :extra_pool_lines, kind_of: [Array, String], default: [], coerce: proc { |prop| - Array(prop).flatten -} - -# This resource has no actions, and is only used to verify properties -# for the dhcp_subnet pool subresource. diff --git a/resources/service.rb b/resources/service.rb new file mode 100644 index 00000000..df873341 --- /dev/null +++ b/resources/service.rb @@ -0,0 +1,91 @@ +# +# Cookbook:: dhcp +# Resource:: service +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +include Dhcp::Cookbook::Helpers + +property :ip_version, Symbol, + equal_to: %i(ipv4 ipv6), + default: :ipv4, + description: 'The IP version, 4 or 6' + +property :service_name, String, + coerce: proc { |s| "#{s}.service" }, + description: 'Override the default service names' + +property :systemd_unit_content, [String, Hash], + default: lazy { dhcpd_systemd_unit_content(ip_version) }, + description: 'Override the systemd unit file contents' + +action_class do + def service_name + if new_resource.service_name + new_resource.service_name + else + dhcpd_service_name(new_resource.ip_version) + end + end + + def do_service_action(resource_action) + with_run_context(:root) do + edit_resource(:service, service_name) do + delayed_action resource_action + end + end + end +end + +action :create do + with_run_context :root do + edit_resource(:systemd_unit, "#{service_name}.service") do |new_resource| + content new_resource.dhcpd_systemd_unit_content(new_resource.ip_version) + triggers_reload true + verify false + + action :create + end if dhcpd_use_systemd? + end +end + +action :delete do + do_service_action([:stop, :disable]) + with_run_context :root do + edit_resource(:systemd_unit, "#{service_name}.service").action(:delete) if dhcpd_use_systemd? + end +end + +action :start do + do_service_action(action) +end + +action :stop do + do_service_action(action) +end + +action :restart do + do_service_action(action) +end + +action :reload do + do_service_action(action) +end + +action :enable do + do_service_action(action) +end + +action :disable do + do_service_action(action) +end diff --git a/resources/shared_network.rb b/resources/shared_network.rb index f592889b..78f9cc76 100644 --- a/resources/shared_network.rb +++ b/resources/shared_network.rb @@ -1,10 +1,7 @@ # -# Author:: Jacob McCann () # Cookbook:: dhcp # Resource:: shared_network # -# Copyright:: 2015-2018, Sous Chefs -# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -18,56 +15,93 @@ # limitations under the License. # -default_action :add +include Dhcp::Cookbook::Helpers -property :conf_dir, String, default: '/etc/dhcp' +property :comment, String, + description: 'Unparsed comment to add to the configuration file' -include Chef::DSL::Recipe +property :ip_version, Symbol, + equal_to: %i(ipv4 ipv6), + default: :ipv4, + description: 'The IP version, 4 or 6' -attr_accessor :subnets +property :conf_dir, String, + default: lazy { dhcpd_config_resource_directory(ip_version, declared_type) }, + description: 'Directory to create configuration file in' -def subnet(name, &block) - @subnets ||= [] - s = dhcp_subnet("#{@name}-#{name}", &block) - s.action :nothing - s.subnet name - @subnets << s - s -end +property :cookbook, String, + default: 'dhcp', + description: 'Template source cookbook' + +property :template, String, + default: 'shared_network.conf.erb', + description: 'Template source file' + +property :owner, String, + default: lazy { dhcpd_user }, + description: 'Generated file owner' + +property :group, String, + default: lazy { dhcpd_group }, + description: 'Generated file group' + +property :mode, String, + default: '0640', + description: 'Generated file mode' + +property :subnets, Hash, + description: 'Shared subnets configuration hash' action_class do - include Dhcp::Helpers + include Dhcp::Cookbook::ResourceHelpers end -action :add do - with_run_context :root do - run_context.include_recipe 'dhcp::_service' +action :create do + subnets_include = [] + + new_resource.subnets.each do |subnet, properties| + sr = edit_resource(:dhcp_subnet, "#{new_resource.name}_sharedsubnet_#{subnet}") do + owner new_resource.owner + group new_resource.group + mode new_resource.mode - directory "#{new_resource.conf_dir}/shared_networks.d #{new_resource.name}" do - path "#{new_resource.conf_dir}/shared_networks.d" + ip_version new_resource.ip_version + conf_dir new_resource.conf_dir + shared_network true end - template "#{new_resource.conf_dir}/shared_networks.d/#{new_resource.name}.conf" do - cookbook 'dhcp' - source 'shared_network.conf.erb' - variables name: new_resource.name, subnets: new_resource.subnets - owner 'root' - group 'root' - mode '0644' - notifies :restart, "service[#{node['dhcp']['service_name']}]", :delayed + properties.each do |property, value| + sr.send(property, value) end - write_include 'shared_networks.d', new_resource.name + subnets_include.push("#{new_resource.conf_dir}/#{new_resource.name}_sharedsubnet_#{subnet}.conf") end + + template "#{new_resource.conf_dir}/#{new_resource.name}.conf" do + cookbook new_resource.cookbook + source new_resource.template + + owner new_resource.owner + group new_resource.group + mode new_resource.mode + + variables( + name: new_resource.name, + comment: new_resource.comment, + subnets: subnets_include + ) + helpers(Dhcp::Cookbook::TemplateHelpers) + + action :create + end + + add_to_list_resource(new_resource.conf_dir, "#{new_resource.conf_dir}/#{new_resource.name}.conf") end -action :remove do - with_run_context :root do - file "#{new_resource.conf_dir}/shared_networks.d/#{new_resource.name}.conf" do - action :delete - notifies :restart, "service[#{node['dhcp']['service_name']}]", :delayed - notifies :send_notification, new_resource, :immediately - end - write_include 'shared_networks.d', new_resource.name +action :delete do + file "#{new_resource.conf_dir}/#{new_resource.name}.conf" do + action :delete end + + remove_from_list_resource(new_resource.conf_dir, "#{new_resource.conf_dir}/#{new_resource.name}.conf") end diff --git a/resources/subnet.rb b/resources/subnet.rb index 19c10d6e..9e2c9963 100644 --- a/resources/subnet.rb +++ b/resources/subnet.rb @@ -1,4 +1,3 @@ -# frozen_string_literal: true # # Cookbook:: dhcp # Resource:: subnet @@ -16,78 +15,139 @@ # limitations under the License. # -default_action :add - -property :subnet, String, name_property: true -property :broadcast, String -property :netmask, String, required: true -property :routers, Array, default: [] -property :options, Array, default: [] -property :ddns, String -property :evals, Array, default: [] -property :key, Hash, default: {} -property :zones, Array, default: [] -property :conf_dir, String, default: '/etc/dhcp' -property :next_server, String - -include Chef::DSL::Recipe - -attr_accessor :pools - -def pool(&block) - @pools ||= [] - p = dhcp_pool("#{@name}-pool#{@pools.count}", &block) - p.action :nothing - @pools << p - p -end +include Dhcp::Cookbook::Helpers + +property :comment, String, + description: 'Unparsed comment to add to the configuration file' + +property :ip_version, Symbol, + equal_to: %i(ipv4 ipv6), + default: :ipv4, + description: 'The IP version, 4 or 6' + +property :conf_dir, String, + default: lazy { dhcpd_config_resource_directory(ip_version, declared_type) }, + description: 'Directory to create configuration file in' + +property :cookbook, String, + default: 'dhcp', + description: 'Template source cookbook' + +property :template, String, + default: lazy { + case ip_version + when :ipv4 + 'subnet.conf.erb' + when :ipv6 + 'subnet6.conf.erb' + end + }, + description: 'Template source file' + +property :owner, String, + default: lazy { dhcpd_user }, + description: 'Generated file owner' + +property :group, String, + default: lazy { dhcpd_group }, + description: 'Generated file group' + +property :mode, String, + default: '0640', + description: 'Generated file mode' + +property :shared_network, [true, false], + default: false, + description: 'Flag to indicate subnet is used inside a shared_network resource and should not be added to list.conf' + +property :subnet, String, + name_property: true, + description: 'Subnet network address' + +property :netmask, String, + description: 'Subnet network mask, required for IPv4' + +property :prefix, Integer, + description: 'Subnet network prefix, required for IPv6' + +property :parameters, [Hash, Array], + description: 'Subnet configuration parameters' + +property :options, [Hash, Array], + description: 'Subnet options' + +property :evals, Array, + description: 'Subnet conditional eval statements' + +property :key, Hash, + description: 'Subnet TSIG keys' + +property :zones, Hash, + description: 'Subnet dynamic DNS zones' + +property :allow, Array, + description: 'Subnet allow access control' + +property :deny, Array, + description: 'Subnet deny access control' + +property :extra_lines, Array, + description: 'Subnet additional configuration lines' + +property :pools, [Hash, Array], + description: 'Subnet pool(s) configuration' + +property :range, [String, Array] action_class do - include Dhcp::Helpers + include Dhcp::Cookbook::ResourceHelpers end -action :add do - with_run_context :root do - run_context.include_recipe 'dhcp::_service' - - directory "#{new_resource.conf_dir}/subnets.d #{new_resource.name}" do - path "#{new_resource.conf_dir}/subnets.d" - end - - template "#{new_resource.conf_dir}/subnets.d/#{new_resource.name}.conf" do - cookbook 'dhcp' - source 'subnet.conf.erb' - variables( - subnet: new_resource.subnet, - netmask: new_resource.netmask, - broadcast: new_resource.broadcast, - pools: new_resource.pools, - routers: new_resource.routers, - options: new_resource.options, - evals: new_resource.evals, - key: new_resource.key, - zones: new_resource.zones, - ddns: new_resource.ddns, - next_server: new_resource.next_server - ) - owner 'root' - group 'root' - mode '0644' - notifies :restart, "service[#{node['dhcp']['service_name']}]", :delayed - end - - write_include 'subnets.d', new_resource.name +action :create do + case new_resource.ip_version + when :ipv4 + raise 'netmask is a required property for IPv4' unless new_resource.netmask + when :ipv6 + raise 'prefix is a required property for IPv6' unless new_resource.prefix end -end -action :remove do - with_run_context :root do - file "#{new_resource.conf_dir}/subnets.d/#{new_resource.name}.conf" do - action :delete - notifies :restart, "service[#{node['dhcp']['service_name']}]", :delayed - notifies :send_notification, new_resource, :immediately - end + template "#{new_resource.conf_dir}/#{new_resource.name}.conf" do + cookbook new_resource.cookbook + source new_resource.template - write_include 'subnets.d', new_resource.name + owner new_resource.owner + group new_resource.group + mode new_resource.mode + + variables( + name: new_resource.name, + comment: new_resource.comment, + subnet: new_resource.subnet, + netmask: new_resource.netmask, + prefix: new_resource.prefix, + parameters: new_resource.parameters, + options: new_resource.options, + evals: new_resource.evals, + key: new_resource.key, + zones: new_resource.zones, + allow: new_resource.allow, + deny: new_resource.deny, + extra_lines: new_resource.extra_lines, + pools: new_resource.pools, + range: new_resource.range + ) + helpers(Dhcp::Cookbook::TemplateHelpers) + + action :create end + + add_to_list_resource(new_resource.conf_dir, "#{new_resource.conf_dir}/#{new_resource.name}.conf") unless new_resource.shared_network +end + +action :delete do + file "#{new_resource.conf_dir}/#{new_resource.name}.conf" do + action :delete + end + + remove_from_list_resource(new_resource.conf_dir, "#{new_resource.conf_dir}/#{new_resource.name}.conf") unless new_resource.shared_network end diff --git a/spec/_config_spec.rb b/spec/_config_spec.rb deleted file mode 100644 index b87b1d32..00000000 --- a/spec/_config_spec.rb +++ /dev/null @@ -1,92 +0,0 @@ -require 'spec_helper' - -describe 'dhcp::_config' do - context 'when all attributes are default, on rhel 6.x' do - cached(:chef_run) do - ChefSpec::ServerRunner.new(platform: 'centos', version: '6.8').converge(described_recipe) - end - - let(:params) do - { - 'default-lease-time' => '6400', - 'ddns-domainname' => '"local"', - 'ddns-update-style' => 'interim', - 'max-lease-time' => '86400', - 'update-static-leases' => 'true', - 'one-lease-per-client' => 'true', - 'authoritative' => '', - 'ping-check' => 'true', - 'next-server' => '10.0.0.2', - 'filename' => '"pxelinux.0"', - } - end - - let(:opts) do - { - 'domain-name' => '"local"', - 'domain-name-servers' => '8.8.8.8', - 'host-name' => ' = binary-to-ascii (16, 8, "-", substring (hardware, 1, 6))', - } - end - - it 'generates a config file' do - expect(chef_run).to create_template('/etc/dhcp/dhcpd.conf') - .with(variables: { allows: %w(booting bootp unknown-clients), - parameters: params, options: opts, masters: nil, - keys: nil, my_ip: '10.0.0.2', role: nil, - peer_ip: nil, failover: false, hooks: {} }) - expect(chef_run).to render_file('/etc/dhcp/dhcpd.conf').with_content(File.read(File.join(File.dirname(__FILE__), 'fixtures', 'dhcpd.conf.default'))) - end - end - - context 'when attributes are overridden, on rhel 6.x' do - # Only testing overriding node['dhcp']['extra_files'] - # Need to add testing for all other attributes used by _config recipe - cached(:chef_run) do - ChefSpec::ServerRunner.new(platform: 'centos', version: '6.8') do |node| - node.override['dhcp']['extra_files'] = ['/etc/dhcp/my_conf.conf', '/tmp/bad.conf'] - node.override['dhcp']['hooks'] = hooks - end.converge(described_recipe) - end - - let(:hooks) do - { - 'commit' => ['set clip = binary-to-ascii(10, 8, ".", leased-address);', - 'set clhw = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));', - 'execute("/usr/local/sbin/dhcpevent", "commit", clip, clhw, host-decl-name);'], - } - end - - let(:params) do - { - 'default-lease-time' => '6400', - 'ddns-domainname' => '"local"', - 'ddns-update-style' => 'interim', - 'max-lease-time' => '86400', - 'update-static-leases' => 'true', - 'one-lease-per-client' => 'true', - 'authoritative' => '', - 'ping-check' => 'true', - 'next-server' => '10.0.0.2', - 'filename' => '"pxelinux.0"', - } - end - - let(:opts) do - { - 'domain-name' => '"local"', - 'domain-name-servers' => '8.8.8.8', - 'host-name' => ' = binary-to-ascii (16, 8, "-", substring (hardware, 1, 6))', - } - end - - it 'generates config file with noextra config files' do - expect(chef_run).to create_template('/etc/dhcp/dhcpd.conf') - .with(variables: { allows: %w(booting bootp unknown-clients), - parameters: params, options: opts, masters: nil, - keys: nil, my_ip: '10.0.0.2', role: nil, - peer_ip: nil, failover: false, hooks: hooks }) - expect(chef_run).to render_file('/etc/dhcp/dhcpd.conf').with_content(File.read(File.join(File.dirname(__FILE__), 'fixtures', 'dhcpd.conf.overrides'))) - end - end -end diff --git a/spec/_hosts_spec.rb b/spec/_hosts_spec.rb deleted file mode 100644 index fd8d680f..00000000 --- a/spec/_hosts_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'spec_helper' - -describe 'dhcp::_hosts' do - context 'when all attributes are default, on rhel 6.x' do - cached(:chef_run) do - ChefSpec::ServerRunner.new(platform: 'centos', version: '6.8') do |node, server| - node.default['dhcp']['hosts'] = %w(pxe_test-vm vagrant-vm) - server.create_data_bag('dhcp_hosts', - 'pxe_test-vm' => parse_data_bag('dhcp_hosts/pxe_test-vm'), - 'vagrant-vm' => parse_data_bag('dhcp_hosts/vagrant-vm')) - end.converge(described_recipe) - end - - it 'converges' do - chef_run - end - end -end diff --git a/spec/_networks_spec.rb b/spec/_networks_spec.rb deleted file mode 100644 index 2cd868a8..00000000 --- a/spec/_networks_spec.rb +++ /dev/null @@ -1,101 +0,0 @@ -require 'spec_helper' - -describe 'dhcp::_networks Exceptions' do - before(:each) do - Fauxhai.mock(platform: 'ubuntu', version: '14.04') - @chef_run = ChefSpec::ServerRunner.new - end - - it 'should not raise error unless when bags are missing' do - @chef_run.converge 'dhcp::_networks' - end -end - -describe 'dhcp::_networks' do - context 'driven by node attributes' do - cached(:chef_run) do - ChefSpec::ServerRunner.new(platform: 'centos', version: '6.8', step_into: %w(dhcp_subnet dhcp_shared_network)) do |node| - node.default['chef_environment'] = 'production' - node.override['dhcp']['use_bags'] = false - node.override['dhcp']['networks'] = ['192.168.9.0/24', '192.168.11.0/24'] - node.override['dhcp']['network_data']['192.168.9.0/24'] = { - 'routers' => ['192.168.9.1'], - 'address' => '192.168.9.0', - 'netmask' => '255.255.255.0', - 'broadcast' => '192.168.9.255', - 'range' => '192.168.9.50 192.168.9.240', - 'options' => ['time-offset 10'], - 'next_server' => '192.168.9.11', - } - node.override['dhcp']['network_data']['192.168.11.0/24'] = { - 'address' => '192.168.11.0', - 'netmask' => '255.255.255.0', - } - node.override['dhcp']['shared_network_data']['mysharednet']['subnets']['192.168.10.0/24'] = { - 'routers' => ['192.168.10.1'], - 'address' => '192.168.10.0', - 'netmask' => '255.255.255.0', - 'broadcast' => '192.168.10.255', - 'range' => '192.168.10.50 192.168.10.240', - 'next_server' => '192.168.10.11', - } - node.override['dhcp']['shared_network_data']['mysharednet']['subnets']['10.0.2.0/24'] = { - 'address' => '10.0.2.0', - 'netmask' => '255.255.255.0', - } - end.converge(described_recipe) - end - - it 'declares subnet 192.168.9.0' do - expect(chef_run).to add_dhcp_subnet('192.168.9.0') - .with(broadcast: '192.168.9.255', netmask: '255.255.255.0', routers: ['192.168.9.1'], - options: ['time-offset 10'], next_server: '192.168.9.11', - conf_dir: '/etc/dhcp', evals: [], key: {}, zones: []) - - # Check that pools are defined for the subnet - subnet = chef_run.dhcp_subnet '192.168.9.0' - expect(subnet.pools.count).to eq 1 - - # Check that the pool is defined correctly - subnet_pool = chef_run.dhcp_pool '192.168.9.0-pool0' - expect(subnet_pool).to do_nothing - expect(subnet_pool.range).to eq '192.168.9.50 192.168.9.240' - end - - it 'generates subnet config for 192.168.9.0' do - expect(chef_run).to create_template '/etc/dhcp/subnets.d/192.168.9.0.conf' - expect(chef_run).to render_file('/etc/dhcp/subnets.d/192.168.9.0.conf').with_content(File.read(File.join(File.dirname(__FILE__), 'fixtures', '192.168.9.0.conf'))) - end - - it 'generates blank subnet config for 192.168.11.0' do - expect(chef_run).to create_template '/etc/dhcp/subnets.d/192.168.11.0.conf' - expect(chef_run).to render_file('/etc/dhcp/subnets.d/192.168.11.0.conf').with_content(File.read(File.join(File.dirname(__FILE__), 'fixtures', '192.168.11.0.conf'))) - end - - it 'declares shared-network mysharednet' do - expect(chef_run).to add_dhcp_shared_network('mysharednet') - end - - it 'declares the subnets in the mysharednet shared-network' do - subnet1 = chef_run.dhcp_subnet 'mysharednet-192.168.10.0' - expect(subnet1).to do_nothing - expect(subnet1.pools.count).to eq 1 - expect(subnet1.subnet).to eq '192.168.10.0' - expect(subnet1.netmask).to eq '255.255.255.0' - subnet1_pool = chef_run.dhcp_pool 'mysharednet-192.168.10.0-pool0' - expect(subnet1_pool).to do_nothing - expect(subnet1_pool.range).to eq '192.168.10.50 192.168.10.240' - - subnet2 = chef_run.dhcp_subnet 'mysharednet-10.0.2.0' - expect(subnet2).to do_nothing - expect(subnet2.pools).to be_nil - expect(subnet2.subnet).to eq '10.0.2.0' - expect(subnet2.netmask).to eq '255.255.255.0' - end - - it 'generates a shared network config' do - expect(chef_run).to create_template '/etc/dhcp/shared_networks.d/mysharednet.conf' - expect(chef_run).to render_file('/etc/dhcp/shared_networks.d/mysharednet.conf').with_content(File.read(File.join(File.dirname(__FILE__), 'fixtures', 'mysharednet.conf'))) - end - end -end diff --git a/spec/dynadns_spec.rb b/spec/dynadns_spec.rb deleted file mode 100644 index a2608598..00000000 --- a/spec/dynadns_spec.rb +++ /dev/null @@ -1,61 +0,0 @@ -require 'spec_helper' -require_relative '../libraries/dynadns' - -describe 'DHCP::DyanDns disabled' do - cached(:chef_run) do - ChefSpec::ServerRunner.new.converge('dhcp::library') - end - - it 'should take no action if we have no zone info' do - DHCP::DynaDns.load chef_run.node - expect(DHCP::DynaDns.zones).to be_nil - end -end - -describe 'DHCP::DynaDns malformed' do - cached(:chef_run) do - ChefSpec::ServerRunner.new do |node, server| - node.override['dns']['zones'] = %w(192.168.1.0) - - server.create_data_bag('dns_zones', - '192-168-1-0' => parse_data_bag('dns_zones/192-168-1-0'), - 'vm' => parse_data_bag('dns_zones/vm')) - end.converge('dhcp::library') - end - - it 'should not return masters without keys' do - DHCP::DynaDns.load chef_run.node - expect(DHCP::DynaDns.masters).to eq({}) - end -end - -describe 'DHCP::DynaDns' do - # cached does not appear to be working w/ data bags in chefspec 6+ - let(:chef_run) do - ChefSpec::ServerRunner.new do |node, server| - node.override['dns']['master'] = '192.168.9.9' - node.override['dns']['zones'] = %w(vm 192.168.1.0) - node.normal['dns']['rndc_key'] = 'dhcp-key' - - server.create_data_bag('dns_zones', - '192-168-1-0' => parse_data_bag('dns_zones/192-168-1-0'), - 'vm' => parse_data_bag('dns_zones/vm')) - server.create_data_bag('rndc_keys', 'dhcp-key' => parse_data_bag('rndc_keys/dhcp-key')) - end.converge('dhcp::library') - end - - it 'should load defined zones' do - DHCP::DynaDns.load(chef_run.node) - expect(DHCP::DynaDns.load_zones.length).to eq 2 - end - - it 'should return masters' do - DHCP::DynaDns.load(chef_run.node) - expect(DHCP::DynaDns.masters).to eq('vm' => { 'master' => '192.168.1.9', 'key' => 'dhcp-key' }, '1.168.192.IN-ADDR.ARPA' => { 'master' => '192.168.9.9', 'key' => 'dhcp-key' }) - end - - it 'should load requested keys' do - DHCP::DynaDns.load(chef_run.node) - expect(DHCP::DynaDns.keys).to eq('dhcp-key' => { 'id' => 'dhcp-key', 'algorithm' => 'hmac-md5', 'secret' => 'L+Jl4+4onU4Wstfi4pdmnQ==', 'chef_type' => 'data_bag_item', 'data_bag' => 'rndc_keys' }) - end -end diff --git a/spec/failover_spec.rb b/spec/failover_spec.rb deleted file mode 100644 index 5e980df8..00000000 --- a/spec/failover_spec.rb +++ /dev/null @@ -1,117 +0,0 @@ -require 'spec_helper' -require_relative '../libraries/failover' - -describe 'DHCP::Failover' do - cached(:chef_run) do - ChefSpec::ServerRunner.new.converge('dhcp::library') - end - - it 'should detect when disabled' do - DHCP::Failover.load(chef_run.node) - expect(DHCP::Failover.enabled?).to be false - end -end - -describe 'DHCP::Failover Master without slaves' do - cached(:chef_run) do - ChefSpec::ServerRunner.new do |node, _server| - node.default['dhcp']['master'] = true - node.automatic['ipaddress'] = '10.1.1.10' - end.converge('dhcp::library') - end - - it 'should disable without secondary' do - DHCP::Failover.load(chef_run.node) - expect(DHCP::Failover.enabled?).to be false - end - - it 'should identify as primary' do - DHCP::Failover.load(chef_run.node) - expect(DHCP::Failover.role).to eq 'primary' - end -end - -describe 'DHCP::Failover Master with slaves' do - let(:slave) do - stub_node('slave', platform: 'ubuntu', version: '14.04') do |node| - node.default['dhcp']['slave'] = true - node.automatic['ipaddress'] = '10.1.1.20' - end - end - - let(:chef_run) do - ChefSpec::ServerRunner.new do |node, server| - node.default[:dhcp][:master] = true - node.automatic[:ipaddress] = '10.1.1.10' - - server.create_node(slave) - end.converge('dhcp::library') - end - - it 'should identify as primary' do - DHCP::Failover.load(chef_run.node) - expect(DHCP::Failover.role).to eq 'primary' - end - - it 'should enable when secondary found' do - DHCP::Failover.load(chef_run.node) - expect(DHCP::Failover.enabled?).to be true - end - - it 'should find our peer' do - DHCP::Failover.load(chef_run.node) - expect(DHCP::Failover.peer).to eq '10.1.1.20' - end -end - -describe 'DHCP::Failover Slave no master' do - cached(:chef_run) do - ChefSpec::ServerRunner.new do |node| - node.default['dhcp']['slave'] = true - node.automatic['ipaddress'] = '10.1.1.20' - end.converge('dhcp::library') - end - - it 'should identify as secondary' do - DHCP::Failover.load(chef_run.node) - expect(DHCP::Failover.role).to eq 'secondary' - end - - it 'should disable when no primaries found' do - DHCP::Failover.load(chef_run.node) - expect(DHCP::Failover.enabled?).to be false - end -end - -describe 'DHCP::Failover Slave' do - let(:master) do - stub_node('master', platform: 'ubuntu', version: '14.04') do |node| - node.default['dhcp']['master'] = true - node.automatic['ipaddress'] = '10.1.1.10' - end - end - - let(:chef_run) do - ChefSpec::ServerRunner.new do |node, server| - node.default[:dhcp][:slave] = true - node.default[:ipaddress] = '10.1.1.20' - - server.create_node(master) - end.converge('dhcp::library') - end - - it 'should identify as secondary' do - DHCP::Failover.load(chef_run.node) - expect(DHCP::Failover.role).to eq 'secondary' - end - - it 'should enable when primary found' do - DHCP::Failover.load(chef_run.node) - expect(DHCP::Failover.enabled?).to be true - end - - it 'should find our peer' do - DHCP::Failover.load(chef_run.node) - expect(DHCP::Failover.peer).to eq '10.1.1.10' - end -end diff --git a/spec/fixtures/192.168.11.0.conf b/spec/fixtures/192.168.11.0.conf deleted file mode 100644 index 7fabbeb8..00000000 --- a/spec/fixtures/192.168.11.0.conf +++ /dev/null @@ -1,5 +0,0 @@ -# File managed by Chef - -subnet 192.168.11.0 netmask 255.255.255.0 { - option subnet-mask 255.255.255.0; -} diff --git a/spec/fixtures/192.168.9.0.conf b/spec/fixtures/192.168.9.0.conf deleted file mode 100644 index a32193b5..00000000 --- a/spec/fixtures/192.168.9.0.conf +++ /dev/null @@ -1,13 +0,0 @@ -# File managed by Chef - -subnet 192.168.9.0 netmask 255.255.255.0 { -pool { - range 192.168.9.50 192.168.9.240; -} - - option routers 192.168.9.1; - option subnet-mask 255.255.255.0; - option broadcast-address 192.168.9.255; - option time-offset 10; - next-server 192.168.9.11; -} diff --git a/spec/fixtures/dhcpd.conf.default b/spec/fixtures/dhcpd.conf.default deleted file mode 100644 index 2c784fac..00000000 --- a/spec/fixtures/dhcpd.conf.default +++ /dev/null @@ -1,33 +0,0 @@ -# File managed by Chef - -# set this to store vendor strings. -set vendor-string = option vendor-class-identifier; - -allow booting; -allow bootp; -allow unknown-clients; - -authoritative ; -ddns-domainname "local"; -ddns-update-style interim; -default-lease-time 6400; -filename "pxelinux.0"; -max-lease-time 86400; -next-server 10.0.0.2; -one-lease-per-client true; -ping-check true; -update-static-leases true; - -option domain-name "local"; -option domain-name-servers 8.8.8.8; -option host-name = binary-to-ascii (16, 8, "-", substring (hardware, 1, 6)); - - - - - -include "/etc/dhcp/classes.d/list.conf"; -include "/etc/dhcp/groups.d/list.conf"; -include "/etc/dhcp/subnets.d/list.conf"; -include "/etc/dhcp/shared_networks.d/list.conf"; -include "/etc/dhcp/hosts.d/list.conf"; diff --git a/spec/fixtures/dhcpd.conf.overrides b/spec/fixtures/dhcpd.conf.overrides deleted file mode 100644 index cbac326a..00000000 --- a/spec/fixtures/dhcpd.conf.overrides +++ /dev/null @@ -1,41 +0,0 @@ -# File managed by Chef - -# set this to store vendor strings. -set vendor-string = option vendor-class-identifier; - -allow booting; -allow bootp; -allow unknown-clients; - -authoritative ; -ddns-domainname "local"; -ddns-update-style interim; -default-lease-time 6400; -filename "pxelinux.0"; -max-lease-time 86400; -next-server 10.0.0.2; -one-lease-per-client true; -ping-check true; -update-static-leases true; - -option domain-name "local"; -option domain-name-servers 8.8.8.8; -option host-name = binary-to-ascii (16, 8, "-", substring (hardware, 1, 6)); - -on commit { - set clip = binary-to-ascii(10, 8, ".", leased-address);; - set clhw = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));; - execute("/usr/local/sbin/dhcpevent", "commit", clip, clhw, host-decl-name);; -} - - - - -include "/etc/dhcp/classes.d/list.conf"; -include "/etc/dhcp/groups.d/list.conf"; -include "/etc/dhcp/subnets.d/list.conf"; -include "/etc/dhcp/shared_networks.d/list.conf"; -include "/etc/dhcp/hosts.d/list.conf"; - -include "/etc/dhcp/my_conf.conf"; -include "/tmp/bad.conf"; diff --git a/spec/fixtures/mysharednet.conf b/spec/fixtures/mysharednet.conf deleted file mode 100644 index 3774f001..00000000 --- a/spec/fixtures/mysharednet.conf +++ /dev/null @@ -1,19 +0,0 @@ -# File managed by Chef - -shared-network mysharednet { - subnet 192.168.10.0 netmask 255.255.255.0 { -pool { - range 192.168.10.50 192.168.10.240; -} - - option routers 192.168.10.1; - option subnet-mask 255.255.255.0; - option broadcast-address 192.168.10.255; - next-server 192.168.10.11; -} - - subnet 10.0.2.0 netmask 255.255.255.0 { - option subnet-mask 255.255.255.0; -} - -} diff --git a/spec/providers/dhcp_class_spec.rb b/spec/providers/dhcp_class_spec.rb deleted file mode 100644 index f6a74f32..00000000 --- a/spec/providers/dhcp_class_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'spec_helper' - -describe 'test::dhcp_class' do - cached(:chef_run) do - ChefSpec::ServerRunner.new(platform: 'centos', version: '6.8', step_into: ['dhcp_class']).converge(described_recipe) - end - - let(:list_conf_contents) do - '# -# file managed by chef -# Host entry includes -# -include "/etc/dhcp/classes.d/BlankClass.conf"; -include "/etc/dhcp/classes.d/RegisteredHosts.conf";' - end - - it 'creates classes.d directory' do - expect(chef_run).to create_directory '/etc/dhcp/classes.d' - end - - it 'creates list.conf to include other subconfig files' do - expect(chef_run).to create_template '/etc/dhcp/classes.d/list.conf' - expect(chef_run).to render_file('/etc/dhcp/classes.d/list.conf').with_content(list_conf_contents) - end - - it 'generates a blank class config' do - expect(chef_run).to create_template '/etc/dhcp/classes.d/BlankClass.conf' - expect(chef_run).to render_file('/etc/dhcp/classes.d/BlankClass.conf').with_content(File.read(File.join(File.dirname(__FILE__), 'fixtures', 'dhcp_class_blank.conf'))) - end - - it 'generates class config with subclasses' do - expect(chef_run).to create_template '/etc/dhcp/classes.d/RegisteredHosts.conf' - expect(chef_run).to render_file('/etc/dhcp/classes.d/RegisteredHosts.conf').with_content(File.read(File.join(File.dirname(__FILE__), 'fixtures', 'dhcp_class_registered_hosts.conf'))) - end -end diff --git a/spec/providers/dhcp_shared_network_spec.rb b/spec/providers/dhcp_shared_network_spec.rb deleted file mode 100644 index 3ec3a71e..00000000 --- a/spec/providers/dhcp_shared_network_spec.rb +++ /dev/null @@ -1,17 +0,0 @@ -require 'spec_helper' - -describe 'test::dhcp_shared_network' do - cached(:chef_run) do - ChefSpec::ServerRunner.new(platform: 'centos', version: '6.8', step_into: ['dhcp_shared_network']).converge(described_recipe) - end - - it 'generates a shared network with a single network' do - expect(chef_run).to create_template '/etc/dhcp/shared_networks.d/single.conf' - expect(chef_run).to render_file('/etc/dhcp/shared_networks.d/single.conf').with_content(File.read(File.join(File.dirname(__FILE__), 'fixtures', 'dhcp_shared_network.single'))) - end - - it 'generates a shared network with multiple networks' do - expect(chef_run).to create_template '/etc/dhcp/shared_networks.d/multiple.conf' - expect(chef_run).to render_file('/etc/dhcp/shared_networks.d/multiple.conf').with_content(File.read(File.join(File.dirname(__FILE__), 'fixtures', 'dhcp_shared_network.multiple'))) - end -end diff --git a/spec/providers/dhcp_subnet_spec.rb b/spec/providers/dhcp_subnet_spec.rb deleted file mode 100644 index 6b8b5b03..00000000 --- a/spec/providers/dhcp_subnet_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'spec_helper' - -describe 'test::dhcp_subnet' do - cached(:chef_run) do - ChefSpec::ServerRunner.new(platform: 'centos', version: '6.8', step_into: ['dhcp_subnet']).converge(described_recipe) - end - - it 'generates a default config' do - expect(chef_run).to create_template '/etc/dhcp/subnets.d/10.0.2.0.conf' - expect(chef_run).to render_file('/etc/dhcp/subnets.d/10.0.2.0.conf').with_content(File.read(File.join(File.dirname(__FILE__), 'fixtures', 'dhcp_subnet.default'))) - end - - it 'generates a basic subnet config' do - expect(chef_run).to create_template '/etc/dhcp/subnets.d/basic.conf' - expect(chef_run).to render_file('/etc/dhcp/subnets.d/basic.conf').with_content(File.read(File.join(File.dirname(__FILE__), 'fixtures', 'dhcp_subnet.basic'))) - end - - it 'generates an overriden config' do - expect(chef_run).to create_template '/etc/dhcp_override/subnets.d/overrides.conf' - expect(chef_run).to render_file('/etc/dhcp_override/subnets.d/overrides.conf').with_content(File.read(File.join(File.dirname(__FILE__), 'fixtures', 'dhcp_subnet.overrides'))) - end -end diff --git a/spec/providers/fixtures/dhcp_class_blank.conf b/spec/providers/fixtures/dhcp_class_blank.conf deleted file mode 100644 index 512b0ec0..00000000 --- a/spec/providers/fixtures/dhcp_class_blank.conf +++ /dev/null @@ -1,3 +0,0 @@ -class "BlankClass" { - match hardware; -} diff --git a/spec/providers/fixtures/dhcp_class_registered_hosts.conf b/spec/providers/fixtures/dhcp_class_registered_hosts.conf deleted file mode 100644 index 3d704854..00000000 --- a/spec/providers/fixtures/dhcp_class_registered_hosts.conf +++ /dev/null @@ -1,5 +0,0 @@ -class "RegisteredHosts" { - match hardware; -} -subclass "RegisteredHosts" 1:10:bf:48:42:55:01; -subclass "RegisteredHosts" 1:10:bf:48:42:55:02; diff --git a/spec/providers/fixtures/dhcp_shared_network.multiple b/spec/providers/fixtures/dhcp_shared_network.multiple deleted file mode 100644 index b0a97709..00000000 --- a/spec/providers/fixtures/dhcp_shared_network.multiple +++ /dev/null @@ -1,25 +0,0 @@ -# File managed by Chef - -shared-network multiple { - subnet 192.168.2.0 netmask 255.255.255.0 { -pool { - range 192.168.2.20 192.168.2.30; -} - - option routers 192.168.2.1; - option subnet-mask 255.255.255.0; - option broadcast-address 192.168.2.255; -} - - subnet 192.168.3.0 netmask 255.255.255.0 { -pool { - range 192.168.3.20 192.168.3.30; - range 192.168.3.40 192.168.3.50; -} - - option routers 192.168.3.1; - option subnet-mask 255.255.255.0; - option broadcast-address 192.168.3.255; -} - -} diff --git a/spec/providers/fixtures/dhcp_shared_network.single b/spec/providers/fixtures/dhcp_shared_network.single deleted file mode 100644 index c3390ad0..00000000 --- a/spec/providers/fixtures/dhcp_shared_network.single +++ /dev/null @@ -1,14 +0,0 @@ -# File managed by Chef - -shared-network single { - subnet 192.168.1.0 netmask 255.255.255.0 { -pool { - range 192.168.1.20 192.168.1.30; -} - - option routers 192.168.1.1; - option subnet-mask 255.255.255.0; - option broadcast-address 192.168.1.255; -} - -} diff --git a/spec/providers/fixtures/dhcp_subnet.basic b/spec/providers/fixtures/dhcp_subnet.basic deleted file mode 100644 index 9e3eed41..00000000 --- a/spec/providers/fixtures/dhcp_subnet.basic +++ /dev/null @@ -1,12 +0,0 @@ -# File managed by Chef - -subnet 192.168.0.0 netmask 255.255.255.0 { -pool { - range 192.168.0.100 192.168.0.200; -} - - option routers 192.168.0.1; - option subnet-mask 255.255.255.0; - option broadcast-address 192.168.0.255; - option time-offset 10; -} diff --git a/spec/providers/fixtures/dhcp_subnet.default b/spec/providers/fixtures/dhcp_subnet.default deleted file mode 100644 index b5634793..00000000 --- a/spec/providers/fixtures/dhcp_subnet.default +++ /dev/null @@ -1,5 +0,0 @@ -# File managed by Chef - -subnet 10.0.2.0 netmask 255.255.254.0 { - option subnet-mask 255.255.254.0; -} diff --git a/spec/providers/fixtures/dhcp_subnet.overrides b/spec/providers/fixtures/dhcp_subnet.overrides deleted file mode 100644 index aa963acc..00000000 --- a/spec/providers/fixtures/dhcp_subnet.overrides +++ /dev/null @@ -1,35 +0,0 @@ -# File managed by Chef - -subnet 192.168.0.0 netmask 255.255.255.0 { -pool { - failover peer "local"; - range 192.168.0.100 192.168.0.200; - deny members of "RegisteredHosts"; - allow members of "UnregisteredHosts"; - allow members of "OtherHosts"; -} - - ddns-domainname "test.com"; - option routers 192.168.0.1; - option subnet-mask 255.255.255.0; - option broadcast-address 192.168.0.255; - option time-offset 10; - next-server 192.168.0.3; - - if exists user-class and option user-class = "iPXE" { - filename "bootstrap.ipxe"; - } else { - filename "undionly.kpxe"; - } - - key test_key { - algorithm test_algo; - secret "test_secret"; - }; - - zone test { - primary test_pri; - key test_key; - } - -} diff --git a/spec/server_spec.rb b/spec/server_spec.rb deleted file mode 100644 index 9761e64f..00000000 --- a/spec/server_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'spec_helper' - -describe 'dhcp::server' do - let(:chef_run) do - ChefSpec::ServerRunner.new do |node| - node.default['chef_environment'] = 'production' - end.converge(described_recipe) - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 04a26677..773d5579 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,15 +1,8 @@ require 'chefspec' require 'chefspec/berkshelf' -require 'json' -require 'pry' RSpec.configure do |config| - config.platform = 'ubuntu' - config.version = '14.04' - config.log_level = :error -end - -def parse_data_bag(path) - data_bags_path = File.expand_path(File.join(File.dirname(__FILE__), '../test/integration/data_bags')) - JSON.parse(File.read("#{data_bags_path}/#{path}.json")) + config.color = true # Use color in STDOUT + config.formatter = :documentation # Use the specified formatter + config.log_level = :error # Avoid deprecation notice SPAM end diff --git a/spec/unit/recipes/class_spec.rb b/spec/unit/recipes/class_spec.rb new file mode 100644 index 00000000..6c7a1b40 --- /dev/null +++ b/spec/unit/recipes/class_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe 'dhcp_class' do + step_into :dhcp_class + platform 'centos' + + context 'create a dhcpd class with no subclass and verify config is created properly' do + recipe do + dhcp_class 'BlankClass' do + match 'hardware' + end + end + + it 'Creates the class configuration file correctly' do + is_expected.to render_file('/etc/dhcp/dhcpd.d/classes.d/BlankClass.conf') + .with_content(/class "BlankClass" {/) + .with_content(/ match hardware;/) + end + end + + context 'create a dhcpd class with subclass and verify config is created properly' do + recipe do + dhcp_class 'RegisteredHosts' do + match 'hardware' + subclass [ + '1:10:bf:48:42:55:01', + '1:10:bf:48:42:55:02', + ] + end + end + + it 'Creates the class configuration file correctly' do + is_expected.to render_file('/etc/dhcp/dhcpd.d/classes.d/RegisteredHosts.conf') + .with_content(/class "RegisteredHosts" {/) + .with_content(/ match hardware;/) + .with_content(/subclass "RegisteredHosts" 1:10:bf:48:42:55:01;/) + end + end +end diff --git a/spec/unit/recipes/config_spec.rb b/spec/unit/recipes/config_spec.rb new file mode 100644 index 00000000..b8d916b8 --- /dev/null +++ b/spec/unit/recipes/config_spec.rb @@ -0,0 +1,77 @@ +require 'spec_helper' + +describe 'dhcp_config' do + step_into :dhcp_config + platform 'centos' + + context 'create a dhcpd config and verify config is created properly' do + recipe do + dhcp_config '/etc/dhcp/dhcpd.conf' do + allow %w(booting bootp unknown-clients) + parameters( + 'default-lease-time' => 7200, + 'ddns-update-style' => 'interim', + 'max-lease-time' => 86400, + 'update-static-leases' => true, + 'one-lease-per-client' => true, + 'authoritative' => '', + 'ping-check' => true + ) + options( + 'domain-name' => '"test.domain.local"', + 'domain-name-servers' => '8.8.8.8', + 'host-name' => ' = binary-to-ascii (16, 8, "-", substring (hardware, 1, 6))' + ) + hooks( + 'commit' => ['use-host-decl-names on'], + 'release' => ['use-host-decl-names on'] + ) + include_files [ + '/etc/dhcp/extra1.conf', + '/etc/dhcp/extra2.conf', + '/etc/dhcp_override/list.conf', + ] + action :create + end + end + + it 'Creates the main configuration file' do + is_expected.to render_file('/etc/dhcp/dhcpd.conf') + .with_content(/authoritative/) + .with_content(/default-lease-time 7200/) + .with_content(/option domain-name-servers 8.8.8.8;/) + .with_content(%r{include "/etc/dhcp/dhcpd.d/classes.d/list.conf";}) + end + end + + context 'create a dhcpd6 config and verify config is created properly' do + recipe do + dhcp_config '/etc/dhcp/dhcpd6.conf' do + ip_version :ipv6 + deny %w(duplicates) + parameters( + 'default-lease-time' => 7200, + 'ddns-updates' => 'on', + 'ddns-update-style' => 'interim', + 'max-lease-time' => 86400, + 'update-static-leases' => true, + 'one-lease-per-client' => 'on', + 'authoritative' => '', + 'ping-check' => true + ) + options( + 'dhcp6.name-servers' => '2001:4860:4860::8888, 2001:4860:4860::8844' + ) + action :create + end + end + + it 'Creates the main configuration file' do + is_expected.to render_file('/etc/dhcp/dhcpd6.conf') + .with_content(/authoritative/) + .with_content(/default-lease-time 7200/) + .with_content(/option dhcp6.name-servers 2001:4860:4860::8888, 2001:4860:4860::8844;/) + .with_content(%r{include "/etc/dhcp/dhcpd6.d/classes.d/list.conf";}) + end + end +end diff --git a/spec/unit/recipes/default_spec.rb b/spec/unit/recipes/default_spec.rb new file mode 100644 index 00000000..fa23370c --- /dev/null +++ b/spec/unit/recipes/default_spec.rb @@ -0,0 +1,9 @@ +require 'spec_helper' + +describe 'Default recipe on CentOS 7' do + let(:runner) { ChefSpec::ServerRunner.new(platform: 'centos', version: '7', step_into: ['dhcp_package']) } + + it 'converges successfully' do + expect { :chef_run }.to_not raise_error + end +end diff --git a/spec/unit/recipes/group_spec.rb b/spec/unit/recipes/group_spec.rb new file mode 100644 index 00000000..254e6abc --- /dev/null +++ b/spec/unit/recipes/group_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe 'dhcp_group' do + step_into :dhcp_group, :dhcp_host + platform 'centos' + + context 'create a dhcpd group and verify config is created properly' do + recipe do + dhcp_group 'ip-phones' do + options( + 'tftp-server-name' => '"192.0.2.10"' + ) + hosts( + 'SEP010101010101' => { + 'identifier' => 'hardware ethernet 01:01:01:01:01:01', + } + ) + end + end + + it 'Creates the group configuration file correctly' do + is_expected.to render_file('/etc/dhcp/dhcpd.d/groups.d/ip-phones.conf') + .with_content(/tftp-server-name "192.0.2.10";/) + .with_content(%r{include "/etc/dhcp/dhcpd.d/groups.d/ip-phones_grouphost_SEP010101010101.conf";}) + end + + it 'Creates the nested host configuration file correctly' do + is_expected.to render_file('/etc/dhcp/dhcpd.d/groups.d/ip-phones_grouphost_SEP010101010101.conf') + .with_content(/host ip-phones_grouphost_SEP010101010101 {/) + .with_content(/hardware ethernet 01:01:01:01:01:01;/) + end + end +end diff --git a/spec/unit/recipes/host_spec.rb b/spec/unit/recipes/host_spec.rb new file mode 100644 index 00000000..b184613e --- /dev/null +++ b/spec/unit/recipes/host_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe 'dhcp_host' do + step_into :dhcp_host + platform 'centos' + + context 'create a dhcpd host and verify config is created properly' do + recipe do + dhcp_host 'Test-IPv4-Host' do + options 'host-name' => 'test-ipv4-host' + identifier 'hardware ethernet 00:53:00:00:00:01' + address '192.168.0.10' + end + end + + it 'Creates the class configuration file correctly' do + is_expected.to render_file('/etc/dhcp/dhcpd.d/hosts.d/Test-IPv4-Host.conf') + .with_content(/host Test-IPv4-Host {/) + .with_content(/hardware ethernet 00:53:00:00:00:01;/) + .with_content(/option host-name test-ipv4-host;/) + end + end + + context 'create a dhcpd6 host and verify config is created properly' do + recipe do + dhcp_host 'Test-IPv6-Host' do + ip_version :ipv6 + options 'host-name' => 'test-ipv6-host' + identifier 'host-identifier option dhcp6.client-id 00:53:00:00:00:01:a4:65:b7:c8' + address '2001:db8:1:1:0:0:1:10' + end + end + + it 'Creates the class configuration file correctly' do + is_expected.to render_file('/etc/dhcp/dhcpd6.d/hosts.d/Test-IPv6-Host.conf') + .with_content(/host Test-IPv6-Host {/) + .with_content(/host-identifier option dhcp6.client-id 00:53:00:00:00:01:a4:65:b7:c8;/) + .with_content(/option host-name test-ipv6-host;/) + end + end +end diff --git a/spec/unit/recipes/shared_network_spec.rb b/spec/unit/recipes/shared_network_spec.rb new file mode 100644 index 00000000..b2803a62 --- /dev/null +++ b/spec/unit/recipes/shared_network_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +describe 'dhcp_shared_network' do + step_into :dhcp_shared_network, :dhcp_subnet + platform 'centos' + + context 'create a dhcpd single shared network and verify config is created properly' do + recipe do + dhcp_shared_network 'single' do + subnets( + '192.168.1.0' => { + 'subnet' => '192.168.1.0', + 'netmask' => '255.255.255.0', + 'options' => { + 'broadcast-address' => '192.168.1.255', + 'routers' => '192.168.1.1', + }, + 'pools' => { + 'range' => '192.168.1.20 192.168.1.30', + }, + } + ) + end + end + + it 'Creates the shared network configuration file correctly' do + is_expected.to render_file('/etc/dhcp/dhcpd.d/shared_networks.d/single.conf') + .with_content(%r{include "/etc/dhcp/dhcpd.d/shared_networks.d/single_sharedsubnet_192.168.1.0.conf";}) + end + + it 'Creates the nested subnet configuration file correctly' do + is_expected.to render_file('/etc/dhcp/dhcpd.d/shared_networks.d/single_sharedsubnet_192.168.1.0.conf') + .with_content(/subnet 192.168.1.0 netmask 255.255.255.0 {/) + .with_content(/range 192.168.1.20 192.168.1.30;/) + end + end + + context 'create a dhcpd multiple shared network and verify config is created properly' do + recipe do + dhcp_shared_network 'multiple' do + subnets( + '192.168.2.0' => { + 'subnet' => '192.168.2.0', + 'netmask' => '255.255.255.0', + 'options' => { + 'broadcast-address' => '192.168.2.255', + 'routers' => '192.168.2.1', + }, + 'pools' => { + 'range' => '192.168.2.20 192.168.2.30', + }, + }, + '192.168.3.0' => { + 'subnet' => '192.168.3.0', + 'netmask' => '255.255.255.0', + 'options' => { + 'broadcast-address' => '192.168.3.255', + 'routers' => '192.168.3.1', + }, + 'pools' => { + 'range' => [ + '192.168.3.20 192.168.3.30', + '192.168.3.40 192.168.3.50', + ], + }, + } + ) + end + end + + it 'Creates the shared network configuration file correctly' do + is_expected.to render_file('/etc/dhcp/dhcpd.d/shared_networks.d/multiple.conf') + .with_content(%r{include "/etc/dhcp/dhcpd.d/shared_networks.d/multiple_sharedsubnet_192.168.2.0.conf";}) + .with_content(%r{include "/etc/dhcp/dhcpd.d/shared_networks.d/multiple_sharedsubnet_192.168.3.0.conf";}) + end + + it 'Creates the nested subnet configuration files correctly' do + is_expected.to render_file('/etc/dhcp/dhcpd.d/shared_networks.d/multiple_sharedsubnet_192.168.2.0.conf') + .with_content(/subnet 192.168.2.0 netmask 255.255.255.0 {/) + .with_content(/range 192.168.2.20 192.168.2.30;/) + is_expected.to render_file('/etc/dhcp/dhcpd.d/shared_networks.d/multiple_sharedsubnet_192.168.3.0.conf') + .with_content(/subnet 192.168.3.0 netmask 255.255.255.0 {/) + .with_content(/option broadcast-address 192.168.3.255;/) + .with_content(/range 192.168.3.40 192.168.3.50;/) + end + end +end diff --git a/spec/unit/recipes/subnet_spec.rb b/spec/unit/recipes/subnet_spec.rb new file mode 100644 index 00000000..999c7246 --- /dev/null +++ b/spec/unit/recipes/subnet_spec.rb @@ -0,0 +1,108 @@ +require 'spec_helper' + +describe 'dhcp_subnet' do + step_into :dhcp_subnet + platform 'centos' + + context 'create a dhcpd subnet for listening only and verify config is created properly' do + recipe do + dhcp_subnet '192.168.9.0' do + comment 'Listen Subnet Declaration' + subnet '192.168.9.0' + netmask '255.255.255.0' + end + end + + it 'Creates the subnet configuration file correctly' do + is_expected.to render_file('/etc/dhcp/dhcpd.d/subnets.d/192.168.9.0.conf') + .with_content(/# 192.168.9.0 - Listen Subnet Declaration/) + .with_content(/subnet 192.168.9.0 netmask 255.255.255.0 {\n}/) + end + end + + context 'create a dhcpd subnet with options as hash and verify config is created properly' do + recipe do + dhcp_subnet 'overrides' do + comment 'Overrides Subnet Declaration' + subnet '192.168.1.0' + netmask '255.255.255.0' + options( + 'routers' => '192.168.1.1', + 'time-offset' => 10, + 'broadcast-address' => '192.168.0.255' + ) + pools( + 'peer' => '192.168.0.2', + 'range' => '192.168.1.100 192.168.1.200', + 'deny' => 'members of "RegisteredHosts"', + 'allow' => ['members of "UnregisteredHosts"', 'members of "OtherHosts"'] + ) + parameters( + 'ddns-domainname' => '"test.com"', + 'next-server' => '192.168.0.3' + ) + evals [ 'if exists user-class and option user-class = "iPXE" { + filename "bootstrap.ipxe"; + } else { + filename "undionly.kpxe"; + }' ] + key 'name' => 'test_key', 'algorithm' => 'hmac-sha256', 'secret' => 'c7nBOcB2rbJh7lYCI65/PGrS6QdlLMCPe2xunZ4dij8=' + zones 'test' => { 'primary' => 'test_pri', 'key' => 'test_key' } + conf_dir '/etc/dhcp_override' + end + end + + it 'Creates the subnet configuration file correctly' do + is_expected.to render_file('/etc/dhcp_override/overrides.conf') + .with_content(/subnet 192.168.1.0 netmask 255.255.255.0 {/) + .with_content(/option routers 192.168.1.1;/) + .with_content(/if exists user-class and option user-class = "iPXE" {/) + .with_content(/key test_key {/) + .with_content(/zone test {/) + .with_content(/deny members of "RegisteredHosts";/) + end + end + + context 'create a dhcpd subnet with parameters as array and verify config is created properly' do + recipe do + dhcp_subnet 'overrides' do + comment 'Overrides Subnet Declaration' + subnet '192.168.1.0' + netmask '255.255.255.0' + options( + 'routers' => '192.168.1.1', + 'time-offset' => 10, + 'broadcast-address' => '192.168.0.255' + ) + pools( + 'peer' => '192.168.0.2', + 'range' => '192.168.1.100 192.168.1.200', + 'deny' => 'members of "RegisteredHosts"', + 'allow' => ['members of "UnregisteredHosts"', 'members of "OtherHosts"'] + ) + parameters [ + 'ddns-domainname "test.com"', + 'next-server 192.168.0.3', + ] + evals [ 'if exists user-class and option user-class = "iPXE" { + filename "bootstrap.ipxe"; + } else { + filename "undionly.kpxe"; + }' ] + key 'name' => 'test_key', 'algorithm' => 'hmac-sha256', 'secret' => 'c7nBOcB2rbJh7lYCI65/PGrS6QdlLMCPe2xunZ4dij8=' + zones 'test' => { 'primary' => 'test_pri', 'key' => 'test_key' } + conf_dir '/etc/dhcp_override' + end + end + + it 'Creates the subnet configuration file correctly' do + is_expected.to render_file('/etc/dhcp_override/overrides.conf') + .with_content(/subnet 192.168.1.0 netmask 255.255.255.0 {/) + .with_content(/option routers 192.168.1.1;/) + .with_content(/if exists user-class and option user-class = "iPXE" {/) + .with_content(/key test_key {/) + .with_content(/zone test {/) + .with_content(/deny members of "RegisteredHosts";/) + end + end +end diff --git a/templates/default/class.conf.erb b/templates/default/class.conf.erb index 7c248136..fe6bf2f3 100644 --- a/templates/default/class.conf.erb +++ b/templates/default/class.conf.erb @@ -1,9 +1,28 @@ -# File managed by Chef +# +# Generated by Chef for <%= node['fqdn'] %> +# Do NOT modify this file by hand, changes will be overwritten. +# +# <%= @name -%><% if @comment -%> - <%= @comment -%><% end -%> class "<%= @name %>" { match <%= @match %>; + <% unless nil_or_empty?(@parameters) -%> + + # Parameters + <% property_collection_sorted(@parameters).each do |parameter, value| -%> + <%= parameter %><% unless nil_or_empty?(value) -%> <%= value %><% end -%>; + <% end -%> + <% end -%> + <% unless nil_or_empty?(@options) -%> + + # Options + <% property_collection_sorted(@options).each do |option, value| -%> + option <%= option %> <%= value %>; + <% end -%> + <% end -%> } -<% unless @subclasses.nil? || @subclasses.empty? -%> +<% unless nil_or_empty?(@subclasses) -%> + <% @subclasses.each do |value| -%> subclass "<%= @name %>" <%= value %>; <% end -%> diff --git a/templates/default/dhcp-server.erb b/templates/default/dhcp-server.erb deleted file mode 100644 index 2caf3e8b..00000000 --- a/templates/default/dhcp-server.erb +++ /dev/null @@ -1,13 +0,0 @@ -# Defaults for dhcp initscript -# sourced by /etc/init.d/dhcp - -# -# This is a POSIX shell fragment -# - -# On what interfaces should the DHCP server (dhcpd) serve DHCP requests? -# Separate multiple interfaces with spaces, e.g. "eth0 eth1". -<% unless @interfaces.empty? -%> -INTERFACES="<%= @interfaces.map {|i| i }.join(" ") %>" -<% end -%> - diff --git a/templates/default/dhcpd-env.erb b/templates/default/dhcpd-env.erb new file mode 100644 index 00000000..4f4a45f9 --- /dev/null +++ b/templates/default/dhcpd-env.erb @@ -0,0 +1,18 @@ +# +# Generated by Chef for <%= node['fqdn'] %> +# Do NOT modify this file by hand, changes will be overwritten. +# + +<% if nil_or_empty?(@lines) -%> +# WARNING: This file is NOT used anymore. + +# If you are here to restrict what interfaces should dhcpd listen on, +# be aware that dhcpd listens *only* on interfaces for which it finds subnet +# declaration in dhcpd.conf. It means that explicitly enumerating interfaces +# also on command line should not be required in most cases. +<% else -%> +# User specified extra lines +<% property_array(@lines).each do |line| -%> +<%= line %> +<% end -%> +<% end -%> diff --git a/templates/default/dhcpd.conf.erb b/templates/default/dhcpd.conf.erb index 55fe6df9..19f4f9a0 100644 --- a/templates/default/dhcpd.conf.erb +++ b/templates/default/dhcpd.conf.erb @@ -1,21 +1,64 @@ -# File managed by Chef +# +# Generated by Chef for <%= node['fqdn'] %> +# Do NOT modify this file by hand, changes will be overwritten. +# -# set this to store vendor strings. +# The following line populates the lease file with the Vendor Class Identifier that the client sends. set vendor-string = option vendor-class-identifier; +<% unless nil_or_empty?(@failover) -%> -<% @allows.each do |allow| -%> +# DHCP Failover +include "/etc/dhcp/dhcpd.failover.conf"; +<% end -%> +<% unless nil_or_empty?(@parameters) -%> + +# Global Parameters +<% property_collection_sorted(@parameters).each do |parameter, value| -%> +<%= parameter %><% unless nil_or_empty?(value) -%> <%= value %><% end -%>; +<% end -%> +<% end -%> +<% unless nil_or_empty?(@options) -%> + +# Global Options +<% property_collection(@options).each do |option, value| -%> +option <%= option %> <%= value %>; +<% end -%> +<% end -%> +<% unless nil_or_empty?(@evals) -%> + +# Evals +<% @evals.each do |eval| -%> +<%= eval %> +<% end -%> +<% end -%> +<% unless nil_or_empty?(@allow) && nil_or_empty?(@deny) && nil_or_empty?(@ignore) -%> + +# Request Control +<% unless nil_or_empty?(@allow) -%> + +# Allow +<% @allow.each do |allow| -%> allow <%= allow %>; <% end -%> +<% end -%> +<% unless nil_or_empty?(@deny) -%> -<% @parameters.sort.each do |key, value| -%> -<%= key %> <%= value %>; +# Deny +<% @deny.each do |deny| -%> +deny <%= deny %>; +<% end -%> <% end -%> +<% unless nil_or_empty?(@ignore) -%> -<% @options.sort.each do |key, value| -%> -option <%= key %> <%= value %>; +# Ignore +<% @ignore.each do |ignore| -%> +ignore <%= ignore %>; +<% end -%> <% end -%> +<% end -%> +<% unless nil_or_empty?(@hooks) -%> -<% unless @hooks.nil? || @hooks.empty? -%> +# Events <% @hooks.each do |key, value| -%> on <%= key %> { <% value.each do |v| -%> @@ -24,49 +67,44 @@ on <%= key %> { } <% end -%> <% end -%> +<% unless nil_or_empty?(@keys) -%> -<% unless @keys.nil? || @keys.empty? -%> - <% @keys.each do |key, data| -%> +# TSIG Keys +<% @keys.each do |key, data| -%> key "<%= key %>" { algorithm <%= data['algorithm'] %>; secret "<%= data['secret'] %>"; }; - <%end -%> <%end -%> +<%end -%> +<% unless nil_or_empty?(@zones) -%> -<% unless @masters.nil? || @masters.empty? -%> -<% @masters.each do |zone, data| -%> +# Dynamic DNS Zones +<% @zones.each do |zone, data| -%> zone <%= zone %>. { - primary <%= data["master"] %>; - key "<%= data["key"] %>"; + primary <%= data["primary"] %>;<% if data.key?('key') -%> + key "<%= data["key"] =%>";<% end -%> } - <% end -%> +<% end -%> <% end -%> -<% if @failover %> -failover peer "<%= node['domain'] %>" { - <%= @role %>; - address <%= @my_ip %>; - port <%= node['dhcp']['failover_params']['port'] %>; - peer address <%= @peer_ip %>; - peer port <%= node['dhcp']['failover_params']['peer_port'] %>; - max-response-delay 60; - max-unacked-updates 10; - mclt <%= node['dhcp']['failover_params']['mclt'] %>; - auto-partner-down <%= node['dhcp']['failover_params']['auto_partner_down'] %> ; - <% if @role =~ /primary/i -%> - split 128; - load balance max seconds 3; - <% end -%> -} -<% end %> - -include "<%= node['dhcp']['dir'] %>/classes.d/list.conf"; -include "<%= node['dhcp']['dir'] %>/groups.d/list.conf"; -include "<%= node['dhcp']['dir'] %>/subnets.d/list.conf"; -include "<%= node['dhcp']['dir'] %>/shared_networks.d/list.conf"; -include "<%= node['dhcp']['dir'] %>/hosts.d/list.conf"; +# Includes +include "<%= @includes_dir %>/classes.d/list.conf"; +include "<%= @includes_dir %>/groups.d/list.conf"; +include "<%= @includes_dir %>/subnets.d/list.conf"; +include "<%= @includes_dir %>/shared_networks.d/list.conf"; +include "<%= @includes_dir %>/hosts.d/list.conf"; +<% unless nil_or_empty?(@include_files) -%> -<% node['dhcp']['extra_files'].each do |file| -%> +# Custom Includes +<% @include_files.each do |file| -%> include "<%= file %>"; <% end -%> +<% end -%> +<% unless nil_or_empty?(@extra_lines) -%> + +# User specified extra lines +<% property_array(@extra_lines).each do |line| -%> +<%= line %> +<% end -%> +<% end -%> diff --git a/templates/default/dhcpd.failover.conf.erb b/templates/default/dhcpd.failover.conf.erb new file mode 100644 index 00000000..b1797f04 --- /dev/null +++ b/templates/default/dhcpd.failover.conf.erb @@ -0,0 +1,11 @@ +# +# Generated by Chef for <%= node['fqdn'] %> +# Do NOT modify this file by hand, changes will be overwritten. +# + +failover peer "<%= @failover['peer'] %>" { + <%= @failover['role'] -%>; + <% property_collection_sorted(@failover['parameters']).each do |parameter, value| -%> + <%= parameter %><% unless nil_or_empty?(value) -%> <%= value %><% end -%>; + <% end -%> +} diff --git a/templates/default/dhcpd.leases-hack.erb b/templates/default/dhcpd.leases-hack.erb deleted file mode 100644 index 0b255187..00000000 --- a/templates/default/dhcpd.leases-hack.erb +++ /dev/null @@ -1,4 +0,0 @@ -failover peer "<%= node['domain'] %>" state { - my state partner-down; - partner state unknown-state; -} diff --git a/templates/default/group.conf.erb b/templates/default/group.conf.erb index 027915d0..244a8b87 100644 --- a/templates/default/group.conf.erb +++ b/templates/default/group.conf.erb @@ -1,30 +1,36 @@ -# File managed by Chef +# +# Generated by Chef for <%= node['fqdn'] %> +# Do NOT modify this file by hand, changes will be overwritten. +# -#<%= @name %> -group { -<% @parameters.sort.each do |parameter| -%> - <%= parameter %>; -<% end -%> -<% @evals.sort.each do |eval| -%> -<%= eval %> +# <%= @name -%><% if @comment -%> - <%= @comment -%><% end -%> +group { +<% unless nil_or_empty?(@parameters) -%> + + # Parameters + <% property_collection_sorted(@parameters).each do |parameter, value| -%> + <%= parameter %><% unless nil_or_empty?(value) -%> <%= value %><% end -%>; + <% end -%> <% end -%> +<% unless nil_or_empty?(@options) -%> -<% @hosts.sort.each do |host,data| -%> - host <%= host %> { -<% if data.has_key?("parameters") -%> - <% data['parameters'].sort.each do |param| -%> - <%= param %>; + # Options + <% property_collection_sorted(@options).each do |option, value| -%> + option <%= option %> <%= value %>; <% end -%> <% end -%> -<% if data.has_key?("options") -%> - <% data['options'].sort.each do |option| -%> - option <%= option %>; +<% unless nil_or_empty?(@evals) -%> + + # Evals + <% @evals.each do |eval| -%> + <%= eval %> <% end -%> <% end -%> - hardware ethernet <%= data["mac"] %>; - <% if data.has_key?("ip") -%> - fixed-address <%= data["ip"] %>; - <% end -%> - } +<% unless nil_or_empty?(@hosts) -%> + + # Hosts +<% @hosts.sort.each do |host| -%> + include "<%= host %>"; +<% end -%> <% end -%> -} \ No newline at end of file +} diff --git a/templates/default/host.conf.erb b/templates/default/host.conf.erb index a851a9f5..12b27cdf 100644 --- a/templates/default/host.conf.erb +++ b/templates/default/host.conf.erb @@ -1,31 +1,28 @@ # -# File managed by Chef +# Generated by Chef for <%= node['fqdn'] %> +# Do NOT modify this file by hand, changes will be overwritten. # +# <%= @name -%><% if @comment -%> - <%= @comment -%><% end -%> host <%= @name %> { -<% if @hostname -%> - option host-name "<%= @hostname %>"; +<% if @identifier -%> + <%= @identifier %>; <% end -%> -<% if @macaddress -%> - hardware ethernet <%= @macaddress %>; +<% if @address -%> + <%= @ip_version.eql?(:ipv6) ? 'fixed-address6' : 'fixed-address' %> <%= @address %>; <% end -%> -<% if @servername -%> - server-name "<%= @servername %>"; -<% end -%> -<% if @ipaddress -%> - fixed-address <%= @ipaddress %>; -<% end -%> -<% if @filename -%> - file-name "<%= @filename %>"; -<% end -%> -<% unless @parameters.empty? -%> - <% @parameters.sort.each do |parameter| -%> - <%= parameter -%>; +<% unless nil_or_empty?(@parameters) -%> + + # Parameters + <% property_collection_sorted(@parameters).each do |parameter, value| -%> + <%= parameter %><% unless nil_or_empty?(value) -%> <%= value %><% end -%>; <% end -%> <% end -%> -<% unless @options.empty? -%> - <% @options.sort.each do |option| -%> - option <%= option -%>; +<% unless nil_or_empty?(@options) -%> + + # Options + <% property_collection_sorted(@options).each do |option, value| -%> + option <%= option -%> <%= value -%>; <% end -%> <% end -%> } diff --git a/templates/default/init_config.erb b/templates/default/init_config.erb deleted file mode 100644 index 65eee2b9..00000000 --- a/templates/default/init_config.erb +++ /dev/null @@ -1,11 +0,0 @@ -# Defaults for dhcp initscript -# sourced by /etc/init.d/dhcp -# managed by chef dhcp recipe - - -# On what interfaces should the DHCP server (dhcpd) serve DHCP requests? -# Separate multiple interfaces with spaces, e.g. "eth0 eth1". -<% unless @interfaces.empty? -%> -<%= @var.upcase %>="<%= @interfaces.collect {|i| i }.join(" ") %>" -<% end -%> - diff --git a/templates/default/list.conf.erb b/templates/default/list.conf.erb index 04b00518..a68dcab5 100644 --- a/templates/default/list.conf.erb +++ b/templates/default/list.conf.erb @@ -1,7 +1,13 @@ # -# file managed by chef -# Host entry includes +# Generated by Chef for <%= node['fqdn'] %> +# Do NOT modify this file by hand, changes will be overwritten. # + +<% if @files.empty? -%> +# No files to include +<% else -%> +# Include files <% @files.sort.each do |file| -%> include "<%= file %>"; <% end -%> +<% end -%> diff --git a/templates/default/shared_network.conf.erb b/templates/default/shared_network.conf.erb index 3b89b83c..4c1b556d 100644 --- a/templates/default/shared_network.conf.erb +++ b/templates/default/shared_network.conf.erb @@ -1,21 +1,11 @@ -# File managed by Chef +# +# Generated by Chef for <%= node['fqdn'] %> +# Do NOT modify this file by hand, changes will be overwritten. +# +# <%= @name -%><% if @comment -%> - <%= @comment -%><% end -%> shared-network <%= @name %> { - <% @subnets.each do |subnet| -%> - <%= render 'subnet.erb', :cookbook => 'dhcp', :variables => { - node: @node, - subnet: subnet.subnet, - netmask: subnet.netmask, - broadcast: subnet.broadcast, - routers: subnet.routers, - options: subnet.options, - pools: subnet.pools, - evals: subnet.evals, - key: subnet.key, - zones: subnet.zones, - ddns: subnet.ddns, - next_server: subnet.next_server - } - %> + <% @subnets.sort.each do |subnet| -%> + include "<%= subnet %>"; <% end -%> } diff --git a/templates/default/subnet.conf.erb b/templates/default/subnet.conf.erb index 0d98a4af..acc68b4d 100644 --- a/templates/default/subnet.conf.erb +++ b/templates/default/subnet.conf.erb @@ -1,3 +1,99 @@ -# File managed by Chef +# +# Generated by Chef for <%= node['fqdn'] %> +# Do NOT modify this file by hand, changes will be overwritten. +# -<%= render 'subnet.erb', :cookbook => 'dhcp', :variables => @variables %> +# <%= @name -%><% if @comment -%> - <%= @comment -%><% end -%> +subnet <%= @subnet %> netmask <%= @netmask%> { + <% unless nil_or_empty?(@parameters) -%> + # Parameters + <% property_collection_sorted(@parameters).each do |parameter, value| -%> + <%= parameter %><% unless nil_or_empty?(value) -%> <%= value %><% end -%>; + <% end -%> +<% end -%> +<% unless nil_or_empty?(@options) -%> + + # Options + <% property_collection_sorted(@options).each do |option, value| -%> + option <%= option %> <%= value %>; + <% end -%> +<% end -%> +<% unless nil_or_empty?(@extra_lines) -%> + + # Extra Lines + <% @extra_lines.each do |line| -%> + <%= line %> + <% end -%> +<% end -%> +<% unless nil_or_empty?(@evals) -%> + + # Evals + <% @evals.each do |eval| -%> + <%= eval %> + <% end -%> +<% end -%> +<% unless nil_or_empty?(@key) -%> + + # Key + key <%= @key['name'] %> { + algorithm <%= @key['algorithm'] %>; + secret "<%= @key['secret'] %>"; + }; +<% end -%> +<% unless nil_or_empty?(@zones) -%> + + # Zones + <% @zones.each do |zone, options| -%> + zone <%= zone %> { + primary <%= options['primary'] %>; + key <%= options['key'] %>; + } + <% end -%> +<% end -%> +<% unless nil_or_empty?(@pools) -%> + + # Pools +<% property_array(@pools).each do |pool| -%> + pool { + <% if pool['failover_peer'] -%> + failover peer "<%= pool['failover_peer'] %>"; + <% end -%> + <% unless nil_or_empty?(pool['options']) -%> + + # Options + <% property_collection_sorted(pool['options']).each do |option, value| -%> + option <%= option %> <%= value %>; + <% end -%> + <% end -%> + <% unless nil_or_empty?(pool['parameters']) -%> + + # Parameters + <% property_collection_sorted(pool['parameters']).each do |parameter, value| -%> + <%= parameter %> <%= value %>; + <% end -%> + <% end -%> + <% unless nil_or_empty?(pool['range']) %> + + # Ranges + <% property_array(pool['range']).sort.each do |range| -%> + range <%= range %>; + <% end -%> + <% end -%> + <% unless nil_or_empty?(pool['deny']) -%> + + # Deny Access Control + <% property_array(pool['deny']).sort.each do |denied| -%> + deny <%= denied %>; + <% end -%> + <% end -%> + <% unless nil_or_empty?(pool['allow']) -%> + + # Allow Access Control + <% property_array(pool['allow']).sort.each do |allowed| -%> + allow <%= allowed %>; + <% end -%> + <% end -%> + } +<% end -%> +<% end -%> +} diff --git a/templates/default/subnet.erb b/templates/default/subnet.erb deleted file mode 100644 index 25ec4b31..00000000 --- a/templates/default/subnet.erb +++ /dev/null @@ -1,47 +0,0 @@ -subnet <%= @subnet %> netmask <%= @netmask%> { -<% unless @pools.nil? || @pools.empty? -%> -<% @pools.each do |pool| -%> -<%= render 'subnet_pool.erb', :cookbook => 'dhcp', :variables => { node: @node, peer: pool.peer, range: pool.range, deny: pool.deny, allow: pool.allow, extra_pool_lines: pool.extra_pool_lines } %> -<% end -%> -<% end -%> -<% if @ddns -%> - ddns-domainname "<%= @ddns %>"; -<% end -%> -<% unless @routers.empty? -%> - option routers <%= @routers.map {|i| i }.join(",") %>; -<% end -%> -<% if @netmask -%> - option subnet-mask <%= @netmask %>; -<% end -%> -<% if @broadcast -%> - option broadcast-address <%= @broadcast %>; -<% end -%> -<% unless @options.empty? -%> -<% @options.sort.each do |option| -%> - option <%= option %>; -<% end -%> -<% end -%> -<% if @next_server -%> - next-server <%= @next_server %>; -<% end -%> -<% @evals.sort.each do |eval| -%> -<%= eval %> - -<% end -%> -<% unless @key.empty? -%> - key <%= @key['name'] %> { - algorithm <%= @key['algorithm'] %>; - secret "<%= @key['secret'] %>"; - }; - -<% end -%> -<% unless @zones.empty? -%> -<% @zones.each do |zone| -%> - zone <%= zone['zone'] %> { - primary <%= zone['primary'] %>; - key <%= zone['key'] %>; - } -<% end -%> - -<% end -%> -} diff --git a/templates/default/subnet6.conf.erb b/templates/default/subnet6.conf.erb new file mode 100644 index 00000000..0f2327d4 --- /dev/null +++ b/templates/default/subnet6.conf.erb @@ -0,0 +1,83 @@ +# +# Generated by Chef for <%= node['fqdn'] %> +# Do NOT modify this file by hand, changes will be overwritten. +# + +# <%= @name -%><% if @comment -%> - <%= @comment -%><% end -%> +subnet6 <%= @subnet %>/<%= @prefix %> { +<% unless nil_or_empty?(@range) -%> + # Ranges + <% property_array(@range).each do |range| -%> + range6 <%= range %>; + <% end -%> +<% end -%> +<% unless nil_or_empty?(@parameters) -%> + + # Parameters + <% property_collection_sorted(@parameters).each do |parameter, value| -%> + <%= parameter %><% unless nil_or_empty?(value) -%> <%= value %><% end -%>; + <% end -%> +<% end -%> +<% unless nil_or_empty?(@options) -%> + + # Options + <% property_collection_sorted(@options).each do |option, value| -%> + option <%= option %> <%= value %>; + <% end -%> +<% end -%> +<% unless nil_or_empty?(@allow) -%> + + # Allow Access Control + <% property_array(@allow).each do |allowed| -%> + allow <%= allowed %>; + <% end -%> +<% end -%> +<% unless nil_or_empty?(@deny) -%> + + # Deny Access Control + <% property_array(@deny).each do |denied| -%> + deny <%= denied %>; + <% end -%> +<% end -%> +<% unless nil_or_empty?(@extra_lines) -%> + + # Extra Lines + <% @extra_lines.each do |line| -%> + <%= line %> + <% end -%> +<% end -%> +<% unless nil_or_empty?(@evals) -%> + + # Evals + <% @evals.each do |eval| -%> + <%= eval %> + <% end -%> +<% end -%> + +<% unless nil_or_empty?(@pool) -%> +<% property_array(@pool).each do |pool| -%> + pool6 { + <% unless nil_or_empty?(pool['range']) -%> + # Ranges + <% pool['range'].sort.each do |range| -%> + range6 <%= range %>; + <% end -%> + <% end -%> + <% unless nil_or_empty?(@allow) -%> + + # Allow Access Control + <% pool['allow'].sort.each do |allowed| -%> + allow <%= allowed %>; + <% end -%> + <% end -%> + <% unless nil_or_empty?(@deny) -%> + + # Deny Access Control + <% pool['deny'].sort.each do |denied| -%> + deny <%= denied %>; + <% end -%> + <% end -%> + } +<% end -%> +<% end -%> +} diff --git a/templates/default/subnet_pool.erb b/templates/default/subnet_pool.erb deleted file mode 100644 index f91a788a..00000000 --- a/templates/default/subnet_pool.erb +++ /dev/null @@ -1,21 +0,0 @@ -pool { - <% if @extra_pool_lines -%> - <% @extra_pool_lines = [@extra_pool_lines] if @extra_pool_lines.is_a?(String) -%> - <% @extra_pool_lines.each do |line| -%> - <%= line %> - <% end -%> - <% end -%> - <% if @peer -%> - failover peer "<%= node['domain'] %>"; - <% end -%> - <% @range = [@range] if @range.is_a?(String) -%> - <% @range.each do |range| -%> - range <%= range %>; - <% end -%> - <% @deny.each do |denied| -%> - deny <%= denied %>; - <% end -%> - <% @allow.each do |allowed| -%> - allow <%= allowed %>; - <% end -%> -} diff --git a/test/cookbooks/test/README.md b/test/cookbooks/test/README.md new file mode 100644 index 00000000..536353bb --- /dev/null +++ b/test/cookbooks/test/README.md @@ -0,0 +1,3 @@ +# dhcp_test + +This cookbook installs ISC DHCPd and tests whether the dhcp cookbook can configure a server. diff --git a/test/cookbooks/test/attributes/attribute_driver.rb b/test/cookbooks/test/attributes/attribute_driver.rb deleted file mode 100644 index 20bac07c..00000000 --- a/test/cookbooks/test/attributes/attribute_driver.rb +++ /dev/null @@ -1,43 +0,0 @@ -default['dhcp']['network_data']['192.168.12.0/24'] = { - 'id' => '192-168-12-0_24', - 'routers' => ['192.168.12.1'], - 'address' => '192.168.12.0', - 'netmask' => '255.255.255.0', - 'broadcast' => '192.168.12.255', - 'range' => '192.168.12.50 192.168.12.240', - 'options' => ['time-offset 10'], - 'next_server' => '192.168.12.11', -} - -default['dhcp']['shared_network_data']['mysharedattrnetwork']['subnets']['192.168.13.0/24'] = { - 'id' => '192-168-13-0_24', - 'routers' => ['192.168.13.1'], - 'address' => '192.168.13.0', - 'netmask' => '255.255.255.0', - 'broadcast' => '192.168.13.255', - 'range' => '192.168.13.50 192.168.13.240', - 'next_server' => '192.168.13.11', -} -default['dhcp']['shared_network_data']['mysharedattrnetwork']['subnets']['192.168.14.0/24'] = { - 'id' => '192-168-14-0_24', - 'routers' => ['192.168.14.1'], - 'address' => '192.168.14.0', - 'netmask' => '255.255.255.0', - 'broadcast' => '192.168.14.255', - 'range' => '192.168.14.50 192.168.14.240', - 'next_server' => '192.168.14.11', -} - -default['dhcp']['hooks'] = { - 'commit' => ['use-host-decl-names on'], - 'release' => ['use-host-decl-names on'], -} - -default['dhcp']['interfaces'] = %w(eth0 eth1) -default['dhcp']['groups'] = [] -default['dhcp']['hosts'] = [] -default['dhcp']['networks'] = ['192.168.9.0/24'] -default['dhcp']['shared_networks'] = ['mysharednet'] -default['dhcp']['options']['domain-name'] = 'vm' -default['dhcp']['options']['domain-name-servers'] = '192.168.9.1' -default['dhcp']['extra_files'] = ['/etc/dhcp/extra1.conf', '/etc/dhcp/extra2.conf'] diff --git a/test/cookbooks/test/metadata.rb b/test/cookbooks/test/metadata.rb index a1079b26..20d78501 100644 --- a/test/cookbooks/test/metadata.rb +++ b/test/cookbooks/test/metadata.rb @@ -1,3 +1,4 @@ name 'test' +version '0.0.1' depends 'dhcp' diff --git a/test/cookbooks/test/recipes/config_delete.rb b/test/cookbooks/test/recipes/config_delete.rb new file mode 100644 index 00000000..d98fbe71 --- /dev/null +++ b/test/cookbooks/test/recipes/config_delete.rb @@ -0,0 +1,49 @@ +# +# Cookbook:: dhcp_test +# Recipe:: config_delete +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +dhcp_config '/etc/dhcp/dhcpd6.conf' do + ip_version :ipv6 + action :delete + notifies :stop, 'dhcp_service[dhcpd6]', :delayed + notifies :disable, 'dhcp_service[dhcpd6]', :delayed +end + +dhcp_host 'Test-IPv6-Host' do + ip_version :ipv6 + action :delete +end + +dhcp_subnet 'overrides' do + conf_dir '/etc/dhcp_override' + action :delete + notifies :restart, 'dhcp_service[dhcpd]', :delayed +end + +dhcp_subnet 'overrides' do + action :delete + notifies :restart, 'dhcp_service[dhcpd]', :delayed +end + +dhcp_subnet 'deny host from class' do + action :delete + notifies :restart, 'dhcp_service[dhcpd]', :delayed +end + +dhcp_class 'UnregisteredHosts' do + action :delete + notifies :restart, 'dhcp_service[dhcpd]', :delayed +end diff --git a/test/cookbooks/test/recipes/default.rb b/test/cookbooks/test/recipes/default.rb index 11ce48a8..5dd79c72 100644 --- a/test/cookbooks/test/recipes/default.rb +++ b/test/cookbooks/test/recipes/default.rb @@ -1,5 +1,30 @@ -include_recipe 'dhcp::server' -include_recipe 'test::net_setup' -include_recipe 'test::extra_files' -include_recipe 'test::dhcp_shared_network' -include_recipe 'test::dhcp_class' +# +# Cookbook:: dhcp_test +# Recipe:: default +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +include_recipe '::net_setup' + +include_recipe '::package' +include_recipe '::include_files' + +include_recipe '::dhcp_config' +include_recipe '::dhcp_subnet' +include_recipe '::dhcp_host' +include_recipe '::dhcp_class' +include_recipe '::dhcp_shared_network' +include_recipe '::dhcp_group' + +include_recipe '::service' diff --git a/test/cookbooks/test/recipes/dhcp_class.rb b/test/cookbooks/test/recipes/dhcp_class.rb index c1a357b6..60b25c08 100644 --- a/test/cookbooks/test/recipes/dhcp_class.rb +++ b/test/cookbooks/test/recipes/dhcp_class.rb @@ -4,24 +4,41 @@ dhcp_class 'RegisteredHosts' do match 'hardware' - subclass '1:10:bf:48:42:55:01' - subclass '1:10:bf:48:42:55:02' + subclass [ + '1:10:bf:48:42:55:01', + '1:10:bf:48:42:55:02', + ] end dhcp_class 'UnregisteredHosts' do match 'hardware' - subclass '1:10:bf:48:42:55:03' - subclass '1:10:bf:48:42:55:04' + subclass [ + '1:10:bf:48:42:55:03', + '1:10:bf:48:42:55:04', + ] + options( + 'domain-name-servers' => '8.8.8.8' + ) +end + +dhcp_class 'OtherHosts' do + match 'hardware' + subclass [ + '1:10:bf:48:42:55:05', + '1:10:bf:48:42:55:06', + ] end dhcp_subnet 'deny host from class' do subnet '192.168.4.0' - broadcast '192.168.4.255' netmask '255.255.255.0' - routers ['192.168.4.1'] - pool do - range '192.168.4.20 192.168.4.30' - deny 'members of "RegisteredHosts"' - allow 'members of "UnregisteredHosts"' - end + options( + 'broadcast-address' => '192.168.4.255', + 'routers' => '192.168.4.1' + ) + pools( + 'range' => '192.168.4.20 192.168.4.30', + 'deny' => 'members of "RegisteredHosts"', + 'allow' => 'members of "UnregisteredHosts"' + ) end diff --git a/test/cookbooks/test/recipes/dhcp_config.rb b/test/cookbooks/test/recipes/dhcp_config.rb new file mode 100644 index 00000000..4a52803e --- /dev/null +++ b/test/cookbooks/test/recipes/dhcp_config.rb @@ -0,0 +1,63 @@ +# +# Cookbook:: dhcp_test +# Recipe:: config +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +dhcp_config '/etc/dhcp/dhcpd.conf' do + allow %w(booting bootp unknown-clients) + parameters( + 'default-lease-time' => 7200, + 'ddns-update-style' => 'interim', + 'max-lease-time' => 86400, + 'update-static-leases' => true, + 'one-lease-per-client' => true, + 'authoritative' => '', + 'ping-check' => true + ) + options( + 'domain-name' => '"test.domain.local"', + 'domain-name-servers' => '8.8.8.8', + 'host-name' => ' = binary-to-ascii (16, 8, "-", substring (hardware, 1, 6))' + ) + hooks( + 'commit' => ['use-host-decl-names on'], + 'release' => ['use-host-decl-names on'] + ) + include_files [ + '/etc/dhcp/extra1.conf', + '/etc/dhcp/extra2.conf', + '/etc/dhcp_override/list.conf', + ] + action :create +end + +dhcp_config '/etc/dhcp/dhcpd6.conf' do + ip_version :ipv6 + deny %w(duplicates) + parameters( + 'default-lease-time' => 7200, + 'ddns-updates' => 'on', + 'ddns-update-style' => 'interim', + 'max-lease-time' => 86400, + 'update-static-leases' => true, + 'one-lease-per-client' => 'on', + 'authoritative' => '', + 'ping-check' => true + ) + options( + 'dhcp6.name-servers' => '2001:4860:4860::8888, 2001:4860:4860::8844' + ) + action :create +end diff --git a/test/cookbooks/test/recipes/dhcp_group.rb b/test/cookbooks/test/recipes/dhcp_group.rb new file mode 100644 index 00000000..d51deebd --- /dev/null +++ b/test/cookbooks/test/recipes/dhcp_group.rb @@ -0,0 +1,10 @@ +dhcp_group 'ip-phones' do + options( + 'tftp-server-name' => '"192.0.2.10"' + ) + hosts( + 'SEP010101010101' => { + 'identifier' => 'hardware ethernet 01:01:01:01:01:01', + } + ) +end diff --git a/test/cookbooks/test/recipes/dhcp_host.rb b/test/cookbooks/test/recipes/dhcp_host.rb new file mode 100644 index 00000000..9f2d0aee --- /dev/null +++ b/test/cookbooks/test/recipes/dhcp_host.rb @@ -0,0 +1,12 @@ +dhcp_host 'Test-IPv4-Host' do + options 'host-name' => 'test-ipv4-host' + identifier 'hardware ethernet 00:53:00:00:00:01' + address '192.168.0.10' +end + +dhcp_host 'Test-IPv6-Host' do + ip_version :ipv6 + options 'host-name' => 'test-ipv6-host' + identifier 'host-identifier option dhcp6.client-id 00:53:00:00:00:01:a4:65:b7:c8' + address '2001:db8:1:1:0:0:1:10' +end diff --git a/test/cookbooks/test/recipes/dhcp_shared_network.rb b/test/cookbooks/test/recipes/dhcp_shared_network.rb index 5ab3c330..38a6d53e 100644 --- a/test/cookbooks/test/recipes/dhcp_shared_network.rb +++ b/test/cookbooks/test/recipes/dhcp_shared_network.rb @@ -1,29 +1,45 @@ dhcp_shared_network 'single' do - subnet '192.168.1.0' do - broadcast '192.168.1.255' - netmask '255.255.255.0' - routers ['192.168.1.1'] - pool do - range '192.168.1.20 192.168.1.30' - end - end + subnets( + '192.168.1.0' => { + 'subnet' => '192.168.1.0', + 'netmask' => '255.255.255.0', + 'options' => { + 'broadcast-address' => '192.168.1.255', + 'routers' => '192.168.1.1', + }, + 'pools' => { + 'range' => '192.168.1.20 192.168.1.30', + }, + } + ) end dhcp_shared_network 'multiple' do - subnet '192.168.2.0' do - broadcast '192.168.2.255' - netmask '255.255.255.0' - routers ['192.168.2.1'] - pool do - range ['192.168.2.20 192.168.2.30'] - end - end - subnet '192.168.3.0' do - broadcast '192.168.3.255' - netmask '255.255.255.0' - routers ['192.168.3.1'] - pool do - range ['192.168.3.20 192.168.3.30', '192.168.3.40 192.168.3.50'] - end - end + subnets( + '192.168.2.0' => { + 'subnet' => '192.168.2.0', + 'netmask' => '255.255.255.0', + 'options' => { + 'broadcast-address' => '192.168.2.255', + 'routers' => '192.168.2.1', + }, + 'pools' => { + 'range' => '192.168.2.20 192.168.2.30', + }, + }, + '192.168.3.0' => { + 'subnet' => '192.168.3.0', + 'netmask' => '255.255.255.0', + 'options' => { + 'broadcast-address' => '192.168.3.255', + 'routers' => '192.168.3.1', + }, + 'pools' => { + 'range' => [ + '192.168.3.20 192.168.3.30', + '192.168.3.40 192.168.3.50', + ], + }, + } + ) end diff --git a/test/cookbooks/test/recipes/dhcp_subnet.rb b/test/cookbooks/test/recipes/dhcp_subnet.rb index 8718dd87..d3dedfe6 100644 --- a/test/cookbooks/test/recipes/dhcp_subnet.rb +++ b/test/cookbooks/test/recipes/dhcp_subnet.rb @@ -1,42 +1,76 @@ # Define using defaults -dhcp_subnet '10.0.2.0' do - netmask '255.255.254.0' # Requried attribute +dhcp_subnet '192.168.9.0' do + comment 'Listen Subnet Declaration' + netmask '255.255.255.0' end # Basic definition dhcp_subnet 'basic' do + comment 'Basic Subnet Declaration' subnet '192.168.0.0' - broadcast '192.168.0.255' netmask '255.255.255.0' - routers ['192.168.0.1'] - options ['time-offset 10'] - pool do - range '192.168.0.100 192.168.0.200' - end + options [ + 'routers 192.168.0.1', + 'time-offset 10', + ] + pools 'range' => '192.168.0.100 192.168.0.200' end +directory '/etc/dhcp_override' + # Override everything dhcp_subnet 'overrides' do - subnet '192.168.0.0' - broadcast '192.168.0.255' + comment 'Overrides Subnet Declaration' + subnet '192.168.1.0' netmask '255.255.255.0' - routers ['192.168.0.1'] - options ['time-offset 10'] - pool do - peer '192.168.0.2' - range '192.168.0.100 192.168.0.200' - deny 'members of "RegisteredHosts"' - allow ['members of "UnregisteredHosts"', 'members of "OtherHosts"'] - end - ddns 'test.com' - evals [' - if exists user-class and option user-class = "iPXE" { - filename "bootstrap.ipxe"; - } else { - filename "undionly.kpxe"; - }'] - key 'name' => 'test_key', 'algorithm' => 'test_algo', 'secret' => 'test_secret' - zones [{ 'zone' => 'test', 'primary' => 'test_pri', 'key' => 'test_key' }] + options [ + 'routers 192.168.1.1', + 'time-offset 10', + 'broadcast-address 192.168.0.255', + ] + pools( + 'peer' => '192.168.0.2', + 'range' => '192.168.1.100 192.168.1.200', + 'deny' => 'members of "RegisteredHosts"', + 'allow' => ['members of "UnregisteredHosts"', 'members of "OtherHosts"'] + ) + parameters( + 'ddns-domainname' => '"test.com"', + 'next-server' => '192.168.0.3' + ) + evals [ 'if exists user-class and option user-class = "iPXE" { + filename "bootstrap.ipxe"; + } else { + filename "undionly.kpxe"; + }' ] + key 'name' => 'test_key', 'algorithm' => 'hmac-sha256', 'secret' => 'c7nBOcB2rbJh7lYCI65/PGrS6QdlLMCPe2xunZ4dij8=' + zones 'test' => { 'primary' => 'test_pri', 'key' => 'test_key' } conf_dir '/etc/dhcp_override' - next_server '192.168.0.3' +end + +# DHCPv6 listen subnet +dhcp_subnet 'dhcpv6_listen' do + ip_version :ipv6 + comment 'Testing DHCPv6 Basic Subnet' + subnet '2001:db8:1::' + prefix 64 +end + +# DHCPv6 basic subnet +dhcp_subnet 'dhcpv6_basic' do + ip_version :ipv6 + comment 'Testing DHCPv6 Basic Subnet' + subnet '2001:db8:2:1::' + prefix 64 + options( + 'domain-name' => '"test.domain.local"', + 'dhcp6.name-servers' => '2001:4860:4860::8888, 2001:4860:4860::8844' + ) + parameters( + 'ddns-domainname' => '"test.domain.local"', + 'default-lease-time' => 28800 + ) + range [ + '2001:db8:2:1::1:0/112', + ] end diff --git a/test/cookbooks/test/recipes/extra_files.rb b/test/cookbooks/test/recipes/include_files.rb similarity index 100% rename from test/cookbooks/test/recipes/extra_files.rb rename to test/cookbooks/test/recipes/include_files.rb diff --git a/test/cookbooks/test/recipes/net_setup.rb b/test/cookbooks/test/recipes/net_setup.rb index 9c033b9e..24020e3f 100644 --- a/test/cookbooks/test/recipes/net_setup.rb +++ b/test/cookbooks/test/recipes/net_setup.rb @@ -8,6 +8,12 @@ not_if 'ip a show dev eth1 | grep UP' end -ifconfig '192.168.9.1' do - device 'eth1' +execute 'address eth1' do + command 'ip addr add 192.168.9.1 dev eth1' + not_if 'ip a show dev eth1 | grep \'inet 192.168.9.1\'' +end + +execute 'remove ipv6 default route' do + command 'ip -6 route del ::/0' + only_if 'ip -6 route | grep default' end diff --git a/recipes/default.rb b/test/cookbooks/test/recipes/package.rb similarity index 72% rename from recipes/default.rb rename to test/cookbooks/test/recipes/package.rb index 5b08eb1f..e4bb0365 100644 --- a/recipes/default.rb +++ b/test/cookbooks/test/recipes/package.rb @@ -1,8 +1,6 @@ -# Author:: Matt Ray -# Cookbook:: dhcp -# Recipe:: default # -# Copyright:: 2011-2017, Chef Software, Inc +# Cookbook:: dhcp_test +# Recipe:: package # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,4 +15,4 @@ # limitations under the License. # -Chef::Log.warn('The dhcp::default recipe is empty and should not be applied to a node') +dhcp_package 'Server Test' diff --git a/test/cookbooks/test/recipes/service.rb b/test/cookbooks/test/recipes/service.rb new file mode 100644 index 00000000..361c3a23 --- /dev/null +++ b/test/cookbooks/test/recipes/service.rb @@ -0,0 +1,29 @@ +# +# Cookbook:: dhcp_test +# Recipe:: service +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +dhcp_service 'dhcpd' do + ip_version :ipv4 + action [:create, :enable, :start] +end + +require 'socket' +ipv6_available = Socket.getifaddrs.select { |addr_info| addr_info.addr.ipv6? }.count > 0 + +dhcp_service 'dhcpd6' do + ip_version :ipv6 + action ipv6_available ? [:create, :enable, :start] : [:create, :enable] +end diff --git a/test/integration/data_bags/dhcp_groups/test.json b/test/integration/data_bags/dhcp_groups/test.json deleted file mode 100644 index 0f0c3b26..00000000 --- a/test/integration/data_bags/dhcp_groups/test.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "test", - "pxe": "ubuntu-precise", - "parameters": [ - "use-host-decl-names on", - "max-lease-time 300", - "default-lease-time 120", - "next-server \"someplace\"" - ], - "hosts": { - "some-vm": { - "parameters": [ ], - "mac": "11:22:33:44:55:66", - "ip": "192.168.1.111" - }, - "another-host": { - "mac": "22:33:44:55:66:77" - } - } -} diff --git a/test/integration/data_bags/dhcp_hosts/pxe_test-vm.json b/test/integration/data_bags/dhcp_hosts/pxe_test-vm.json deleted file mode 100644 index 215eea8f..00000000 --- a/test/integration/data_bags/dhcp_hosts/pxe_test-vm.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "id": "pxe_test-vm", - "hostname": "pxe_test.vm", - "mac": "08:00:27:8f:7b:db", - "ip": "192.168.1.31", - "parameters": [ ], - "options": [ ], - "pxe": "ubuntu-precise" -} diff --git a/test/integration/data_bags/dhcp_hosts/vagrant-vm.json b/test/integration/data_bags/dhcp_hosts/vagrant-vm.json deleted file mode 100644 index 368fc2a5..00000000 --- a/test/integration/data_bags/dhcp_hosts/vagrant-vm.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "id": "vagrant-vm", - "hostname": "vagrant.vm", - "mac": "08:00:27:f1:1f:b6", - "pxe": "ubuntu-precise" -} diff --git a/test/integration/data_bags/dhcp_networks/192-168-34-0_24.json b/test/integration/data_bags/dhcp_networks/192-168-34-0_24.json deleted file mode 100644 index b3a3f331..00000000 --- a/test/integration/data_bags/dhcp_networks/192-168-34-0_24.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "id": "192-168-34-0_24", - "routers": [ "192.168.34.1" ], - "address": "192.168.34.0", - "netmask": "255.255.255.0", - "broadcast": "192.168.9.255", - "range": [ - "192.168.34.50 192.168.34.100", - "192.168.34.150 192.168.34.200", - "192.168.34.250 192.168.34.254" - ], - "options": [ "time-offset 10" ], - "next_server": "192.168.34.11" -} diff --git a/test/integration/data_bags/dhcp_networks/192-168-9-0_24.json b/test/integration/data_bags/dhcp_networks/192-168-9-0_24.json deleted file mode 100644 index bdee7a70..00000000 --- a/test/integration/data_bags/dhcp_networks/192-168-9-0_24.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "id": "192-168-9-0_24", - "routers": [ "192.168.9.1" ], - "address": "192.168.9.0", - "netmask": "255.255.255.0", - "broadcast": "192.168.9.255", - "range": "192.168.9.50 192.168.9.240", - "options": [ "time-offset 10" ], - "next_server": "192.168.9.11", - "extra_pool_lines": [ "# test" ] -} diff --git a/test/integration/data_bags/dhcp_networks/mysharednet.json b/test/integration/data_bags/dhcp_networks/mysharednet.json deleted file mode 100644 index e699c2f9..00000000 --- a/test/integration/data_bags/dhcp_networks/mysharednet.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "id": "mysharednet", - "subnets": { - "192.168.10.0/24": { - "routers": ["192.168.10.1"], - "address": "192.168.10.0", - "netmask": "255.255.255.0", - "broadcast": "192.168.10.255", - "range": "192.168.10.50 192.168.10.240", - "next_server": "192.168.10.11" - }, - "10.0.2.0/24": { - "address": "10.0.2.0", - "netmask": "255.255.255.0", - "broadcast": "10.0.2.255", - "range": "10.0.2.50 10.0.2.240" - } - } -} diff --git a/test/integration/data_bags/dns_zones/192-168-1-0.json b/test/integration/data_bags/dns_zones/192-168-1-0.json deleted file mode 100644 index 10821c0c..00000000 --- a/test/integration/data_bags/dns_zones/192-168-1-0.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": "192-168-1-0", - "zone_name": "1.168.192.IN-ADDR.ARPA", - "ttl": "2d", - "authority": "dns01.vm", - "email": "root", - "refresh": "1h", "retry": "15m", "expire": "7d", "minimum": "1h", - "allow_update": "127.0.0.1", - "allow_query": "any;", - "name_servers": { "dns01.vm": "192.168.1.9" }, - "resource_records": { - "9": { "PTR": "dns01.vm." }, - "10": { "PTR": "server.vm." }, - "11": { "PTR": "client01.vm." }, - "12": { "PTR": "dhcp01.vm." } - } -} diff --git a/test/integration/data_bags/dns_zones/vm.json b/test/integration/data_bags/dns_zones/vm.json deleted file mode 100644 index 94f56094..00000000 --- a/test/integration/data_bags/dns_zones/vm.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "id": "vm", - "zone_name": "vm", - "ttl": "2d", - "authority": "dns01.vm", - "email": "root", - "refresh": "1h", "retry": "15m", "expire": "7d", "minimum": "1h", - "allow_update": "127.0.0.1;", - "allow_query": "any;", - "name_servers": { "dns01.vm": "192.168.1.9" }, - "master_address": "192.168.1.9", - "rndc_key": "dhcp-key", - "resource_records": { - "server": { "A": "192.168.1.10" }, - "chef-server": { "CNAME": "server.vm." }, - "client01": { "A": "192.168.1.11" }, - "dhcp01": { "A": "192.168.1.12" } - } -} diff --git a/test/integration/data_bags/rndc_keys/dhcp-key.json b/test/integration/data_bags/rndc_keys/dhcp-key.json deleted file mode 100644 index 174b84be..00000000 --- a/test/integration/data_bags/rndc_keys/dhcp-key.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "id": "dhcp-key", - "algorithm": "hmac-md5", - "secret": "L+Jl4+4onU4Wstfi4pdmnQ==" -} diff --git a/test/integration/default/inspec/classes_spec.rb b/test/integration/default/inspec/classes_spec.rb new file mode 100644 index 00000000..bd036780 --- /dev/null +++ b/test/integration/default/inspec/classes_spec.rb @@ -0,0 +1,20 @@ +describe file('/etc/dhcp/dhcpd.d/classes.d/RegisteredHosts.conf') do + it { should exist } + it { should be_file } + its(:content) { should match 'class "RegisteredHosts" ' } + its(:content) { should match 'subclass "RegisteredHosts" 1:10:bf:48:42:55:01;' } +end + +describe file('/etc/dhcp/dhcpd.d/classes.d/UnregisteredHosts.conf') do + it { should exist } + it { should be_file } + its(:content) { should match 'class "UnregisteredHosts" ' } + its(:content) { should match 'subclass "UnregisteredHosts" 1:10:bf:48:42:55:03;' } + its(:content) { should match 'option domain-name-servers 8.8.8.8;' } +end + +describe file('/etc/dhcp/dhcpd6.d/classes.d/list.conf') do + it { should exist } + it { should be_file } + its(:content) { should match '# No files to include' } +end diff --git a/test/integration/default/inspec/default_spec.rb b/test/integration/default/inspec/default_spec.rb index 92530e64..e9810110 100644 --- a/test/integration/default/inspec/default_spec.rb +++ b/test/integration/default/inspec/default_spec.rb @@ -1,30 +1,65 @@ -title 'DHCP install' -case os[:family] -when 'redhat' - package_name = 'dhcp' - service_name = 'dhcpd' +case os.family +when 'redhat', 'centos' + package_name = if os.release.to_i < 8 + 'dhcp' + else + 'dhcp-server' + end + service_name = %w(dhcpd) + service_name.push('dhcpd6') if interface('eth0').ipv6_address? when 'fedora' package_name = 'dhcp-server' - service_name = 'dhcpd' + service_name = %w(dhcpd) + service_name.push('dhcpd6') if interface('eth0').ipv6_address? when 'debian', 'ubuntu' package_name = 'isc-dhcp-server' - service_name = 'isc-dhcp-server' + service_name = %w(isc-dhcp-server) + service_name.push('isc-dhcp-server6') if interface('eth0').ipv6_address? end describe package(package_name) do it { should be_installed } end -describe service(service_name) do - it { should be_enabled } - it { should be_running } +service_name.each do |service| + describe service(service) do + it { should be_enabled } + it { should be_running } + end end -describe processes('dhcpd') do - its('states') { should eq ['Ss'] } +describe command('/usr/sbin/dhcpd -t -4 -cf /etc/dhcp/dhcpd.conf') do + its('exit_status') { should eq 0 } end +describe command('/usr/sbin/dhcpd -t -6 -cf /etc/dhcp/dhcpd6.conf') do + its('exit_status') { should eq 0 } +end + +# if interface('eth0').ipv6_address? - Using bodge due to +if command('ip addr show dev eth0 | grep inet6').exit_status.eql?(0) + describe processes('dhcpd') do + its('states') { should eq %w(Ss Ss) } + end +else + describe processes('dhcpd') do + its('states') { should eq %w(Ss) } + end +end + +describe port(67) do + it { should be_listening } + its('protocols') { should include 'udp' } + its('processes') { should include 'dhcpd' } +end + +describe port(547) do + it { should be_listening } + its('protocols') { should include 'udp' } + its('processes') { should include 'dhcpd' } +end if interface('eth0').ipv6_address? + describe file('/etc/dhcp/dhcpd.conf') do it { should exist } it { should be_file } @@ -33,3 +68,10 @@ its(:content) { should match "on commit {\n use-host-decl-names on;\n}" } its(:content) { should match "on release {\n use-host-decl-names on;\n}" } end + +describe file('/etc/dhcp/dhcpd6.conf') do + it { should exist } + it { should be_file } + its(:content) { should match /option dhcp6.name-servers 2001:4860:4860::8888, 2001:4860:4860::8844;/ } + its(:content) { should match "# Deny\ndeny duplicates;" } +end diff --git a/test/integration/default/inspec/group_spec.rb b/test/integration/default/inspec/group_spec.rb new file mode 100644 index 00000000..01de3f10 --- /dev/null +++ b/test/integration/default/inspec/group_spec.rb @@ -0,0 +1,13 @@ +describe file('/etc/dhcp/dhcpd.d/groups.d/ip-phones.conf') do + it { should exist } + it { should be_file } + its(:content) { should match 'tftp-server-name "192.0.2.10";' } + its(:content) { should match 'include "/etc/dhcp/dhcpd.d/groups.d/ip-phones_grouphost_SEP010101010101.conf";' } +end + +describe file('/etc/dhcp/dhcpd.d/groups.d/ip-phones_grouphost_SEP010101010101.conf') do + it { should exist } + it { should be_file } + its(:content) { should match 'host ip-phones_grouphost_SEP010101010101 {' } + its(:content) { should match 'hardware ethernet 01:01:01:01:01:01;' } +end diff --git a/test/integration/default/inspec/subnets_spec.rb b/test/integration/default/inspec/subnets_spec.rb new file mode 100644 index 00000000..3108a75c --- /dev/null +++ b/test/integration/default/inspec/subnets_spec.rb @@ -0,0 +1,13 @@ +describe file('/etc/dhcp/dhcpd.d/subnets.d/deny host from class.conf') do + it { should exist } + it { should be_file } + its(:content) { should match 'subnet 192.168.4.0 netmask 255.255.255.0' } + its(:content) { should match 'deny members of "RegisteredHosts"' } +end + +describe file('/etc/dhcp/dhcpd6.d/subnets.d/dhcpv6_basic.conf') do + it { should exist } + it { should be_file } + its(:content) { should match 'range6 2001:db8:2:1::1:0/112;' } + its(:content) { should match 'default-lease-time 28800;' } +end diff --git a/test/integration/delete/classes_spec.rb b/test/integration/delete/classes_spec.rb new file mode 100644 index 00000000..2bf44099 --- /dev/null +++ b/test/integration/delete/classes_spec.rb @@ -0,0 +1,23 @@ +describe file('/etc/dhcp/dhcpd.d/classes.d/UnregisteredHosts.conf') do + it { should_not exist } + it { should_not be_file } +end + +describe file('/etc/dhcp/dhcpd.d/classes.d/RegisteredHosts.conf') do + it { should exist } + it { should be_file } + its(:content) { should match 'class "RegisteredHosts" ' } + its(:content) { should match 'subclass "RegisteredHosts" 1:10:bf:48:42:55:01;' } +end + +describe file('/etc/dhcp/dhcpd.d/classes.d/list.conf') do + it { should exist } + it { should be_file } + its(:content) { should_not match 'include /etc/dhcp/dhcpd.d/classes.d/UnregisteredHosts.conf' } +end + +describe file('/etc/dhcp/dhcpd6.d/classes.d/list.conf') do + it { should exist } + it { should be_file } + its(:content) { should match '# No files to include' } +end diff --git a/test/integration/delete/default_spec.rb b/test/integration/delete/default_spec.rb new file mode 100644 index 00000000..b80d1e2e --- /dev/null +++ b/test/integration/delete/default_spec.rb @@ -0,0 +1,64 @@ +case os.family +when 'redhat', 'centos' + package_name = if os.release.to_i < 8 + 'dhcp' + else + 'dhcp-server' + end + service_name = %w(dhcpd) + service_name.push('dhcpd6') if interface('eth0').ipv6_address? +when 'fedora' + package_name = 'dhcp-server' + service_name = %w(dhcpd) + service_name.push('dhcpd6') if interface('eth0').ipv6_address? +when 'debian', 'ubuntu' + package_name = 'isc-dhcp-server' + service_name = %w(isc-dhcp-server) + service_name.push('isc-dhcp-server6') if interface('eth0').ipv6_address? +end + +describe package(package_name) do + it { should be_installed } +end + +describe service(service_name[0]) do + it { should be_enabled } + it { should be_running } +end + +describe service(service_name[1]) do + it { should_not be_enabled } + it { should_not be_running } +end + +describe command('/usr/sbin/dhcpd -t -4 -cf /etc/dhcp/dhcpd.conf') do + its('exit_status') { should eq 0 } +end + +describe processes('dhcpd') do + its('states') { should eq %w(Ss) } +end + +describe port(67) do + it { should be_listening } + its('protocols') { should include 'udp' } + its('processes') { should include 'dhcpd' } +end + +describe port(547) do + it { should_not be_listening } +end + +describe file('/etc/dhcp/dhcpd.conf') do + it { should exist } + it { should be_file } + its(:content) { should match %r{^include "/etc/dhcp/extra1.conf";} } + its(:content) { should match %r{^include "/etc/dhcp/extra2.conf";} } + its(:content) { should match "on commit {\n use-host-decl-names on;\n}" } + its(:content) { should match "on release {\n use-host-decl-names on;\n}" } +end + +describe file('/etc/dhcp/dhcpd6.conf') do + it { should_not exist } + it { should_not be_file } +end diff --git a/test/integration/delete/subnets_spec.rb b/test/integration/delete/subnets_spec.rb new file mode 100644 index 00000000..6ca1a267 --- /dev/null +++ b/test/integration/delete/subnets_spec.rb @@ -0,0 +1,4 @@ +describe file('/etc/dhcp_override/overrides.conf') do + it { should_not exist } + it { should_not be_file } +end