Repository: ansible/ansible

Issue:
packet_device not working with ipxe_script_url set


##### ISSUE TYPE
 - Bug Report


##### COMPONENT NAME
modules/cloud/packet
##### ANSIBLE VERSION

```
ansible 2.4.1.0
  config file = /home/krist/.ansible.cfg
  configured module search path = [u'/home/krist/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = bin/ansible
  python version = 2.7.14 (default, Nov  2 2017, 18:42:05) [GCC 7.2.1 20170915 (Red Hat 7.2.1-2)]
```

##### CONFIGURATION
```
ANSIBLE_PIPELINING(/home/krist/Work/LAB/pakket/ansible.cfg) = True
DEFAULT_HOST_LIST(/home/krist/Work/LAB/pakket/ansible.cfg) = [u'/home/krist/Work/LAB/pakket/inventory']
DEFAULT_ROLES_PATH(/home/krist/Work/LAB/pakket/ansible.cfg) = [u'/home/krist/Work/LAB/pakket/roles']
DEFAULT_VAULT_PASSWORD_FILE(/home/krist/Work/LAB/pakket/ansible.cfg) = /home/krist/.ansible/password
```



##### OS / ENVIRONMENT
Fedora 26, Ansible from git devel branch.

##### SUMMARY

packet_device: Creating a packet host with ipxeboot does not work.

##### STEPS TO REPRODUCE

Install the packet.net CLI tools.
Create a group_vars/all/main.yaml with correct values for location, api key and project id.

Try to provision a host with a playbook:

```yaml
- name: create rhev lab 
  hosts: localhost
  tasks:

  - packet_sshkey:
      key_file: "{{ lookup('env','HOME') + '/.ssh/id_rsa.pub' }}"
      label: default key

  - packet_device:
      project_id: "{{ project_id }}"
      hostnames: "{{ item }}"
      operating_system: custom_ipxe
      ipxe_script_url: http://boot.example.com/rhvh/boot.ipxe
      plan: baremetal_0
      facility: "{{ location }}"
      auth_token: "{{ api_key }}"
    with_items:
    - rhvh1

```


##### EXPECTED RESULTS

Host is provisioned and attempts a ipxe boot with the URL provided.

##### ACTUAL RESULTS
```
task path: /home/krist/Work/LAB/pakket/create_rhvh_lab.yaml:9
Using module file /usr/lib/python2.7/site-packages/ansible/modules/cloud/packet/packet_device.py
<127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: krist
<127.0.0.1> EXEC /bin/sh -c '/usr/bin/python && sleep 0'
failed: [localhost] (item=rhvh1) => {
    "changed": false, 
    "failed": true, 
    "invocation": {
        "module_args": {
            "auth_token": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
            "facility": "ams1", 
            "hostnames": "rhvh1", 
            "ipxe_script_url": "http://boot.example.com/rhvh/boot.ipxe", 
            "operating_system": "custom_ipxe", 
            "plan": "baremetal_0", 
            "project_id": "be6b7156-3c89-447c-b46e-ee376809a3d2"
        }
    }, 
    "item": "rhvh1", 
    "msg": "parameters are mutually exclusive: ('ipxe_script_url', 'operating_system')"
```

I assumed that this just meant that I should not define operating_system when ipxe_script_url is set. So I also tested with a playbook where I had removed the operating_system parameter.
There the result was:

```
task path: /home/krist/Work/LAB/pakket/create_rhvh_lab.yaml:9
Using module file /usr/lib/python2.7/site-packages/ansible/modules/cloud/packet/packet_device.py
<127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: krist
<127.0.0.1> EXEC /bin/sh -c '/usr/bin/python && sleep 0'
The full traceback is:
Traceback (most recent call last):
  File "/tmp/ansible_qyWyXe/ansible_module_packet_device.py", line 640, in main
    module.exit_json(**act_on_devices(module, packet_conn, state))
  File "/tmp/ansible_qyWyXe/ansible_module_packet_device.py", line 575, in act_on_devices
    for n in create_hostnames]
  File "/tmp/ansible_qyWyXe/ansible_module_packet_device.py", line 445, in create_single_device
    % param)
Exception: operating_system parameter is required for new device.

failed: [localhost] (item=rhvh1) => {
    "changed": false, 
    "failed": true, 
    "invocation": {
        "module_args": {
            "always_pxe": false, 
            "auth_token": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
            "count": 1, 
            "count_offset": 1, 
            "device_ids": null, 
            "facility": "ams1", 
            "features": null, 
            "hostnames": [
                "rhvh1"
            ], 
            "ipxe_script_url": "http://home.kri.st/rhvh/boot.ipxe", 
            "locked": false, 
            "operating_system": null, 
            "plan": "baremetal_0", 
            "project_id": "be6b7156-3c89-447c-b46e-ee376809a3d2", 
            "state": "present", 
            "user_data": null, 
            "wait_for_public_IPv": null, 
            "wait_timeout": 900
        }
    }, 
    "item": "rhvh1", 
    "msg": "failed to set device state present, error: operating_system parameter is required for new device."
}
```
I think that packet_device should either allow both operating_system and ipxe_script_url to be set, or otherwise just automatically set operating_system to custom_ipxe when ipxe_script_url is set.






Discussion:
cc @baldwinSPC @t0mk
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Reading through the source I noticed that "ipxe_script_url" is actually not passed to packet_conn.create_device
Is this actually supposed to even work?

hi @kristvanbesien, it's a bug, thanks for reporting.

I will try to fix it today or tomorrow.
As a workaround I used "user_data" to pass the url. That appeared to work.


Resolving patch:
diff --git a/lib/ansible/modules/cloud/packet/packet_device.py b/lib/ansible/modules/cloud/packet/packet_device.py
--- a/lib/ansible/modules/cloud/packet/packet_device.py
+++ b/lib/ansible/modules/cloud/packet/packet_device.py
@@ -445,6 +445,11 @@ def create_single_device(module, packet_conn, hostname):
     locked = module.params.get('locked')
     ipxe_script_url = module.params.get('ipxe_script_url')
     always_pxe = module.params.get('always_pxe')
+    if operating_system != 'custom_ipxe':
+        for param in ('ipxe_script_url', 'always_pxe'):
+            if module.params.get(param):
+                raise Exception('%s paramater is not valid for non custom_ipxe operating_system.' % param)
+
     device = packet_conn.create_device(
         project_id=project_id,
         hostname=hostname,
@@ -607,8 +612,6 @@ def main():
         ),
         required_one_of=[('device_ids', 'hostnames',)],
         mutually_exclusive=[
-            ('always_pxe', 'operating_system'),
-            ('ipxe_script_url', 'operating_system'),
             ('hostnames', 'device_ids'),
             ('count', 'device_ids'),
             ('count_offset', 'device_ids'),


Test patch: