From 8111425a96181ba08cfa5bcf051d2e13fd369d6a Mon Sep 17 00:00:00 2001
From: Gaudenz Steinlin <gaudenz@users.noreply.github.com>
Date: Tue, 12 Mar 2019 21:56:05 +0100
Subject: [PATCH] Cloudscale server password (#53701)

* cloudscale_server: remove required parameter check

The valid parameter combinations are already checked by the API. There
is no need to check a subset of this in the Ansible module as well. This
makes it more likely that future changes in the API won't require
changes in the Ansible module.

* cloudscale_server: add password parameter

Recent revisions of the cloudscale.ch API allow setting a password for a
server. Add this option to the cloudscale_server module.
---
 .../cloud/cloudscale/cloudscale_server.py     |  8 +++-
 .../targets/cloudscale_common/tasks/main.yml  |  6 +++
 .../targets/cloudscale_server/tasks/tests.yml | 47 ++++++++++++++++++-
 3 files changed, 58 insertions(+), 3 deletions(-)
 create mode 100644 test/integration/targets/cloudscale_common/tasks/main.yml

diff --git a/lib/ansible/modules/cloud/cloudscale/cloudscale_server.py b/lib/ansible/modules/cloud/cloudscale/cloudscale_server.py
index 8433dc7d52b..9793fd4b4c8 100644
--- a/lib/ansible/modules/cloud/cloudscale/cloudscale_server.py
+++ b/lib/ansible/modules/cloud/cloudscale/cloudscale_server.py
@@ -69,6 +69,11 @@ options:
        - List of SSH public keys.
        - Use the full content of your .pub file here.
     type: list
+  password:
+    description:
+       - Password for the server.
+    type: str
+    version_added: '2.8'
   use_public_network:
     description:
       - Attach a public network interface to the server.
@@ -375,8 +380,6 @@ class AnsibleCloudscaleServer(AnsibleCloudscaleBase):
 
     def _create_server(self, server_info):
         self._result['changed'] = True
-        required_params = ('name', 'ssh_keys', 'image', 'flavor')
-        self._module.fail_on_missing_params(required_params)
 
         data = deepcopy(self._module.params)
         for i in ('uuid', 'state', 'force', 'api_timeout', 'api_token'):
@@ -443,6 +446,7 @@ def main():
         volume_size_gb=dict(type='int', default=10),
         bulk_volume_size_gb=dict(type='int'),
         ssh_keys=dict(type='list'),
+        password=dict(no_log=True),
         use_public_network=dict(type='bool', default=True),
         use_private_network=dict(type='bool', default=False),
         use_ipv6=dict(type='bool', default=True),
diff --git a/test/integration/targets/cloudscale_common/tasks/main.yml b/test/integration/targets/cloudscale_common/tasks/main.yml
new file mode 100644
index 00000000000..fa0be6eb800
--- /dev/null
+++ b/test/integration/targets/cloudscale_common/tasks/main.yml
@@ -0,0 +1,6 @@
+---
+# Password to use for test server
+# This has to be set as a fact, otherwise a new password will be generated
+# on every variable access.
+- set_fact:
+    cloudscale_test_password: "{{ lookup('password', '/dev/null length=15 chars=ascii_letters') }}"
diff --git a/test/integration/targets/cloudscale_server/tasks/tests.yml b/test/integration/targets/cloudscale_server/tasks/tests.yml
index f784ea6af69..e9936e078db 100644
--- a/test/integration/targets/cloudscale_server/tasks/tests.yml
+++ b/test/integration/targets/cloudscale_server/tasks/tests.yml
@@ -246,6 +246,50 @@
       - server_stopped.anti_affinity_with.0.uuid == running_server_uuid
       - server_stopped.interfaces.0.type == 'private'
 
+- name: Test create server with password in check mode
+  cloudscale_server:
+    name: '{{ cloudscale_resource_prefix }}-test-password'
+    flavor: '{{ cloudscale_test_flavor }}'
+    image: '{{ cloudscale_test_image }}'
+    password: '{{ cloudscale_test_password }}'
+  check_mode: yes
+  register: server_password
+- name: Verify create server with password in check mode
+  assert:
+    that:
+      - server_password is changed
+      - server_password.state == 'absent'
+      # Verify password is not logged
+      - server_password.diff.after.password != cloudscale_test_password
+
+- name: Test create server with password
+  cloudscale_server:
+    name: '{{ cloudscale_resource_prefix }}-test-password'
+    flavor: '{{ cloudscale_test_flavor }}'
+    image: '{{ cloudscale_test_image }}'
+    password: '{{ cloudscale_test_password }}'
+  register: server_password
+- name: Verify create server with password
+  assert:
+    that:
+      - server_password is changed
+      - server_password.state == 'running'
+      # Verify password is not logged
+      - server_password.diff.after.password != cloudscale_test_password
+
+- name: Test create server with password idempotence
+  cloudscale_server:
+    name: '{{ cloudscale_resource_prefix }}-test-password'
+    flavor: '{{ cloudscale_test_flavor }}'
+    image: '{{ cloudscale_test_image }}'
+    password: '{{ cloudscale_test_password }}'
+  register: server_password
+- name: Verify create server with password idempotence
+  assert:
+    that:
+      - server_password is not changed
+      - server_password.state == 'running'
+
 - name: Test create server failure without required parameters
   cloudscale_server:
     name: '{{ cloudscale_resource_prefix }}-test-failed'
@@ -255,7 +299,8 @@
   assert:
     that:
       - server_failed is failed
-      - "'missing required arguments' in server_failed.msg"
+      - "'Failure while calling the cloudscale.ch API with POST for \"servers\".' in server_failed.msg"
+      - "'This field is required.' in server_failed.fetch_url_info.body"
 
 - name: Test stop running server in check mode
   cloudscale_server: