Add dhclient basic configuration

    Allows configuring general section as well as configuring
    each dhcp enabled interface separetly.
    Does not allow alias or lease configuration.
diff --git a/README.rst b/README.rst
index 2cccd1d..f6e0a83 100644
--- a/README.rst
+++ b/README.rst
@@ -491,14 +491,14 @@
               https: http://maas-01:8080
         ...
         proxy:
-          # package manager fallback defaults 
+          # package manager fallback defaults
           # used if linux:system:repo:apt-mk:proxy has no protocol specific entries
           pkg:
             enabled: true
             ftp:   ftp://proxy.host.local:2121
             #http:  http://proxy.host.local:3142
             #https: https://proxy.host.local:3143
-          ... 
+          ...
           # global system fallback system defaults
           ftp:   ftp://proxy.host.local:2121
           http:  http://proxy.host.local:3142
@@ -772,6 +772,82 @@
             use_interfaces:
             - eth1
 
+DHCP client configuration
+
+None of the keys is mandatory, include only those you really need. For full list
+of available options under send, supersede, prepend, append refer to dhcp-options(5)
+
+.. code-block:: yaml
+
+     linux:
+       network:
+         dhclient:
+           enabled: true
+           backoff_cutoff: 15
+           initial_interval: 10
+           reboot: 10
+           retry: 60
+           select_timeout: 0
+           timeout: 120
+           send:
+             - option: host-name
+               declaration: "= gethostname()"
+           supersede:
+             - option: host-name
+               declaration: "spaceship"
+             - option: domain-name
+               declaration: "domain.home"
+             #- option: arp-cache-timeout
+             #  declaration: 20
+           prepend:
+             - option: domain-name-servers
+               declaration:
+                 - 8.8.8.8
+                 - 8.8.4.4
+             - option: domain-search
+               declaration:
+                 - example.com
+                 - eng.example.com
+           #append:
+             #- option: domain-name-servers
+             #  declaration: 127.0.0.1
+           # ip or subnet to reject dhcp offer from
+           reject:
+             - 192.33.137.209
+             - 10.0.2.0/24
+           request:
+             - subnet-mask
+             - broadcast-address
+             - time-offset
+             - routers
+             - domain-name
+             - domain-name-servers
+             - domain-search
+             - host-name
+             - dhcp6.name-servers
+             - dhcp6.domain-search
+             - dhcp6.fqdn
+             - dhcp6.sntp-servers
+             - netbios-name-servers
+             - netbios-scope
+             - interface-mtu
+             - rfc3442-classless-static-routes
+             - ntp-servers
+           require:
+             - subnet-mask
+             - domain-name-servers
+           # if per interface configuration required add below
+           interface:
+             ens2:
+               initial_interval: 11
+               reject:
+                 - 192.33.137.210
+             ens3:
+               initial_interval: 12
+               reject:
+                 - 192.33.137.211
+
+
 Configure global environment variables
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/linux/files/dhclient.conf b/linux/files/dhclient.conf
new file mode 100644
index 0000000..1f767c6
--- /dev/null
+++ b/linux/files/dhclient.conf
@@ -0,0 +1,106 @@
+{# Macro, put quotation marks around strings that are not ipv4 address #}
+{%- macro quote_if_not_ip(var) -%}
+{%- set var_split_str = var.split(".") -%}
+    {%- if var_split_str|length == 4 -%}
+        {%- set var_is_ipaddr = True -%}
+        {%- for octet in var_split_str -%}
+            {%- if not octet|int in range(255) -%}
+                {%- set var_is_ipaddr = False -%}
+            {%- endif -%}
+        {%- endfor -%}
+    {%- endif -%}
+    {%- if var_is_ipaddr is defined and var_is_ipaddr == True -%}
+{{ var }}
+    {%- else -%}
+"{{ var }}"
+    {%- endif -%}
+{%- endmacro -%}
+
+{# Macro, renders nested options for specific key #}
+{%- macro render_key(section, key) -%}
+{%- if section.get(key) and section.get(key)|length > 0 %}
+     {%- for item in section.get(key) %}
+        {%- if item.declaration is string %}
+{{ key }} {{ item.option }} {{ quote_if_not_ip(item.declaration) }};
+        {%- elif item.declaration is sequence %}
+{{ key }} {{ item.option }}
+            {%- for value in item.declaration -%}
+                {%- set space = " " -%}
+{{ space }}{{ quote_if_not_ip(value) }}
+                {%- if not loop.last -%},{%- endif -%}
+            {%- endfor -%}
+;
+        {%- else %}
+{{ key }} {{ item.option }} {{ item.declaration }};
+        {%- endif -%}
+    {%- endfor -%}
+{%- endif -%}
+{%- endmacro -%}
+
+{# Macro, renders set of options for global section or for interface section #}
+{%- macro render_section(section) -%}
+{%- if section.backoff_cutoff is defined %}
+backoff-cutoff {{ section.backoff_cutoff|default(15, true) }};
+{%- endif -%}
+
+{%- if section.initial_interval is defined %}
+initial-interval {{ section.initial_interval|default(10, true) }};
+{%- endif -%}
+
+{%- if section.reboot is defined %}
+# The reboot statement sets the time that must elapse after the client
+# first tries to reacquire its old address before it gives up and tries
+# to discover a new address.
+reboot {{ section.reboot|default(10, true) }};
+{%- endif -%}
+
+{%- if section.retry is defined %}
+retry {{ section.retry|default(60, true) }};
+{%- endif -%}
+
+{%- if section.select_timeout is defined %}
+# The select-timeout is the time after the client sends its first lease
+# discovery request at which it stops waiting for offers from servers,
+# assuming that it has received at least one such offer
+select-timeout {{ section.select_timeout|default(0, True) }};
+{%- endif -%}
+
+{%- if section.timeout is defined %}
+timeout {{ section.timeout|default(120, True) }};
+{%- endif -%}
+
+{{ render_key(section, "send") }}
+{{ render_key(section, "supersede") }}
+{{ render_key(section, "prepend") }}
+{{ render_key(section, "append") }}
+
+{%- if section.reject is defined and section.reject|length > 0 %}
+reject {{ section.reject|join(",\n    ") }};
+{%- endif %}
+
+{%- if section.request is defined and section.request|length > 0 %}
+request {{ section.request|join(",\n    ") }};
+{%- endif %}
+
+{%- if section.require is defined and section.require|length > 0 %}
+require {{ section.require|join(",\n    ") }};
+{% endif -%}
+{%- endmacro -%}
+
+{# Actual template start #}
+{%- from "linux/map.jinja" import network with context -%}
+{%- set dhclient = network.get('dhclient', {}) %}
+# dhclient.conf(5) file managed by salt-minion(1)
+#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
+option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
+{{ render_section(dhclient) }}
+{%- if dhclient.get("interface") -%}
+{%- for iface_name, options in dhclient.interface.iteritems() %}
+{%- if network.interface.get(iface_name) and network.interface.get(iface_name).enabled == True
+   and network.interface.get(iface_name).proto == 'dhcp' -%}
+interface "{{ iface_name }}" {
+    {{ render_section(options)|indent }}
+}
+{%- endif -%}
+{%- endfor %}
+{%- endif -%}
diff --git a/linux/map.jinja b/linux/map.jinja
index 045212d..64e805b 100644
--- a/linux/map.jinja
+++ b/linux/map.jinja
@@ -110,6 +110,7 @@
            'host': 'none',
         },
         'host': {},
+        'dhclient_config': '/etc/dhcp/dhclient.conf',
     },
     'Debian': {
         'hostname_file': '/etc/hostname',
@@ -124,6 +125,7 @@
            'host': 'none'
         },
         'host': {},
+        'dhclient_config': '/etc/dhcp/dhclient.conf',
     },
     'RedHat': {
         'bridge_pkgs': ['bridge-utils'],
@@ -137,6 +139,7 @@
            'host': 'none'
         },
         'host': {},
+        'dhclient_config': '/etc/dhcp/dhclient.conf',
     },
 }, grain='os_family', merge=salt['pillar.get']('linux:network')) %}
 
diff --git a/linux/network/dhclient.sls b/linux/network/dhclient.sls
new file mode 100644
index 0000000..6de2cfd
--- /dev/null
+++ b/linux/network/dhclient.sls
@@ -0,0 +1,11 @@
+{%- from "linux/map.jinja" import network with context %}
+
+{%- if network.dhclient.enabled|default(False) %}
+
+dhclient_conf:
+  file.managed:
+    - name: {{ network.dhclient_config }}
+    - source: salt://linux/files/dhclient.conf
+    - template: jinja
+
+{%- endif %}
diff --git a/linux/network/init.sls b/linux/network/init.sls
index 53d394b..21069d3 100644
--- a/linux/network/init.sls
+++ b/linux/network/init.sls
@@ -10,6 +10,9 @@
 {%- if network.dpdk is defined %}
 - linux.network.dpdk
 {%- endif %}
+{%- if network.dhclient is defined %}
+- linux.network.dhclient
+{%- endif %}
 {%- if network.interface|length > 0 %}
 - linux.network.interface
 {%- endif %}
diff --git a/tests/pillar/network.sls b/tests/pillar/network.sls
index bf8b176..f862ed1 100644
--- a/tests/pillar/network.sls
+++ b/tests/pillar/network.sls
@@ -24,3 +24,67 @@
         #type: vlan
         #use_interfaces:
         #- interface: ${linux:interface:eth0}
+    dhclient:
+      enabled: true
+      backoff_cutoff: 15
+      initial_interval: 10
+      reboot: 10
+      retry: 60
+      select_timeout: 0
+      timeout: 120
+      send:
+        - option: host-name
+          declaration: "= gethostname()"
+      supersede:
+        - option: host-name
+          declaration: linux
+        - option: domain-name
+          declaration: ci.local
+        #- option: arp-cache-timeout
+        #  declaration: 20
+      prepend:
+        - option: domain-name-servers
+          declaration:
+            - 8.8.8.8
+            - 8.8.4.4
+        - option: domain-search
+          declaration:
+            - example.com
+            - eng.example.com
+      # ip or subnet to reject dhcp offer from
+      reject:
+        - 10.0.2.0/24
+      request:
+        - subnet-mask
+        - broadcast-address
+        - time-offset
+        - routers
+        - domain-name
+        - domain-name-servers
+        - domain-search
+        - host-name
+        - dhcp6.name-servers
+        - dhcp6.domain-search
+        - dhcp6.fqdn
+        - dhcp6.sntp-servers
+        - netbios-name-servers
+        - netbios-scope
+        - interface-mtu
+        - rfc3442-classless-static-routes
+        - ntp-servers
+      require:
+        - subnet-mask
+        - domain-name-servers
+      # if per interface configuration required add below
+      # interface:
+      #   ens2:
+      #     initial_interval: 11
+      #     request:
+      #       - subnet-mask
+      #       - broadcast-address
+      #     reject:
+      #       - 10.0.3.0/24
+      #   ens3:
+      #     initial_interval: 12
+      #     reject:
+      #       - 10.0.4.0/24