mirror of
https://github.com/ansible-collections/google.cloud.git
synced 2025-07-26 06:31:32 -07:00
updated plugsins based on feedback, fixed linting and documentation errors.
This commit is contained in:
parent
3ce29db3ee
commit
40d2c9a7d5
1 changed files with 84 additions and 59 deletions
|
@ -1,25 +1,35 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/python
|
||||||
|
|
||||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt
|
||||||
|
# or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Documentation
|
# Documentation
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ["preview"], 'supported_by': 'community'}
|
ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ["preview"], 'supported_by': 'community'}
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
DOCUMENTATION = '''
|
||||||
---
|
---
|
||||||
module: gcp_secret_manager
|
module: gcp_secret_manager
|
||||||
description:
|
description:
|
||||||
- Simple create/delete of secrets
|
- Access secrets stored in Google Secrets Manager.
|
||||||
- add or delete secret versions
|
- Create new secrets.
|
||||||
- other features like etags, replication, annontation expected to be managed outside of Ansible
|
- Create new secret values.
|
||||||
|
- Add/remove versions of secrets.
|
||||||
|
- Please note that other features like etags, replication, annontation expected to be managed outside of Ansible.
|
||||||
|
short_description: Access and Update Google Cloud Secrets Manager objects
|
||||||
|
author: Dave Costakos @RedHat
|
||||||
requirements:
|
requirements:
|
||||||
- python >= 2.6
|
- python >= 2.6
|
||||||
- requests >= 2.18.4
|
- requests >= 2.18.4
|
||||||
- google-auth >= 1.3.0
|
- google-auth >= 1.3.0
|
||||||
options:
|
|
||||||
options:
|
options:
|
||||||
project:
|
project:
|
||||||
description:
|
description:
|
||||||
|
@ -57,6 +67,7 @@ options:
|
||||||
description:
|
description:
|
||||||
- Name of the secret to be used
|
- Name of the secret to be used
|
||||||
type: str
|
type: str
|
||||||
|
required: true
|
||||||
aliases:
|
aliases:
|
||||||
- key
|
- key
|
||||||
- secret
|
- secret
|
||||||
|
@ -69,7 +80,11 @@ options:
|
||||||
type: str
|
type: str
|
||||||
state:
|
state:
|
||||||
description:
|
description:
|
||||||
- 'absent' or 'present': whether the secret should exist
|
- whether the secret should exist
|
||||||
|
default: present
|
||||||
|
choices:
|
||||||
|
- absent
|
||||||
|
- present
|
||||||
type: str
|
type: str
|
||||||
return_value:
|
return_value:
|
||||||
description:
|
description:
|
||||||
|
@ -81,19 +96,31 @@ options:
|
||||||
description:
|
description:
|
||||||
- A version label to apply to the secret
|
- A version label to apply to the secret
|
||||||
- Default is "latest" which is the newest version of the secret
|
- Default is "latest" which is the newest version of the secret
|
||||||
- "all" is also acceptable on delete (which will delete all versions of a secret)
|
- The special "all" is also acceptable on delete (which will delete all versions of a secret)
|
||||||
type: str
|
type: str
|
||||||
default: 'latest'
|
default: latest
|
||||||
labels:
|
labels:
|
||||||
description:
|
description:
|
||||||
- A set of key-value pairs to assign as labels to asecret
|
- A set of key-value pairs to assign as labels to asecret
|
||||||
- only used in creation
|
- only used in creation
|
||||||
- Note that the "value" piece of a label must contain only readable chars
|
- Note that the "value" piece of a label must contain only readable chars
|
||||||
type: dict
|
type: dict
|
||||||
required: False
|
notes:
|
||||||
|
- 'API Reference: U(https://cloud.google.com/secret-manager/docs/reference/rests)'
|
||||||
|
- 'Official Documentation: U(https://cloud.google.com/secret-manager/docs/overview)'
|
||||||
|
- for authentication, you can set service_account_file using the C(GCP_SERVICE_ACCOUNT_FILE)
|
||||||
|
env variable.
|
||||||
|
- for authentication, you can set service_account_contents using the C(GCP_SERVICE_ACCOUNT_CONTENTS)
|
||||||
|
env variable.
|
||||||
|
- For authentication, you can set service_account_email using the C(GCP_SERVICE_ACCOUNT_EMAIL)
|
||||||
|
env variable.
|
||||||
|
- For authentication, you can set auth_kind using the C(GCP_AUTH_KIND) env variable.
|
||||||
|
- For authentication, you can set scopes using the C(GCP_SCOPES) env variable.
|
||||||
|
- Environment variables values will only be used if the playbook values are not set.
|
||||||
|
- The I(service_account_email) and I(service_account_file) options are mutually exclusive.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
EXAMPLES='''
|
EXAMPLES = r'''
|
||||||
- name: Create a new secret
|
- name: Create a new secret
|
||||||
google.cloud.gcp_secret_manager:
|
google.cloud.gcp_secret_manager:
|
||||||
name: secret_key
|
name: secret_key
|
||||||
|
@ -131,9 +158,15 @@ EXAMPLES='''
|
||||||
version: all
|
version: all
|
||||||
state: absent
|
state: absent
|
||||||
|
|
||||||
|
- name: Create a secret with labels
|
||||||
|
google.cloud.gcp_secret_manager:
|
||||||
|
name: secret_key
|
||||||
|
value: super_secret
|
||||||
|
labels:
|
||||||
|
key_name: "ansible_rox"
|
||||||
'''
|
'''
|
||||||
|
|
||||||
RETURN = '''
|
RETURN = r'''
|
||||||
resources:
|
resources:
|
||||||
description: List of resources
|
description: List of resources
|
||||||
returned: always
|
returned: always
|
||||||
|
@ -182,33 +215,35 @@ resources:
|
||||||
from ansible_collections.google.cloud.plugins.module_utils.gcp_utils import (
|
from ansible_collections.google.cloud.plugins.module_utils.gcp_utils import (
|
||||||
navigate_hash,
|
navigate_hash,
|
||||||
GcpSession,
|
GcpSession,
|
||||||
GcpModule,
|
GcpModule
|
||||||
GcpRequest,
|
|
||||||
remove_nones_from_dict,
|
|
||||||
replace_resource_dict,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
import json
|
|
||||||
# for decoding and validating secrets
|
# for decoding and validating secrets
|
||||||
|
import json
|
||||||
import base64
|
import base64
|
||||||
import binascii
|
import copy
|
||||||
|
|
||||||
|
|
||||||
def get_auth(module):
|
def get_auth(module):
|
||||||
return GcpSession(module, 'secret-manager')
|
return GcpSession(module, 'secret-manager')
|
||||||
|
|
||||||
|
|
||||||
def self_access_link(module):
|
def self_access_link(module):
|
||||||
return "https://secretmanager.googleapis.com/v1/projects/{project}/secrets/{name}/versions/{calc_version}:access".format(**module.params)
|
return "https://secretmanager.googleapis.com/v1/projects/{project}/secrets/{name}/versions/{calc_version}:access".format(**module.params)
|
||||||
|
|
||||||
|
|
||||||
def self_get_link(module):
|
def self_get_link(module):
|
||||||
return "https://secretmanager.googleapis.com/v1/projects/{project}/secrets/{name}/versions/{calc_version}".format(**module.params)
|
return "https://secretmanager.googleapis.com/v1/projects/{project}/secrets/{name}/versions/{calc_version}".format(**module.params)
|
||||||
|
|
||||||
|
|
||||||
def self_update_link(module):
|
def self_update_link(module):
|
||||||
return "https://secretmanager.googleapis.com/v1/projects/{project}/secrets/{name}/versions/{calc_version:version}".format(**module.params)
|
return "https://secretmanager.googleapis.com/v1/projects/{project}/secrets/{name}/versions/{calc_version:version}".format(**module.params)
|
||||||
|
|
||||||
|
|
||||||
def self_list_link(module):
|
def self_list_link(module):
|
||||||
return "https://secretmanager.googleapis.com/v1/projects/{project}/secrets/{name}/versions?filter=state:ENABLED".format(**module.params)
|
return "https://secretmanager.googleapis.com/v1/projects/{project}/secrets/{name}/versions?filter=state:ENABLED".format(**module.params)
|
||||||
|
|
||||||
|
|
||||||
def self_delete_link(module):
|
def self_delete_link(module):
|
||||||
return "https://secretmanager.googleapis.com/v1/projects/{project}/secrets/{name}".format(**module.params)
|
return "https://secretmanager.googleapis.com/v1/projects/{project}/secrets/{name}".format(**module.params)
|
||||||
|
|
||||||
|
@ -245,24 +280,12 @@ def fetch_resource(module, allow_not_found=True):
|
||||||
return None
|
return None
|
||||||
return merge_dicts(get_obj, access_obj)
|
return merge_dicts(get_obj, access_obj)
|
||||||
|
|
||||||
|
|
||||||
def merge_dicts(x, y):
|
def merge_dicts(x, y):
|
||||||
z = x.copy()
|
z = copy.deepcopy(x)
|
||||||
z.update(y)
|
z.update(y)
|
||||||
return z
|
return z
|
||||||
|
|
||||||
def snake_to_camel(snake):
|
|
||||||
result = ''
|
|
||||||
capitalize_next = False
|
|
||||||
for char in snake:
|
|
||||||
if char == '_':
|
|
||||||
capitalize_next = True
|
|
||||||
else:
|
|
||||||
if capitalize_next:
|
|
||||||
result += char.upper()
|
|
||||||
capitalize_next = False
|
|
||||||
else:
|
|
||||||
result += char
|
|
||||||
return str(result)
|
|
||||||
|
|
||||||
# create secret is a create call + an add version call
|
# create secret is a create call + an add version call
|
||||||
def create_secret(module):
|
def create_secret(module):
|
||||||
|
@ -278,6 +301,7 @@ def create_secret(module):
|
||||||
module.raise_for_status(post_response)
|
module.raise_for_status(post_response)
|
||||||
return update_secret(module)
|
return update_secret(module)
|
||||||
|
|
||||||
|
|
||||||
def update_secret(module):
|
def update_secret(module):
|
||||||
# build the payload
|
# build the payload
|
||||||
b64_value = base64.b64encode(module.params['value'].encode("utf-8")).decode("utf-8")
|
b64_value = base64.b64encode(module.params['value'].encode("utf-8")).decode("utf-8")
|
||||||
|
@ -290,12 +314,14 @@ def update_secret(module):
|
||||||
url = "https://secretmanager.googleapis.com/v1/projects/{project}/secrets/{name}:addVersion".format(**module.params)
|
url = "https://secretmanager.googleapis.com/v1/projects/{project}/secrets/{name}:addVersion".format(**module.params)
|
||||||
return return_if_object(module, auth.post(url, payload), False)
|
return return_if_object(module, auth.post(url, payload), False)
|
||||||
|
|
||||||
|
|
||||||
def list_secret_versions(module):
|
def list_secret_versions(module):
|
||||||
# filter by only enabled secrets
|
# filter by only enabled secrets
|
||||||
url = self_list_link(module)
|
url = self_list_link(module)
|
||||||
auth = get_auth(module)
|
auth = get_auth(module)
|
||||||
return return_if_object(module, auth.get(url), True)
|
return return_if_object(module, auth.get(url), True)
|
||||||
|
|
||||||
|
|
||||||
# technically we're destroying the version
|
# technically we're destroying the version
|
||||||
def delete_secret(module, destroy_all=False):
|
def delete_secret(module, destroy_all=False):
|
||||||
# delete secret does not take "latest" as a default version
|
# delete secret does not take "latest" as a default version
|
||||||
|
@ -309,6 +335,7 @@ def delete_secret(module, destroy_all=False):
|
||||||
url = self_get_link(module) + ":destroy"
|
url = self_get_link(module) + ":destroy"
|
||||||
return return_if_object(module, auth.post(url, {}), False)
|
return return_if_object(module, auth.post(url, {}), False)
|
||||||
|
|
||||||
|
|
||||||
def return_if_object(module, response, allow_not_found=False):
|
def return_if_object(module, response, allow_not_found=False):
|
||||||
# If not found, return nothing.
|
# If not found, return nothing.
|
||||||
if allow_not_found and response.status_code == 404:
|
if allow_not_found and response.status_code == 404:
|
||||||
|
@ -316,11 +343,11 @@ def return_if_object(module, response, allow_not_found=False):
|
||||||
|
|
||||||
if response.status_code == 409:
|
if response.status_code == 409:
|
||||||
module.params['info'] = "exists already"
|
module.params['info'] = "exists already"
|
||||||
return None;
|
return None
|
||||||
|
|
||||||
# probably a code error
|
# probably a code error
|
||||||
if response.status_code == 400:
|
if response.status_code == 400:
|
||||||
module.fail_json(msg=f"unexpected REST failure: {response.json()['error']}")
|
module.fail_json(msg="unexpected REST failure: %s" % response.json()['error'])
|
||||||
|
|
||||||
# If no content, return nothing.
|
# If no content, return nothing.
|
||||||
if response.status_code == 204:
|
if response.status_code == 204:
|
||||||
|
@ -365,7 +392,6 @@ def main():
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
if not module.params['scopes']:
|
if not module.params['scopes']:
|
||||||
module.params['scopes'] = ["https://www.googleapis.com/auth/cloud-platform"]
|
module.params['scopes'] = ["https://www.googleapis.com/auth/cloud-platform"]
|
||||||
|
|
||||||
|
@ -386,7 +412,7 @@ def main():
|
||||||
# fail, let the user know
|
# fail, let the user know
|
||||||
# that no secret could be created without a value to encrypt
|
# that no secret could be created without a value to encrypt
|
||||||
elif state == 'present':
|
elif state == 'present':
|
||||||
module.fail_json(msg="secret '{name}' not present in '{project}' and no value for the secret is provided".format(**module.params)),
|
module.fail_json(msg="secret '{name}' not present in '{project}' and no value for the secret is provided".format(**module.params))
|
||||||
|
|
||||||
# secret is absent, success
|
# secret is absent, success
|
||||||
else:
|
else:
|
||||||
|
@ -404,13 +430,13 @@ def main():
|
||||||
if "value" in fetch and module.params.get('value') is not None:
|
if "value" in fetch and module.params.get('value') is not None:
|
||||||
# Update secret
|
# Update secret
|
||||||
if fetch['value'] != module.params['value']:
|
if fetch['value'] != module.params['value']:
|
||||||
update = update_secret(module)
|
update_secret(module)
|
||||||
changed = True
|
changed = True
|
||||||
else:
|
else:
|
||||||
fetch['msg'] = "values identical, no need to update secret"
|
fetch['msg'] = "values identical, no need to update secret"
|
||||||
|
|
||||||
# pop value data if return_value == false
|
# pop value data if return_value == false
|
||||||
if module.params['return_value'] == False:
|
if module.params['return_value'] is False:
|
||||||
fetch.pop('value')
|
fetch.pop('value')
|
||||||
fetch.pop('payload')
|
fetch.pop('payload')
|
||||||
if "msg" in fetch:
|
if "msg" in fetch:
|
||||||
|
@ -426,4 +452,3 @@ def main():
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue