From eb839c94ea2231e02d7baacf36801a273fd57527 Mon Sep 17 00:00:00 2001 From: The Magician Date: Tue, 9 Jul 2019 11:35:15 -0700 Subject: [PATCH] Bug fixes for GCP modules (#58850) --- .../cloud/google/gcp_container_cluster.py | 105 +++++++++++++++++- .../google/gcp_container_cluster_facts.py | 2 +- .../google/gcp_iam_service_account_key.py | 3 +- .../modules/cloud/google/gcp_pubsub_topic.py | 25 ++++- .../cloud/google/gcp_pubsub_topic_facts.py | 9 ++ .../targets/gcp_iam_role/tasks/main.yml | 4 +- .../gcp_iam_service_account/tasks/main.yml | 4 +- .../targets/gcp_redis_instance/tasks/main.yml | 4 +- .../tasks/main.yml | 4 +- 9 files changed, 146 insertions(+), 14 deletions(-) diff --git a/lib/ansible/modules/cloud/google/gcp_container_cluster.py b/lib/ansible/modules/cloud/google/gcp_container_cluster.py index cefc78fee1f..70a140db719 100644 --- a/lib/ansible/modules/cloud/google/gcp_container_cluster.py +++ b/lib/ansible/modules/cloud/google/gcp_container_cluster.py @@ -409,6 +409,20 @@ options: aliases: - zone version_added: 2.8 + kubectl_path: + description: + - The path that the kubectl config file will be written to. + - The file will not be created if this path is unset. + - Any existing file at this path will be completely overwritten. + - This requires the PyYaml library. + required: false + version_added: 2.9 + kubectl_context: + description: + - The name of the context for the kubectl config file. Will default to the cluster + name. + required: false + version_added: 2.9 extends_documentation_fragment: gcp ''' @@ -619,7 +633,7 @@ masterAuth: description: - The password to use for HTTP basic authentication to the master endpoint. Because the master endpoint is open to the Internet, you should create a strong - password. + password with a minimum of 16 characters. returned: success type: str clientCertificateConfig: @@ -924,6 +938,20 @@ location: - The location where the cluster is deployed. returned: success type: str +kubectlPath: + description: + - The path that the kubectl config file will be written to. + - The file will not be created if this path is unset. + - Any existing file at this path will be completely overwritten. + - This requires the PyYaml library. + returned: success + type: str +kubectlContext: + description: + - The name of the context for the kubectl config file. Will default to the cluster + name. + returned: success + type: str ''' ################################################################################ @@ -1000,6 +1028,8 @@ def main(): enable_tpu=dict(type='bool'), tpu_ipv4_cidr_block=dict(type='str'), location=dict(required=True, type='str', aliases=['zone']), + kubectl_path=dict(type='str'), + kubectl_context=dict(type='str'), ) ) @@ -1029,6 +1059,8 @@ def main(): else: fetch = {} + if module.params.get('kubectl_path'): + Kubectl(module).write_file() fetch.update({'changed': changed}) module.exit_json(**fetch) @@ -1231,6 +1263,77 @@ def delete_default_node_pool(module): return wait_for_operation(module, auth.delete(link)) +class Kubectl(object): + def __init__(self, module): + self.module = module + + """ + Writes a kubectl config file + kubectl_path must be set or this will fail. + """ + + def write_file(self): + try: + import yaml + except ImportError: + self.module.fail_json(msg="Please install the pyyaml module") + + with open(self.module.params['kubectl_path'], 'w') as f: + f.write(yaml.dump(self._contents())) + + """ + Returns the contents of a kubectl file + """ + + def _contents(self): + token = self._auth_token() + endpoint = "https://%s" % self.fetch["endpoint"] + context = self.module.params.get('kubectl_context') + if not context: + context = self.module.params['name'] + + return { + 'apiVersion': 'v1', + 'clusters': [ + {'name': context, 'cluster': {'certificate-authority-data': str(self.fetch['masterAuth']['clusterCaCertificate']), 'server': endpoint}} + ], + 'contexts': [{'name': context, 'context': {'cluster': context, 'user': context}}], + 'current-context': context, + 'kind': 'Config', + 'preferences': {}, + 'users': [ + { + 'name': context, + 'user': { + 'auth-provider': { + 'config': { + 'access-token': token, + 'cmd-args': 'config config-helper --format=json', + 'cmd-path': '/usr/lib64/google-cloud-sdk/bin/gcloud', + 'expiry-key': '{.credential.token_expiry}', + 'token-key': '{.credential.access_token}', + }, + 'name': 'gcp', + }, + 'username': str(self.fetch['masterAuth']['username']), + 'password': str(self.fetch['masterAuth']['password']), + }, + } + ], + } + + """ + Returns the auth token used in kubectl + This also sets the 'fetch' variable used in creating the kubectl + """ + + def _auth_token(self): + auth = GcpSession(self.module, 'auth') + response = auth.get(self_link(self.module)) + self.fetch = response.json() + return response.request.headers['authorization'].split(' ')[1] + + class ClusterNodeconfig(object): def __init__(self, request, module): self.module = module diff --git a/lib/ansible/modules/cloud/google/gcp_container_cluster_facts.py b/lib/ansible/modules/cloud/google/gcp_container_cluster_facts.py index 4aeea01c708..0e9aab5df6b 100644 --- a/lib/ansible/modules/cloud/google/gcp_container_cluster_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_container_cluster_facts.py @@ -256,7 +256,7 @@ resources: description: - The password to use for HTTP basic authentication to the master endpoint. Because the master endpoint is open to the Internet, you should create - a strong password. + a strong password with a minimum of 16 characters. returned: success type: str clientCertificateConfig: diff --git a/lib/ansible/modules/cloud/google/gcp_iam_service_account_key.py b/lib/ansible/modules/cloud/google/gcp_iam_service_account_key.py index b923274ff42..4ad8cd4054f 100644 --- a/lib/ansible/modules/cloud/google/gcp_iam_service_account_key.py +++ b/lib/ansible/modules/cloud/google/gcp_iam_service_account_key.py @@ -152,6 +152,7 @@ path: ################################################################################ from ansible.module_utils.gcp_utils import navigate_hash, GcpSession, GcpModule, GcpRequest, replace_resource_dict +from ansible.module_utils._text import to_native import json import os import mimetypes @@ -204,7 +205,7 @@ def create(module): auth = GcpSession(module, 'iam') json_content = return_if_object(module, auth.post(self_link(module), resource_to_request(module))) with open(module.params['path'], 'w') as f: - private_key_contents = base64.b64decode(json_content['privateKeyData']) + private_key_contents = to_native(base64.b64decode(json_content['privateKeyData'])) f.write(private_key_contents) diff --git a/lib/ansible/modules/cloud/google/gcp_pubsub_topic.py b/lib/ansible/modules/cloud/google/gcp_pubsub_topic.py index 35297020fbc..46038c6a229 100644 --- a/lib/ansible/modules/cloud/google/gcp_pubsub_topic.py +++ b/lib/ansible/modules/cloud/google/gcp_pubsub_topic.py @@ -51,6 +51,14 @@ options: description: - Name of the topic. required: true + kms_key_name: + description: + - The resource name of the Cloud KMS CryptoKey to be used to protect access to + messsages published on this topic. Your project's PubSub service account (`service-{{PROJECT_NUMBER}}@gcp-sa-pubsub.iam.gserviceaccount.com`) + must have `roles/cloudkms.cryptoKeyEncrypterDecrypter` to use this feature. + - The expected format is `projects/*/locations/*/keyRings/*/cryptoKeys/*` . + required: false + version_added: 2.9 labels: description: - A set of key/value label pairs to assign to this Topic. @@ -78,6 +86,14 @@ name: - Name of the topic. returned: success type: str +kmsKeyName: + description: + - The resource name of the Cloud KMS CryptoKey to be used to protect access to messsages + published on this topic. Your project's PubSub service account (`service-{{PROJECT_NUMBER}}@gcp-sa-pubsub.iam.gserviceaccount.com`) + must have `roles/cloudkms.cryptoKeyEncrypterDecrypter` to use this feature. + - The expected format is `projects/*/locations/*/keyRings/*/cryptoKeys/*` . + returned: success + type: str labels: description: - A set of key/value label pairs to assign to this Topic. @@ -102,7 +118,10 @@ def main(): module = GcpModule( argument_spec=dict( - state=dict(default='present', choices=['present', 'absent'], type='str'), name=dict(required=True, type='str'), labels=dict(type='dict') + state=dict(default='present', choices=['present', 'absent'], type='str'), + name=dict(required=True, type='str'), + kms_key_name=dict(type='str'), + labels=dict(type='dict'), ) ) @@ -162,7 +181,7 @@ def delete(module, link): def resource_to_request(module): - request = {u'name': module.params.get('name'), u'labels': module.params.get('labels')} + request = {u'name': module.params.get('name'), u'kmsKeyName': module.params.get('kms_key_name'), u'labels': module.params.get('labels')} request = encode_request(request, module) return_vals = {} for k, v in request.items(): @@ -230,7 +249,7 @@ def is_different(module, response): # Remove unnecessary properties from the response. # This is for doing comparisons with Ansible's current parameters. def response_to_hash(module, response): - return {u'name': module.params.get('name'), u'labels': response.get(u'labels')} + return {u'name': module.params.get('name'), u'kmsKeyName': module.params.get('kms_key_name'), u'labels': response.get(u'labels')} def decode_request(response, module): diff --git a/lib/ansible/modules/cloud/google/gcp_pubsub_topic_facts.py b/lib/ansible/modules/cloud/google/gcp_pubsub_topic_facts.py index 8e3edf53c92..7b794fc8ec8 100644 --- a/lib/ansible/modules/cloud/google/gcp_pubsub_topic_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_pubsub_topic_facts.py @@ -63,6 +63,15 @@ resources: - Name of the topic. returned: success type: str + kmsKeyName: + description: + - The resource name of the Cloud KMS CryptoKey to be used to protect access + to messsages published on this topic. Your project's PubSub service account + (`service-{{PROJECT_NUMBER}}@gcp-sa-pubsub.iam.gserviceaccount.com`) must + have `roles/cloudkms.cryptoKeyEncrypterDecrypter` to use this feature. + - The expected format is `projects/*/locations/*/keyRings/*/cryptoKeys/*` . + returned: success + type: str labels: description: - A set of key/value label pairs to assign to this Topic. diff --git a/test/integration/targets/gcp_iam_role/tasks/main.yml b/test/integration/targets/gcp_iam_role/tasks/main.yml index a8f6da8efae..8745bd24b60 100644 --- a/test/integration/targets/gcp_iam_role/tasks/main.yml +++ b/test/integration/targets/gcp_iam_role/tasks/main.yml @@ -56,7 +56,7 @@ - name: verify that command succeeded assert: that: - - results['resources'] | length >= 1 + - results['resources'] | map(attribute='name') | select("match", ".*myCustomRole2.*") | list | length == 1 # ---------------------------------------------------------------------------- - name: create a role that already exists gcp_iam_role: @@ -106,7 +106,7 @@ - name: verify that command succeeded assert: that: - - results['resources'] | length == 0 + - results['resources'] | map(attribute='name') | select("match", ".*myCustomRole2.*") | list | length == 0 # ---------------------------------------------------------------------------- - name: delete a role that does not exist gcp_iam_role: diff --git a/test/integration/targets/gcp_iam_service_account/tasks/main.yml b/test/integration/targets/gcp_iam_service_account/tasks/main.yml index 52f4af9791d..aa6dab354f2 100644 --- a/test/integration/targets/gcp_iam_service_account/tasks/main.yml +++ b/test/integration/targets/gcp_iam_service_account/tasks/main.yml @@ -46,7 +46,7 @@ - name: verify that command succeeded assert: that: - - results['resources'] | length >= 1 + - results['resources'] | map(attribute='name') | select("match", ".*{{ sa_name }}.*") | list | length == 1 # ---------------------------------------------------------------------------- - name: create a service account that already exists gcp_iam_service_account: @@ -86,7 +86,7 @@ - name: verify that command succeeded assert: that: - - results['resources'] | length == 0 + - results['resources'] | map(attribute='name') | select("match", ".*{{ sa_name }}.*") | list | length == 0 # ---------------------------------------------------------------------------- - name: delete a service account that does not exist gcp_iam_service_account: diff --git a/test/integration/targets/gcp_redis_instance/tasks/main.yml b/test/integration/targets/gcp_redis_instance/tasks/main.yml index 5fd54188f3c..a0856f6f61a 100644 --- a/test/integration/targets/gcp_redis_instance/tasks/main.yml +++ b/test/integration/targets/gcp_redis_instance/tasks/main.yml @@ -73,7 +73,7 @@ - name: verify that command succeeded assert: that: - - results['resources'] | length >= 1 + - results['resources'] | map(attribute='name') | select("match", ".*instance37.*") | list | length == 1 # ---------------------------------------------------------------------------- - name: create a instance that already exists gcp_redis_instance: @@ -132,7 +132,7 @@ - name: verify that command succeeded assert: that: - - results['resources'] | length == 0 + - results['resources'] | map(attribute='name') | select("match", ".*instance37.*") | list | length == 0 # ---------------------------------------------------------------------------- - name: delete a instance that does not exist gcp_redis_instance: diff --git a/test/integration/targets/gcp_resourcemanager_project/tasks/main.yml b/test/integration/targets/gcp_resourcemanager_project/tasks/main.yml index d98ab5e6ba6..b594d69ec89 100644 --- a/test/integration/targets/gcp_resourcemanager_project/tasks/main.yml +++ b/test/integration/targets/gcp_resourcemanager_project/tasks/main.yml @@ -50,7 +50,7 @@ - name: verify that command succeeded assert: that: - - results['resources'] | length >= 1 + - results['resources'] | map(attribute='name') | select("match", ".*My Sample Project.*") | list | length == 1 # ---------------------------------------------------------------------------- - name: create a project that already exists gcp_resourcemanager_project: @@ -94,7 +94,7 @@ - name: verify that command succeeded assert: that: - - results['resources'] | length == 0 + - results['resources'] | map(attribute='name') | select("match", ".*My Sample Project.*") | list | length == 0 # ---------------------------------------------------------------------------- - name: delete a project that does not exist gcp_resourcemanager_project: