diff options
Diffstat (limited to 'ansible_collections/community/okd/plugins/modules')
13 files changed, 3312 insertions, 0 deletions
diff --git a/ansible_collections/community/okd/plugins/modules/k8s.py b/ansible_collections/community/okd/plugins/modules/k8s.py new file mode 100644 index 000000000..c3b8d1b66 --- /dev/null +++ b/ansible_collections/community/okd/plugins/modules/k8s.py @@ -0,0 +1,308 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Chris Houseknecht <@chouseknecht> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +# STARTREMOVE (downstream) +DOCUMENTATION = r''' + +module: k8s + +short_description: Manage OpenShift objects + +author: + - "Chris Houseknecht (@chouseknecht)" + - "Fabian von Feilitzsch (@fabianvf)" + +description: + - Use the Kubernetes Python client to perform CRUD operations on K8s objects. + - Pass the object definition from a source file or inline. See examples for reading + files and using Jinja templates or vault-encrypted files. + - Access to the full range of K8s APIs. + - Use the M(kubernetes.core.k8s_info) module to obtain a list of items about an object of type C(kind). + - Authenticate using either a config file, certificates, password or token. + - Supports check mode. + - Optimized for OKD/OpenShift Kubernetes flavors. + +extends_documentation_fragment: + - kubernetes.core.k8s_name_options + - kubernetes.core.k8s_resource_options + - kubernetes.core.k8s_auth_options + - kubernetes.core.k8s_wait_options + - kubernetes.core.k8s_delete_options + +options: + state: + description: + - Determines if an object should be created, patched, or deleted. When set to C(present), an object will be + created, if it does not already exist. If set to C(absent), an existing object will be deleted. If set to + C(present), an existing object will be patched, if its attributes differ from those specified using + I(resource_definition) or I(src). + - C(patched) state is an existing resource that has a given patch applied. If the resource doesn't exist, silently skip it (do not raise an error). + type: str + default: present + choices: [ absent, present, patched ] + force: + description: + - If set to C(yes), and I(state) is C(present), an existing object will be replaced. + type: bool + default: no + merge_type: + description: + - Whether to override the default patch merge approach with a specific type. By default, the strategic + merge will typically be used. + - For example, Custom Resource Definitions typically aren't updatable by the usual strategic merge. You may + want to use C(merge) if you see "strategic merge patch format is not supported" + - See U(https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment) + - If more than one merge_type is given, the merge_types will be tried in order + - Defaults to C(['strategic-merge', 'merge']), which is ideal for using the same parameters + on resource kinds that combine Custom Resources and built-in resources. + - mutually exclusive with C(apply) + - I(merge_type=json) is deprecated and will be removed in version 3.0.0. Please use M(kubernetes.core.k8s_json_patch) instead. + choices: + - json + - merge + - strategic-merge + type: list + elements: str + validate: + description: + - how (if at all) to validate the resource definition against the kubernetes schema. + Requires the kubernetes-validate python module + suboptions: + fail_on_error: + description: whether to fail on validation errors. + type: bool + version: + description: version of Kubernetes to validate against. defaults to Kubernetes server version + type: str + strict: + description: whether to fail when passing unexpected properties + default: True + type: bool + type: dict + append_hash: + description: + - Whether to append a hash to a resource name for immutability purposes + - Applies only to ConfigMap and Secret resources + - The parameter will be silently ignored for other resource kinds + - The full definition of an object is needed to generate the hash - this means that deleting an object created with append_hash + will only work if the same object is passed with state=absent (alternatively, just use state=absent with the name including + the generated hash and append_hash=no) + type: bool + default: false + apply: + description: + - C(apply) compares the desired resource definition with the previously supplied resource definition, + ignoring properties that are automatically generated + - C(apply) works better with Services than 'force=yes' + - mutually exclusive with C(merge_type) + type: bool + default: false + template: + description: + - Provide a valid YAML template definition file for an object when creating or updating. + - Value can be provided as string or dictionary. + - Mutually exclusive with C(src) and C(resource_definition). + - Template files needs to be present on the Ansible Controller's file system. + - Additional parameters can be specified using dictionary. + - 'Valid additional parameters - ' + - 'C(newline_sequence) (str): Specify the newline sequence to use for templating files. + valid choices are "\n", "\r", "\r\n". Default value "\n".' + - 'C(block_start_string) (str): The string marking the beginning of a block. + Default value "{%".' + - 'C(block_end_string) (str): The string marking the end of a block. + Default value "%}".' + - 'C(variable_start_string) (str): The string marking the beginning of a print statement. + Default value "{{".' + - 'C(variable_end_string) (str): The string marking the end of a print statement. + Default value "}}".' + - 'C(trim_blocks) (bool): Determine when newlines should be removed from blocks. When set to C(yes) the first newline + after a block is removed (block, not variable tag!). Default value is true.' + - 'C(lstrip_blocks) (bool): Determine when leading spaces and tabs should be stripped. + When set to C(yes) leading spaces and tabs are stripped from the start of a line to a block. + This functionality requires Jinja 2.7 or newer. Default value is false.' + type: raw + version_added: '2.0.0' + continue_on_error: + description: + - Whether to continue on creation/deletion errors when multiple resources are defined. + - This has no effect on the validation step which is controlled by the C(validate.fail_on_error) parameter. + type: bool + default: False + version_added: 2.0.0 + +requirements: + - "python >= 3.6" + - "kubernetes >= 12.0.0" + - "PyYAML >= 3.11" +''' + +EXAMPLES = r''' +- name: Create a k8s namespace + community.okd.k8s: + name: testing + api_version: v1 + kind: Namespace + state: present + +- name: Create a Service object from an inline definition + community.okd.k8s: + state: present + definition: + apiVersion: v1 + kind: Service + metadata: + name: web + namespace: testing + labels: + app: galaxy + service: web + spec: + selector: + app: galaxy + service: web + ports: + - protocol: TCP + targetPort: 8000 + name: port-8000-tcp + port: 8000 + +- name: Remove an existing Service object + community.okd.k8s: + state: absent + api_version: v1 + kind: Service + namespace: testing + name: web + +# Passing the object definition from a file + +- name: Create a Deployment by reading the definition from a local file + community.okd.k8s: + state: present + src: /testing/deployment.yml + +- name: >- + Read definition file from the Ansible controller file system. + If the definition file has been encrypted with Ansible Vault it will automatically be decrypted. + community.okd.k8s: + state: present + definition: "{{ lookup('file', '/testing/deployment.yml') | from_yaml }}" + +- name: Read definition file from the Ansible controller file system after Jinja templating + community.okd.k8s: + state: present + definition: "{{ lookup('template', '/testing/deployment.yml') | from_yaml }}" + +- name: fail on validation errors + community.okd.k8s: + state: present + definition: "{{ lookup('template', '/testing/deployment.yml') | from_yaml }}" + validate: + fail_on_error: yes + +- name: warn on validation errors, check for unexpected properties + community.okd.k8s: + state: present + definition: "{{ lookup('template', '/testing/deployment.yml') | from_yaml }}" + validate: + fail_on_error: no + strict: yes +''' + +RETURN = r''' +result: + description: + - The created, patched, or otherwise present object. Will be empty in the case of a deletion. + returned: success + type: complex + contains: + api_version: + description: The versioned schema of this representation of an object. + returned: success + type: str + kind: + description: Represents the REST resource this object represents. + returned: success + type: str + metadata: + description: Standard object metadata. Includes name, namespace, annotations, labels, etc. + returned: success + type: complex + spec: + description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind). + returned: success + type: complex + status: + description: Current status details for the object. + returned: success + type: complex + items: + description: Returned only when multiple yaml documents are passed to src or resource_definition + returned: when resource_definition or src contains list of objects + type: list + duration: + description: elapsed time of task in seconds + returned: when C(wait) is true + type: int + sample: 48 + error: + description: error while trying to create/delete the object. + returned: error + type: complex +''' +# ENDREMOVE (downstream) + +from ansible_collections.kubernetes.core.plugins.module_utils.args_common import ( + NAME_ARG_SPEC, RESOURCE_ARG_SPEC, AUTH_ARG_SPEC, WAIT_ARG_SPEC, DELETE_OPTS_ARG_SPEC +) + + +def validate_spec(): + return dict( + fail_on_error=dict(type='bool'), + version=dict(), + strict=dict(type='bool', default=True) + ) + + +def argspec(): + argument_spec = {} + argument_spec.update(NAME_ARG_SPEC) + argument_spec.update(RESOURCE_ARG_SPEC) + argument_spec.update(AUTH_ARG_SPEC) + argument_spec.update(WAIT_ARG_SPEC) + argument_spec['merge_type'] = dict(type='list', elements='str', choices=['json', 'merge', 'strategic-merge']) + argument_spec['validate'] = dict(type='dict', default=None, options=validate_spec()) + argument_spec['append_hash'] = dict(type='bool', default=False) + argument_spec['apply'] = dict(type='bool', default=False) + argument_spec['template'] = dict(type='raw', default=None) + argument_spec['delete_options'] = dict(type='dict', default=None, options=DELETE_OPTS_ARG_SPEC) + argument_spec['continue_on_error'] = dict(type='bool', default=False) + argument_spec['state'] = dict(default='present', choices=['present', 'absent', 'patched']) + argument_spec['force'] = dict(type='bool', default=False) + return argument_spec + + +def main(): + mutually_exclusive = [ + ('resource_definition', 'src'), + ('merge_type', 'apply'), + ('template', 'resource_definition'), + ('template', 'src'), + ] + + from ansible_collections.community.okd.plugins.module_utils.k8s import OKDRawModule + module = OKDRawModule(argument_spec=argspec(), supports_check_mode=True, mutually_exclusive=mutually_exclusive) + module.run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/okd/plugins/modules/openshift_adm_groups_sync.py b/ansible_collections/community/okd/plugins/modules/openshift_adm_groups_sync.py new file mode 100644 index 000000000..66b0fbb15 --- /dev/null +++ b/ansible_collections/community/okd/plugins/modules/openshift_adm_groups_sync.py @@ -0,0 +1,224 @@ +#!/usr/bin/python + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +# STARTREMOVE (downstream) +DOCUMENTATION = r""" + +module: openshift_adm_groups_sync + +short_description: Sync OpenShift Groups with records from an external provider. + +version_added: "2.1.0" + +author: + - Aubin Bikouo (@abikouo) + +description: + - In order to sync/prune OpenShift Group records with those from an external provider, determine which Groups you wish to sync + and where their records live. + - Analogous to `oc adm prune groups` and `oc adm group sync`. + - LDAP sync configuration file syntax can be found here + U(https://docs.openshift.com/container-platform/4.9/authentication/ldap-syncing.html). + - The bindPassword attribute of the LDAP sync configuration is expected to be a string, + please use ansible-vault encryption to secure this information. + +extends_documentation_fragment: + - kubernetes.core.k8s_auth_options + +options: + state: + description: + - Determines if the group should be sync when set to C(present) or pruned when set to C(absent). + type: str + default: present + choices: [ absent, present ] + type: + description: + - which groups allow and deny list entries refer to. + type: str + default: ldap + choices: [ ldap, openshift ] + sync_config: + description: + - Provide a valid YAML definition of an LDAP sync configuration. + type: dict + aliases: + - config + - src + required: True + deny_groups: + description: + - Denied groups, could be openshift group name or LDAP group dn value. + - When parameter C(type) is set to I(ldap) this should contains only LDAP group definition + like I(cn=developers,ou=groups,ou=rfc2307,dc=ansible,dc=redhat). + - The elements specified in this list will override the ones specified in C(allow_groups). + type: list + elements: str + default: [] + allow_groups: + description: + - Allowed groups, could be openshift group name or LDAP group dn value. + - When parameter C(type) is set to I(ldap) this should contains only LDAP group definition + like I(cn=developers,ou=groups,ou=rfc2307,dc=ansible,dc=redhat). + type: list + elements: str + default: [] + +requirements: + - python >= 3.6 + - kubernetes >= 12.0.0 + - python-ldap +""" + +EXAMPLES = r""" +# Prune all orphaned groups +- name: Prune all orphan groups + openshift_adm_groups_sync: + state: absent + src: "{{ lookup('file', '/path/to/ldap-sync-config.yaml') | from_yaml }}" + +# Prune all orphaned groups from a list of specific groups specified in allow_groups +- name: Prune all orphan groups from a list of specific groups specified in allow_groups + openshift_adm_groups_sync: + state: absent + src: "{{ lookup('file', '/path/to/ldap-sync-config.yaml') | from_yaml }}" + allow_groups: + - cn=developers,ou=groups,ou=rfc2307,dc=ansible,dc=redhat + - cn=developers,ou=groups,ou=rfc2307,dc=ansible,dc=redhat + +# Sync all groups from an LDAP server +- name: Sync all groups from an LDAP server + openshift_adm_groups_sync: + src: + kind: LDAPSyncConfig + apiVersion: v1 + url: ldap://localhost:1390 + insecure: true + bindDN: cn=admin,dc=example,dc=org + bindPassword: adminpassword + rfc2307: + groupsQuery: + baseDN: "cn=admins,ou=groups,dc=example,dc=org" + scope: sub + derefAliases: never + filter: (objectClass=*) + pageSize: 0 + groupUIDAttribute: dn + groupNameAttributes: [ cn ] + groupMembershipAttributes: [ member ] + usersQuery: + baseDN: "ou=users,dc=example,dc=org" + scope: sub + derefAliases: never + pageSize: 0 + userUIDAttribute: dn + userNameAttributes: [ mail ] + tolerateMemberNotFoundErrors: true + tolerateMemberOutOfScopeErrors: true + +# Sync all groups except the ones from the deny_groups from an LDAP server +- name: Sync all groups from an LDAP server using deny_groups + openshift_adm_groups_sync: + src: "{{ lookup('file', '/path/to/ldap-sync-config.yaml') | from_yaml }}" + deny_groups: + - cn=developers,ou=groups,ou=rfc2307,dc=ansible,dc=redhat + - cn=developers,ou=groups,ou=rfc2307,dc=ansible,dc=redhat + +# Sync all OpenShift Groups that have been synced previously with an LDAP server +- name: Sync all OpenShift Groups that have been synced previously with an LDAP server + openshift_adm_groups_sync: + src: "{{ lookup('file', '/path/to/ldap-sync-config.yaml') | from_yaml }}" + type: openshift +""" + + +RETURN = r""" +builds: + description: + - The groups that were created, updated or deleted + returned: success + type: list + elements: dict + sample: [ + { + "apiVersion": "user.openshift.io/v1", + "kind": "Group", + "metadata": { + "annotations": { + "openshift.io/ldap.sync-time": "2021-12-17T12:20:28.125282", + "openshift.io/ldap.uid": "cn=developers,ou=groups,ou=rfc2307,dc=ansible,dc=redhat", + "openshift.io/ldap.url": "localhost:1390" + }, + "creationTimestamp": "2021-12-17T11:09:49Z", + "labels": { + "openshift.io/ldap.host": "localhost" + }, + "managedFields": [{ + "apiVersion": "user.openshift.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:openshift.io/ldap.sync-time": {}, + "f:openshift.io/ldap.uid": {}, + "f:openshift.io/ldap.url": {} + }, + "f:labels": { + ".": {}, + "f:openshift.io/ldap.host": {} + } + }, + "f:users": {} + }, + "manager": "OpenAPI-Generator", + "operation": "Update", + "time": "2021-12-17T11:09:49Z" + }], + "name": "developers", + "resourceVersion": "2014696", + "uid": "8dc211cb-1544-41e1-96b1-efffeed2d7d7" + }, + "users": ["jordanbulls@ansible.org"] + } + ] +""" +# ENDREMOVE (downstream) + +import copy +import traceback + +from ansible_collections.kubernetes.core.plugins.module_utils.args_common import AUTH_ARG_SPEC + + +def argument_spec(): + args = copy.deepcopy(AUTH_ARG_SPEC) + args.update( + dict( + state=dict(type='str', choices=['absent', 'present'], default='present'), + type=dict(type='str', choices=['ldap', 'openshift'], default='ldap'), + sync_config=dict(type='dict', aliases=['config', 'src'], required=True), + deny_groups=dict(type='list', elements='str', default=[]), + allow_groups=dict(type='list', elements='str', default=[]), + ) + ) + return args + + +def main(): + from ansible_collections.community.okd.plugins.module_utils.openshift_groups import ( + OpenshiftGroupsSync + ) + + module = OpenshiftGroupsSync(argument_spec=argument_spec(), supports_check_mode=True) + module.run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/okd/plugins/modules/openshift_adm_migrate_template_instances.py b/ansible_collections/community/okd/plugins/modules/openshift_adm_migrate_template_instances.py new file mode 100644 index 000000000..05d5563cd --- /dev/null +++ b/ansible_collections/community/okd/plugins/modules/openshift_adm_migrate_template_instances.py @@ -0,0 +1,371 @@ +#!/usr/bin/python + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +# STARTREMOVE (downstream) +DOCUMENTATION = r""" +module: openshift_adm_migrate_template_instances +short_description: Update TemplateInstances to point to the latest group-version-kinds +version_added: "2.2.0" +author: Alina Buzachis (@alinabuzachis) +description: + - Update TemplateInstances to point to the latest group-version-kinds. + - Analogous to C(oc adm migrate template-instances). +extends_documentation_fragment: + - kubernetes.core.k8s_auth_options + - kubernetes.core.k8s_wait_options +options: + namespace: + description: + - The namespace that the template can be found in. + - If no namespace if specified, migrate objects in all namespaces. + type: str +requirements: + - python >= 3.6 + - kubernetes >= 12.0.0 +""" + +EXAMPLES = r""" + - name: Migrate TemplateInstances in namespace=test + community.okd.openshift_adm_migrate_template_instances: + namespace: test + register: _result + + - name: Migrate TemplateInstances in all namespaces + community.okd.openshift_adm_migrate_template_instances: + register: _result +""" + +RETURN = r""" +result: + description: + - List with all TemplateInstances that have been migrated. + type: list + returned: success + elements: dict + sample: [ + { + "apiVersion": "template.openshift.io/v1", + "kind": "TemplateInstance", + "metadata": { + "creationTimestamp": "2021-11-10T11:12:09Z", + "finalizers": [ + "template.openshift.io/finalizer" + ], + "managedFields": [ + { + "apiVersion": "template.openshift.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:spec": { + "f:template": { + "f:metadata": { + "f:name": {} + }, + "f:objects": {}, + "f:parameters": {} + } + } + }, + "manager": "kubectl-create", + "operation": "Update", + "time": "2021-11-10T11:12:09Z" + }, + { + "apiVersion": "template.openshift.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:finalizers": { + ".": {}, + "v:\"template.openshift.io/finalizer\"": {} + } + }, + "f:status": { + "f:conditions": {} + } + }, + "manager": "openshift-controller-manager", + "operation": "Update", + "time": "2021-11-10T11:12:09Z" + }, + { + "apiVersion": "template.openshift.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + "f:objects": {} + } + }, + "manager": "OpenAPI-Generator", + "operation": "Update", + "time": "2021-11-10T11:12:33Z" + } + ], + "name": "demo", + "namespace": "test", + "resourceVersion": "545370", + "uid": "09b795d7-7f07-4d94-bf0f-2150ee66f88d" + }, + "spec": { + "requester": { + "groups": [ + "system:masters", + "system:authenticated" + ], + "username": "system:admin" + }, + "template": { + "metadata": { + "creationTimestamp": null, + "name": "template" + }, + "objects": [ + { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "labels": { + "foo": "bar" + }, + "name": "secret" + } + }, + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "deployment" + }, + "spec": { + "replicas": 0, + "selector": { + "matchLabels": { + "key": "value" + } + }, + "template": { + "metadata": { + "labels": { + "key": "value" + } + }, + "spec": { + "containers": [ + { + "image": "k8s.gcr.io/e2e-test-images/agnhost:2.32", + "name": "hello-openshift" + } + ] + } + } + } + }, + { + "apiVersion": "v1", + "kind": "Route", + "metadata": { + "name": "route" + }, + "spec": { + "to": { + "name": "foo" + } + } + } + ], + "parameters": [ + { + "name": "NAME", + "value": "${NAME}" + } + ] + } + }, + "status": { + "conditions": [ + { + "lastTransitionTime": "2021-11-10T11:12:09Z", + "message": "", + "reason": "Created", + "status": "True", + "type": "Ready" + } + ], + "objects": [ + { + "ref": { + "apiVersion": "v1", + "kind": "Secret", + "name": "secret", + "namespace": "test", + "uid": "33fad364-6d47-4f9c-9e51-92cba5602a57" + } + }, + { + "ref": { + "apiVersion": "apps/v1", + "kind": "Deployment", + "name": "deployment", + "namespace": "test", + "uid": "3b527f88-42a1-4811-9e2f-baad4e4d8807" + } + }, + { + "ref": { + "apiVersion": "route.openshift.io/v1.Route", + "kind": "Route", + "name": "route", + "namespace": "test", + "uid": "5b5411de-8769-4e27-ba52-6781630e4008" + } + } + ] + } + }, + ... + ] +""" +# ENDREMOVE (downstream) + +from ansible.module_utils._text import to_native + +from ansible_collections.community.okd.plugins.module_utils.openshift_common import AnsibleOpenshiftModule + +try: + from kubernetes.dynamic.exceptions import DynamicApiError +except ImportError: + pass + +from ansible_collections.kubernetes.core.plugins.module_utils.args_common import ( + AUTH_ARG_SPEC, + WAIT_ARG_SPEC, +) + +transforms = { + "Build": "build.openshift.io/v1", + "BuildConfig": "build.openshift.io/v1", + "DeploymentConfig": "apps.openshift.io/v1", + "Route": "route.openshift.io/v1", +} + + +class OpenShiftMigrateTemplateInstances(AnsibleOpenshiftModule): + def __init__(self, **kwargs): + super(OpenShiftMigrateTemplateInstances, self).__init__(**kwargs) + + def patch_template_instance(self, resource, templateinstance): + result = None + + try: + result = resource.status.patch(templateinstance) + except Exception as exc: + self.fail_json( + msg="Failed to migrate TemplateInstance {0} due to: {1}".format( + templateinstance["metadata"]["name"], to_native(exc) + ) + ) + + return result.to_dict() + + @staticmethod + def perform_migrations(templateinstances): + ti_list = [] + ti_to_be_migrated = [] + + ti_list = ( + templateinstances.get("kind") == "TemplateInstanceList" + and templateinstances.get("items") + or [templateinstances] + ) + + for ti_elem in ti_list: + objects = ti_elem["status"].get("objects") + if objects: + for i, obj in enumerate(objects): + object_type = obj["ref"]["kind"] + if ( + object_type in transforms.keys() + and obj["ref"].get("apiVersion") != transforms[object_type] + ): + ti_elem["status"]["objects"][i]["ref"][ + "apiVersion" + ] = transforms[object_type] + ti_to_be_migrated.append(ti_elem) + + return ti_to_be_migrated + + def execute_module(self): + templateinstances = None + namespace = self.params.get("namespace") + results = {"changed": False, "result": []} + + resource = self.find_resource( + "templateinstances", "template.openshift.io/v1", fail=True + ) + + if namespace: + # Get TemplateInstances from a provided namespace + try: + templateinstances = resource.get(namespace=namespace).to_dict() + except DynamicApiError as exc: + self.fail_json( + msg="Failed to retrieve TemplateInstances in namespace '{0}': {1}".format( + namespace, exc.body + ), + error=exc.status, + status=exc.status, + reason=exc.reason, + ) + except Exception as exc: + self.fail_json( + msg="Failed to retrieve TemplateInstances in namespace '{0}': {1}".format( + namespace, to_native(exc) + ), + error="", + status="", + reason="", + ) + else: + # Get TemplateInstances from all namespaces + templateinstances = resource.get().to_dict() + + ti_to_be_migrated = self.perform_migrations(templateinstances) + + if ti_to_be_migrated: + if self.check_mode: + self.exit_json( + **{"changed": True, "result": ti_to_be_migrated} + ) + else: + for ti_elem in ti_to_be_migrated: + results["result"].append( + self.patch_template_instance(resource, ti_elem) + ) + results["changed"] = True + + self.exit_json(**results) + + +def argspec(): + argument_spec = {} + argument_spec.update(AUTH_ARG_SPEC) + argument_spec.update(WAIT_ARG_SPEC) + argument_spec["namespace"] = dict(type="str") + + return argument_spec + + +def main(): + argument_spec = argspec() + module = OpenShiftMigrateTemplateInstances(argument_spec=argument_spec, supports_check_mode=True) + module.run_module() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/community/okd/plugins/modules/openshift_adm_prune_auth.py b/ansible_collections/community/okd/plugins/modules/openshift_adm_prune_auth.py new file mode 100644 index 000000000..a9833fa50 --- /dev/null +++ b/ansible_collections/community/okd/plugins/modules/openshift_adm_prune_auth.py @@ -0,0 +1,132 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, Red Hat +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +# STARTREMOVE (downstream) +DOCUMENTATION = r''' + +module: openshift_adm_prune_auth + +short_description: Removes references to the specified roles, clusterroles, users, and groups + +version_added: "2.2.0" + +author: + - Aubin Bikouo (@abikouo) + +description: + - This module allow administrators to remove references to the specified roles, clusterroles, users, and groups. + - Analogous to C(oc adm prune auth). + +extends_documentation_fragment: + - kubernetes.core.k8s_auth_options + +options: + resource: + description: + - The specified resource to remove. + choices: + - roles + - clusterroles + - users + - groups + type: str + required: True + name: + description: + - Use to specify an object name to remove. + - Mutually exclusive with option I(label_selectors). + - If neither I(name) nor I(label_selectors) are specified, prune all resources in the namespace. + type: str + namespace: + description: + - Use to specify an object namespace. + - Ignored when I(resource) is set to C(clusterroles). + type: str + label_selectors: + description: + - Selector (label query) to filter on. + - Mutually exclusive with option I(name). + type: list + elements: str + +requirements: + - python >= 3.6 + - kubernetes >= 12.0.0 +''' + +EXAMPLES = r''' +- name: Prune all roles from default namespace + openshift_adm_prune_auth: + resource: roles + namespace: testing + +- name: Prune clusterroles using label selectors + openshift_adm_prune_auth: + resource: roles + namespace: testing + label_selectors: + - phase=production +''' + + +RETURN = r''' +cluster_role_binding: + type: list + description: list of cluster role binding deleted. + returned: always +role_binding: + type: list + description: list of role binding deleted. + returned: I(resource=users) or I(resource=groups) or I(resource=clusterroles) +security_context_constraints: + type: list + description: list of Security Context Constraints deleted. + returned: I(resource=users) or I(resource=groups) +authorization: + type: list + description: list of OAuthClientAuthorization deleted. + returned: I(resource=users) +group: + type: list + description: list of Security Context Constraints deleted. + returned: I(resource=users) +''' +# ENDREMOVE (downstream) + +import copy + +from ansible_collections.kubernetes.core.plugins.module_utils.args_common import AUTH_ARG_SPEC + + +def argument_spec(): + args = copy.deepcopy(AUTH_ARG_SPEC) + args.update( + dict( + resource=dict(type='str', required=True, choices=['roles', 'clusterroles', 'users', 'groups']), + namespace=dict(type='str'), + name=dict(type='str'), + label_selectors=dict(type='list', elements='str'), + ) + ) + return args + + +def main(): + + from ansible_collections.community.okd.plugins.module_utils.openshift_adm_prune_auth import ( + OpenShiftAdmPruneAuth) + + module = OpenShiftAdmPruneAuth(argument_spec=argument_spec(), + mutually_exclusive=[("name", "label_selectors")], + supports_check_mode=True) + module.run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/okd/plugins/modules/openshift_adm_prune_builds.py b/ansible_collections/community/okd/plugins/modules/openshift_adm_prune_builds.py new file mode 100644 index 000000000..b0b831e6f --- /dev/null +++ b/ansible_collections/community/okd/plugins/modules/openshift_adm_prune_builds.py @@ -0,0 +1,124 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, Red Hat +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +# STARTREMOVE (downstream) +DOCUMENTATION = r''' + +module: openshift_adm_prune_builds + +short_description: Prune old completed and failed builds + +version_added: "2.3.0" + +author: + - Aubin Bikouo (@abikouo) + +description: + - This module allow administrators to delete old completed and failed builds. + - Analogous to C(oc adm prune builds). + +extends_documentation_fragment: + - kubernetes.core.k8s_auth_options + +options: + namespace: + description: + - Use to specify namespace for builds to be deleted. + type: str + keep_younger_than: + description: + - Specify the minimum age (in minutes) of a Build for it to be considered a candidate for pruning. + type: int + orphans: + description: + - If C(true), prune all builds whose associated BuildConfig no longer exists and whose status is + complete, failed, error, or cancelled. + type: bool + default: False + +requirements: + - python >= 3.6 + - kubernetes >= 12.0.0 +''' + +EXAMPLES = r''' +# Run deleting older completed and failed builds and also including +# all builds whose associated BuildConfig no longer exists +- name: Run delete orphan Builds + community.okd.openshift_adm_prune_builds: + orphans: True + +# Run deleting older completed and failed builds keep younger than 2hours +- name: Run delete builds, keep younger than 2h + community.okd.openshift_adm_prune_builds: + keep_younger_than: 120 + +# Run deleting builds from specific namespace +- name: Run delete builds from namespace + community.okd.openshift_adm_prune_builds: + namespace: testing_namespace +''' + +RETURN = r''' +builds: + description: + - The builds that were deleted + returned: success + type: complex + contains: + api_version: + description: The versioned schema of this representation of an object. + returned: success + type: str + kind: + description: Represents the REST resource this object represents. + returned: success + type: str + metadata: + description: Standard object metadata. Includes name, namespace, annotations, labels, etc. + returned: success + type: dict + spec: + description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind). + returned: success + type: dict + status: + description: Current status details for the object. + returned: success + type: dict +''' +# ENDREMOVE (downstream) + +import copy + +from ansible_collections.kubernetes.core.plugins.module_utils.args_common import AUTH_ARG_SPEC + + +def argument_spec(): + args = copy.deepcopy(AUTH_ARG_SPEC) + args.update( + dict( + namespace=dict(type='str'), + keep_younger_than=dict(type='int'), + orphans=dict(type='bool', default=False), + ) + ) + return args + + +def main(): + + from ansible_collections.community.okd.plugins.module_utils.openshift_builds import OpenShiftPruneBuilds + + module = OpenShiftPruneBuilds(argument_spec=argument_spec(), supports_check_mode=True) + module.run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/okd/plugins/modules/openshift_adm_prune_deployments.py b/ansible_collections/community/okd/plugins/modules/openshift_adm_prune_deployments.py new file mode 100644 index 000000000..bdef18460 --- /dev/null +++ b/ansible_collections/community/okd/plugins/modules/openshift_adm_prune_deployments.py @@ -0,0 +1,100 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, Red Hat +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +# STARTREMOVE (downstream) +DOCUMENTATION = r''' + +module: openshift_adm_prune_deployments + +short_description: Remove old completed and failed deployment configs + +version_added: "2.2.0" + +author: + - Aubin Bikouo (@abikouo) + +description: + - This module allow administrators to remove old completed and failed deployment configs. + - Analogous to C(oc adm prune deployments). + +extends_documentation_fragment: + - kubernetes.core.k8s_auth_options + +options: + namespace: + description: + - Use to specify namespace for deployments to be deleted. + type: str + keep_younger_than: + description: + - Specify the minimum age (in minutes) of a deployment for it to be considered a candidate for pruning. + type: int + orphans: + description: + - If C(true), prune all deployments where the associated DeploymentConfig no longer exists, + the status is complete or failed, and the replica size is C(0). + type: bool + default: False + +requirements: + - python >= 3.6 + - kubernetes >= 12.0.0 +''' + +EXAMPLES = r''' +- name: Prune Deployments from testing namespace + community.okd.openshift_adm_prune_deployments: + namespace: testing + +- name: Prune orphans deployments, keep younger than 2hours + community.okd.openshift_adm_prune_deployments: + orphans: True + keep_younger_than: 120 +''' + + +RETURN = r''' +replication_controllers: + type: list + description: list of replication controllers candidate for pruning. + returned: always +''' +# ENDREMOVE (downstream) + +import copy + +try: + from ansible_collections.kubernetes.core.plugins.module_utils.args_common import AUTH_ARG_SPEC +except ImportError as e: + pass + + +def argument_spec(): + args = copy.deepcopy(AUTH_ARG_SPEC) + args.update( + dict( + namespace=dict(type='str',), + keep_younger_than=dict(type='int',), + orphans=dict(type='bool', default=False), + ) + ) + return args + + +def main(): + + from ansible_collections.community.okd.plugins.module_utils.openshift_adm_prune_deployments import ( + OpenShiftAdmPruneDeployment) + + module = OpenShiftAdmPruneDeployment(argument_spec=argument_spec(), supports_check_mode=True) + module.run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/okd/plugins/modules/openshift_adm_prune_images.py b/ansible_collections/community/okd/plugins/modules/openshift_adm_prune_images.py new file mode 100644 index 000000000..d470fa871 --- /dev/null +++ b/ansible_collections/community/okd/plugins/modules/openshift_adm_prune_images.py @@ -0,0 +1,315 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, Red Hat +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +# STARTREMOVE (downstream) +DOCUMENTATION = r''' + +module: openshift_adm_prune_images + +short_description: Remove unreferenced images + +version_added: "2.2.0" + +author: + - Aubin Bikouo (@abikouo) + +description: + - This module allow administrators to remove references images. + - Note that if the C(namespace) is specified, only references images on Image stream for the corresponding + namespace will be candidate for prune if only they are not used or references in another Image stream from + another namespace. + - Analogous to C(oc adm prune images). + +extends_documentation_fragment: + - kubernetes.core.k8s_auth_options + +options: + namespace: + description: + - Use to specify namespace for objects. + type: str + all_images: + description: + - Include images that were imported from external registries as candidates for pruning. + - If pruned, all the mirrored objects associated with them will also be removed from the integrated registry. + type: bool + default: True + keep_younger_than: + description: + - Specify the minimum age (in minutes) of an image and its referrers for it to be considered a candidate for pruning. + type: int + prune_over_size_limit: + description: + - Specify if images which are exceeding LimitRanges specified in the same namespace, + should be considered for pruning. + type: bool + default: False + registry_url: + description: + - The address to use when contacting the registry, instead of using the default value. + - This is useful if you can't resolve or reach the default registry but you do have an + alternative route that works. + - Particular transport protocol can be enforced using '<scheme>://' prefix. + type: str + registry_ca_cert: + description: + - Path to a CA certificate used to contact registry. The full certificate chain must be provided to + avoid certificate validation errors. + type: path + registry_validate_certs: + description: + - Whether or not to verify the API server's SSL certificates. Can also be specified via K8S_AUTH_VERIFY_SSL + environment variable. + type: bool + prune_registry: + description: + - If set to I(False), the prune operation will clean up image API objects, but + none of the associated content in the registry is removed. + type: bool + default: True + ignore_invalid_refs: + description: + - If set to I(True), the pruning process will ignore all errors while parsing image references. + - This means that the pruning process will ignore the intended connection between the object and the referenced image. + - As a result an image may be incorrectly deleted as unused. + type: bool + default: False +requirements: + - python >= 3.6 + - kubernetes >= 12.0.0 + - docker-image-py +''' + +EXAMPLES = r''' +# Prune if only images and their referrers were more than an hour old +- name: Prune image with referrer been more than an hour old + community.okd.openshift_adm_prune_images: + keep_younger_than: 60 + +# Remove images exceeding currently set limit ranges +- name: Remove images exceeding currently set limit ranges + community.okd.openshift_adm_prune_images: + prune_over_size_limit: true + +# Force the insecure http protocol with the particular registry host name +- name: Prune images using custom registry + community.okd.openshift_adm_prune_images: + registry_url: http://registry.example.org + registry_validate_certs: false +''' + + +RETURN = r''' +updated_image_streams: + description: + - The images streams updated. + returned: success + type: list + elements: dict + sample: [ + { + "apiVersion": "image.openshift.io/v1", + "kind": "ImageStream", + "metadata": { + "annotations": { + "openshift.io/image.dockerRepositoryCheck": "2021-12-07T07:55:30Z" + }, + "creationTimestamp": "2021-12-07T07:55:30Z", + "generation": 1, + "name": "python", + "namespace": "images", + "resourceVersion": "1139215", + "uid": "443bad2c-9fd4-4c8f-8a24-3eca4426b07f" + }, + "spec": { + "lookupPolicy": { + "local": false + }, + "tags": [ + { + "annotations": null, + "from": { + "kind": "DockerImage", + "name": "python:3.8.12" + }, + "generation": 1, + "importPolicy": { + "insecure": true + }, + "name": "3.8.12", + "referencePolicy": { + "type": "Source" + } + } + ] + }, + "status": { + "dockerImageRepository": "image-registry.openshift-image-registry.svc:5000/images/python", + "publicDockerImageRepository": "default-route-openshift-image-registry.apps-crc.testing/images/python", + "tags": [] + } + }, + ... + ] +deleted_images: + description: + - The images deleted. + returned: success + type: list + elements: dict + sample: [ + { + "apiVersion": "image.openshift.io/v1", + "dockerImageLayers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "name": "sha256:5e0b432e8ba9d9029a000e627840b98ffc1ed0c5172075b7d3e869be0df0fe9b", + "size": 54932878 + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "name": "sha256:a84cfd68b5cea612a8343c346bfa5bd6c486769010d12f7ec86b23c74887feb2", + "size": 5153424 + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "name": "sha256:e8b8f2315954535f1e27cd13d777e73da4a787b0aebf4241d225beff3c91cbb1", + "size": 10871995 + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "name": "sha256:0598fa43a7e793a76c198e8d45d8810394e1cfc943b2673d7fcf5a6fdc4f45b3", + "size": 54567844 + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "name": "sha256:83098237b6d3febc7584c1f16076a32ac01def85b0d220ab46b6ebb2d6e7d4d4", + "size": 196499409 + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "name": "sha256:b92c73d4de9a6a8f6b96806a04857ab33cf6674f6411138603471d744f44ef55", + "size": 6290769 + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "name": "sha256:ef9b6ee59783b84a6ec0c8b109c409411ab7c88fa8c53fb3760b5fde4eb0aa07", + "size": 16812698 + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "name": "sha256:c1f6285e64066d36477a81a48d3c4f1dc3c03dddec9e72d97da13ba51bca0d68", + "size": 234 + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "name": "sha256:a0ee7333301245b50eb700f96d9e13220cdc31871ec9d8e7f0ff7f03a17c6fb3", + "size": 2349241 + } + ], + "dockerImageManifestMediaType": "application/vnd.docker.distribution.manifest.v2+json", + "dockerImageMetadata": { + "Architecture": "amd64", + "Config": { + "Cmd": [ + "python3" + ], + "Env": [ + "PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "LANG=C.UTF-8", + "GPG_KEY=E3FF2839C048B25C084DEBE9B26995E310250568", + "PYTHON_VERSION=3.8.12", + "PYTHON_PIP_VERSION=21.2.4", + "PYTHON_SETUPTOOLS_VERSION=57.5.0", + "PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/3cb8888cc2869620f57d5d2da64da38f516078c7/public/get-pip.py", + "PYTHON_GET_PIP_SHA256=c518250e91a70d7b20cceb15272209a4ded2a0c263ae5776f129e0d9b5674309" + ], + "Image": "sha256:cc3a2931749afa7dede97e32edbbe3e627b275c07bf600ac05bc0dc22ef203de" + }, + "Container": "b43fcf5052feb037f6d204247d51ac8581d45e50f41c6be2410d94b5c3a3453d", + "ContainerConfig": { + "Cmd": [ + "/bin/sh", + "-c", + "#(nop) ", + "CMD [\"python3\"]" + ], + "Env": [ + "PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "LANG=C.UTF-8", + "GPG_KEY=E3FF2839C048B25C084DEBE9B26995E310250568", + "PYTHON_VERSION=3.8.12", + "PYTHON_PIP_VERSION=21.2.4", + "PYTHON_SETUPTOOLS_VERSION=57.5.0", + "PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/3cb8888cc2869620f57d5d2da64da38f516078c7/public/get-pip.py", + "PYTHON_GET_PIP_SHA256=c518250e91a70d7b20cceb15272209a4ded2a0c263ae5776f129e0d9b5674309" + ], + "Hostname": "b43fcf5052fe", + "Image": "sha256:cc3a2931749afa7dede97e32edbbe3e627b275c07bf600ac05bc0dc22ef203de" + }, + "Created": "2021-12-03T01:53:41Z", + "DockerVersion": "20.10.7", + "Id": "sha256:f746089c9d02d7126bbe829f788e093853a11a7f0421049267a650d52bbcac37", + "Size": 347487141, + "apiVersion": "image.openshift.io/1.0", + "kind": "DockerImage" + }, + "dockerImageMetadataVersion": "1.0", + "dockerImageReference": "python@sha256:a874dcabc74ca202b92b826521ff79dede61caca00ceab0b65024e895baceb58", + "kind": "Image", + "metadata": { + "annotations": { + "image.openshift.io/dockerLayersOrder": "ascending" + }, + "creationTimestamp": "2021-12-07T07:55:30Z", + "name": "sha256:a874dcabc74ca202b92b826521ff79dede61caca00ceab0b65024e895baceb58", + "resourceVersion": "1139214", + "uid": "33be6ab4-af79-4f44-a0fd-4925bd473c1f" + } + }, + ... + ] +''' +# ENDREMOVE (downstream) + +import copy + +from ansible_collections.kubernetes.core.plugins.module_utils.args_common import AUTH_ARG_SPEC + + +def argument_spec(): + args = copy.deepcopy(AUTH_ARG_SPEC) + args.update( + dict( + namespace=dict(type='str'), + all_images=dict(type='bool', default=True), + keep_younger_than=dict(type='int'), + prune_over_size_limit=dict(type='bool', default=False), + registry_url=dict(type='str'), + registry_validate_certs=dict(type='bool'), + registry_ca_cert=dict(type='path'), + prune_registry=dict(type='bool', default=True), + ignore_invalid_refs=dict(type='bool', default=False), + ) + ) + return args + + +def main(): + + from ansible_collections.community.okd.plugins.module_utils.openshift_adm_prune_images import ( + OpenShiftAdmPruneImages + ) + + module = OpenShiftAdmPruneImages(argument_spec=argument_spec(), supports_check_mode=True) + module.run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/okd/plugins/modules/openshift_auth.py b/ansible_collections/community/okd/plugins/modules/openshift_auth.py new file mode 100644 index 000000000..422018cc5 --- /dev/null +++ b/ansible_collections/community/okd/plugins/modules/openshift_auth.py @@ -0,0 +1,392 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2018, KubeVirt Team <@kubevirt> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = r''' + +module: openshift_auth + +short_description: Authenticate to OpenShift clusters which require an explicit login step + +version_added: "0.2.0" + +author: + - KubeVirt Team (@kubevirt) + - Fabian von Feilitzsch (@fabianvf) + +description: + - This module handles authenticating to OpenShift clusters requiring I(explicit) authentication procedures, + meaning ones where a client logs in (obtains an authentication token), performs API operations using said + token and then logs out (revokes the token). + - On the other hand a popular configuration for username+password authentication is one utilizing HTTP Basic + Auth, which does not involve any additional login/logout steps (instead login credentials can be attached + to each and every API call performed) and as such is handled directly by the C(k8s) module (and other + resource–specific modules) by utilizing the C(host), C(username) and C(password) parameters. Please + consult your preferred module's documentation for more details. + +options: + state: + description: + - If set to I(present) connect to the API server using the URL specified in C(host) and attempt to log in. + - If set to I(absent) attempt to log out by revoking the authentication token specified in C(api_key). + default: present + choices: + - present + - absent + type: str + host: + description: + - Provide a URL for accessing the API server. + required: true + type: str + username: + description: + - Provide a username for authenticating with the API server. + type: str + password: + description: + - Provide a password for authenticating with the API server. + type: str + ca_cert: + description: + - "Path to a CA certificate file used to verify connection to the API server. The full certificate chain + must be provided to avoid certificate validation errors." + aliases: [ ssl_ca_cert ] + type: path + validate_certs: + description: + - "Whether or not to verify the API server's SSL certificates." + type: bool + default: true + aliases: [ verify_ssl ] + api_key: + description: + - When C(state) is set to I(absent), this specifies the token to revoke. + type: str + +requirements: + - python >= 3.6 + - urllib3 + - requests + - requests-oauthlib +''' + +EXAMPLES = r''' +- hosts: localhost + module_defaults: + group/community.okd.okd: + host: https://k8s.example.com/ + ca_cert: ca.pem + tasks: + - block: + # It's good practice to store login credentials in a secure vault and not + # directly in playbooks. + - include_vars: openshift_passwords.yml + + - name: Log in (obtain access token) + community.okd.openshift_auth: + username: admin + password: "{{ openshift_admin_password }}" + register: openshift_auth_results + + # Previous task provides the token/api_key, while all other parameters + # are taken from module_defaults + - name: Get a list of all pods from any namespace + kubernetes.core.k8s_info: + api_key: "{{ openshift_auth_results.openshift_auth.api_key }}" + kind: Pod + register: pod_list + + always: + - name: If login succeeded, try to log out (revoke access token) + when: openshift_auth_results.openshift_auth.api_key is defined + community.okd.openshift_auth: + state: absent + api_key: "{{ openshift_auth_results.openshift_auth.api_key }}" +''' + +# Returned value names need to match k8s modules parameter names, to make it +# easy to pass returned values of openshift_auth to other k8s modules. +# Discussion: https://github.com/ansible/ansible/pull/50807#discussion_r248827899 +RETURN = r''' +openshift_auth: + description: OpenShift authentication facts. + returned: success + type: complex + contains: + api_key: + description: Authentication token. + returned: success + type: str + host: + description: URL for accessing the API server. + returned: success + type: str + ca_cert: + description: Path to a CA certificate file used to verify connection to the API server. + returned: success + type: str + validate_certs: + description: "Whether or not to verify the API server's SSL certificates." + returned: success + type: bool + username: + description: Username for authenticating with the API server. + returned: success + type: str +k8s_auth: + description: Same as returned openshift_auth. Kept only for backwards compatibility + returned: success + type: complex + contains: + api_key: + description: Authentication token. + returned: success + type: str + host: + description: URL for accessing the API server. + returned: success + type: str + ca_cert: + description: Path to a CA certificate file used to verify connection to the API server. + returned: success + type: str + validate_certs: + description: "Whether or not to verify the API server's SSL certificates." + returned: success + type: bool + username: + description: Username for authenticating with the API server. + returned: success + type: str +''' + + +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six.moves.urllib_parse import urlparse, parse_qs, urlencode +from urllib.parse import urljoin + +from base64 import urlsafe_b64encode +import hashlib + +# 3rd party imports +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + +try: + from requests_oauthlib import OAuth2Session + HAS_REQUESTS_OAUTH = True +except ImportError: + HAS_REQUESTS_OAUTH = False + +try: + from urllib3.util import make_headers + HAS_URLLIB3 = True +except ImportError: + HAS_URLLIB3 = False + + +K8S_AUTH_ARG_SPEC = { + 'state': { + 'default': 'present', + 'choices': ['present', 'absent'], + }, + 'host': {'required': True}, + 'username': {}, + 'password': {'no_log': True}, + 'ca_cert': {'type': 'path', 'aliases': ['ssl_ca_cert']}, + 'validate_certs': { + 'type': 'bool', + 'default': True, + 'aliases': ['verify_ssl'] + }, + 'api_key': {'no_log': True}, +} + + +def get_oauthaccesstoken_objectname_from_token(token_name): + + """ + openshift convert the access token to an OAuthAccessToken resource name using the algorithm + https://github.com/openshift/console/blob/9f352ba49f82ad693a72d0d35709961428b43b93/pkg/server/server.go#L609-L613 + """ + + sha256Prefix = "sha256~" + content = token_name.strip(sha256Prefix) + + b64encoded = urlsafe_b64encode(hashlib.sha256(content.encode()).digest()).rstrip(b'=') + return sha256Prefix + b64encoded.decode("utf-8") + + +class OpenShiftAuthModule(AnsibleModule): + def __init__(self): + AnsibleModule.__init__( + self, + argument_spec=K8S_AUTH_ARG_SPEC, + required_if=[ + ('state', 'present', ['username', 'password']), + ('state', 'absent', ['api_key']), + ] + ) + + if not HAS_REQUESTS: + self.fail("This module requires the python 'requests' package. Try `pip install requests`.") + + if not HAS_REQUESTS_OAUTH: + self.fail("This module requires the python 'requests-oauthlib' package. Try `pip install requests-oauthlib`.") + + if not HAS_URLLIB3: + self.fail("This module requires the python 'urllib3' package. Try `pip install urllib3`.") + + def execute_module(self): + state = self.params.get('state') + verify_ssl = self.params.get('validate_certs') + ssl_ca_cert = self.params.get('ca_cert') + + self.auth_username = self.params.get('username') + self.auth_password = self.params.get('password') + self.auth_api_key = self.params.get('api_key') + self.con_host = self.params.get('host') + + # python-requests takes either a bool or a path to a ca file as the 'verify' param + if verify_ssl and ssl_ca_cert: + self.con_verify_ca = ssl_ca_cert # path + else: + self.con_verify_ca = verify_ssl # bool + + # Get needed info to access authorization APIs + self.openshift_discover() + + changed = False + result = dict() + if state == 'present': + new_api_key = self.openshift_login() + result = dict( + host=self.con_host, + validate_certs=verify_ssl, + ca_cert=ssl_ca_cert, + api_key=new_api_key, + username=self.auth_username, + ) + else: + changed = self.openshift_logout() + + # return k8s_auth as well for backwards compatibility + self.exit_json(changed=changed, openshift_auth=result, k8s_auth=result) + + def openshift_discover(self): + url = urljoin(self.con_host, '.well-known/oauth-authorization-server') + ret = requests.get(url, verify=self.con_verify_ca) + + if ret.status_code != 200: + self.fail_request("Couldn't find OpenShift's OAuth API", method='GET', url=url, + reason=ret.reason, status_code=ret.status_code) + + try: + oauth_info = ret.json() + + self.openshift_auth_endpoint = oauth_info['authorization_endpoint'] + self.openshift_token_endpoint = oauth_info['token_endpoint'] + except Exception: + self.fail_json(msg="Something went wrong discovering OpenShift OAuth details.", + exception=traceback.format_exc()) + + def openshift_login(self): + os_oauth = OAuth2Session(client_id='openshift-challenging-client') + authorization_url, state = os_oauth.authorization_url(self.openshift_auth_endpoint, + state="1", code_challenge_method='S256') + auth_headers = make_headers(basic_auth='{0}:{1}'.format(self.auth_username, self.auth_password)) + + # Request authorization code using basic auth credentials + ret = os_oauth.get( + authorization_url, + headers={'X-Csrf-Token': state, 'authorization': auth_headers.get('authorization')}, + verify=self.con_verify_ca, + allow_redirects=False + ) + + if ret.status_code != 302: + self.fail_request("Authorization failed.", method='GET', url=authorization_url, + reason=ret.reason, status_code=ret.status_code) + + # In here we have `code` and `state`, I think `code` is the important one + qwargs = {} + for k, v in parse_qs(urlparse(ret.headers['Location']).query).items(): + qwargs[k] = v[0] + qwargs['grant_type'] = 'authorization_code' + + # Using authorization code given to us in the Location header of the previous request, request a token + ret = os_oauth.post( + self.openshift_token_endpoint, + headers={ + 'Accept': 'application/json', + 'Content-Type': 'application/x-www-form-urlencoded', + # This is just base64 encoded 'openshift-challenging-client:' + 'Authorization': 'Basic b3BlbnNoaWZ0LWNoYWxsZW5naW5nLWNsaWVudDo=' + }, + data=urlencode(qwargs), + verify=self.con_verify_ca + ) + + if ret.status_code != 200: + self.fail_request("Failed to obtain an authorization token.", method='POST', + url=self.openshift_token_endpoint, + reason=ret.reason, status_code=ret.status_code) + + return ret.json()['access_token'] + + def openshift_logout(self): + + name = get_oauthaccesstoken_objectname_from_token(self.auth_api_key) + headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': "Bearer {0}".format(self.auth_api_key) + } + + url = "{0}/apis/oauth.openshift.io/v1/useroauthaccesstokens/{1}".format(self.con_host, name) + json = { + "apiVersion": "oauth.openshift.io/v1", + "kind": "DeleteOptions", + "gracePeriodSeconds": 0 + } + + ret = requests.delete(url, json=json, verify=self.con_verify_ca, headers=headers) + if ret.status_code != 200: + self.fail_json( + msg="Couldn't delete user oauth access token '{0}' due to: {1}".format(name, ret.json().get("message")), + status_code=ret.status_code + ) + + return True + + def fail(self, msg=None): + self.fail_json(msg=msg) + + def fail_request(self, msg, **kwargs): + req_info = {} + for k, v in kwargs.items(): + req_info['req_' + k] = v + self.fail_json(msg=msg, **req_info) + + +def main(): + module = OpenShiftAuthModule() + try: + module.execute_module() + except Exception as e: + module.fail_json(msg=str(e), exception=traceback.format_exc()) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/okd/plugins/modules/openshift_build.py b/ansible_collections/community/okd/plugins/modules/openshift_build.py new file mode 100644 index 000000000..1259a102c --- /dev/null +++ b/ansible_collections/community/okd/plugins/modules/openshift_build.py @@ -0,0 +1,260 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, Red Hat +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +# STARTREMOVE (downstream) +DOCUMENTATION = r''' + +module: openshift_build + +short_description: Start a new build or Cancel running, pending, or new builds. + +version_added: "2.3.0" + +author: + - Aubin Bikouo (@abikouo) + +description: + - This module starts a new build from the provided build config or build name. + - This module also cancel a new, pending or running build by requesting a graceful shutdown of the build. + There may be a delay between requesting the build and the time the build is terminated. + - This can also restart a new build when the current is cancelled. + - Analogous to C(oc cancel-build) and C(oc start-build). + +extends_documentation_fragment: + - kubernetes.core.k8s_auth_options + +options: + state: + description: + - Determines if a Build should be started ,cancelled or restarted. + - When set to C(restarted) a new build will be created after the current build is cancelled. + choices: + - started + - cancelled + - restarted + default: started + type: str + build_name: + description: + - Specify the name of a build which should be re-run. + - Mutually exclusive with parameter I(build_config_name). + type: str + build_config_name: + description: + - Specify the name of a build config from which a new build will be run. + - Mutually exclusive with parameter I(build_name). + type: str + namespace: + description: + - Specify the namespace for the build or the build config. + type: str + required: True + build_args: + description: + - Specify a list of key-value pair to pass to Docker during the build. + type: list + elements: dict + suboptions: + name: + description: + - docker build argument name. + type: str + required: true + value: + description: + - docker build argument value. + type: str + required: true + commit: + description: + - Specify the source code commit identifier the build should use; + requires a build based on a Git repository. + type: str + env_vars: + description: + - Specify a list of key-value pair for an environment variable to set for the build container. + type: list + elements: dict + suboptions: + name: + description: + - Environment variable name. + type: str + required: true + value: + description: + - Environment variable value. + type: str + required: true + incremental: + description: + - Overrides the incremental setting in a source-strategy build, ignored if not specified. + type: bool + no_cache: + description: + - Overrides the noCache setting in a docker-strategy build, ignored if not specified. + type: bool + wait: + description: + - When C(state=started), specify whether to wait for a build to complete + and exit with a non-zero return code if the build fails. + - When I(state=cancelled), specify whether to wait for a build phase to be Cancelled. + default: False + type: bool + wait_sleep: + description: + - Number of seconds to sleep between checks. + - Ignored if C(wait=false). + default: 5 + type: int + wait_timeout: + description: + - How long in seconds to wait for a build to complete. + - Ignored if C(wait=false). + default: 120 + type: int + build_phases: + description: + - List of state for build to cancel. + - Ignored when C(state=started). + type: list + elements: str + choices: + - New + - Pending + - Running + default: [] + +requirements: + - python >= 3.6 + - kubernetes >= 12.0.0 +''' + +EXAMPLES = r''' +# Starts build from build config default/hello-world +- name: Starts build from build config + community.okd.openshift_build: + namespace: default + build_config_name: hello-world + +# Starts build from a previous build "default/hello-world-1" +- name: Starts build from a previous build + community.okd.openshift_build: + namespace: default + build_name: hello-world-1 + +# Cancel the build with the given name +- name: Cancel build from default namespace + community.okd.openshift_build: + namespace: "default" + build_name: ruby-build-1 + state: cancelled + +# Cancel the named build and create a new one with the same parameters +- name: Cancel build from default namespace and create a new one + community.okd.openshift_build: + namespace: "default" + build_name: ruby-build-1 + state: restarted + +# Cancel all builds created from 'ruby-build' build configuration that are in 'new' state +- name: Cancel build from default namespace and create a new one + community.okd.openshift_build: + namespace: "default" + build_config_name: ruby-build + build_phases: + - New + state: cancelled +''' + +RETURN = r''' +builds: + description: + - The builds that were started/cancelled. + returned: success + type: complex + contains: + api_version: + description: The versioned schema of this representation of an object. + returned: success + type: str + kind: + description: Represents the REST resource this object represents. + returned: success + type: str + metadata: + description: Standard object metadata. Includes name, namespace, annotations, labels, etc. + returned: success + type: dict + spec: + description: Specific attributes of the build. + returned: success + type: dict + status: + description: Current status details for the object. + returned: success + type: dict +''' +# ENDREMOVE (downstream) + +import copy + +from ansible_collections.kubernetes.core.plugins.module_utils.args_common import AUTH_ARG_SPEC + + +def argument_spec(): + args = copy.deepcopy(AUTH_ARG_SPEC) + + args_options = dict( + name=dict(type='str', required=True), + value=dict(type='str', required=True) + ) + + args.update( + dict( + state=dict(type='str', choices=['started', 'cancelled', 'restarted'], default="started"), + build_args=dict(type='list', elements='dict', options=args_options), + commit=dict(type='str'), + env_vars=dict(type='list', elements='dict', options=args_options), + build_name=dict(type='str'), + build_config_name=dict(type='str'), + namespace=dict(type='str', required=True), + incremental=dict(type='bool'), + no_cache=dict(type='bool'), + wait=dict(type='bool', default=False), + wait_sleep=dict(type='int', default=5), + wait_timeout=dict(type='int', default=120), + build_phases=dict(type='list', elements='str', default=[], choices=["New", "Pending", "Running"]), + ) + ) + return args + + +def main(): + mutually_exclusive = [ + ('build_name', 'build_config_name'), + ] + from ansible_collections.community.okd.plugins.module_utils.openshift_builds import ( + OpenShiftBuilds + ) + module = OpenShiftBuilds( + argument_spec=argument_spec(), + mutually_exclusive=mutually_exclusive, + required_one_of=[ + [ + 'build_name', + 'build_config_name', + ] + ], + ) + module.run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/okd/plugins/modules/openshift_import_image.py b/ansible_collections/community/okd/plugins/modules/openshift_import_image.py new file mode 100644 index 000000000..df0588cf4 --- /dev/null +++ b/ansible_collections/community/okd/plugins/modules/openshift_import_image.py @@ -0,0 +1,194 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, Red Hat +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +# STARTREMOVE (downstream) +DOCUMENTATION = r''' + +module: openshift_import_image + +short_description: Import the latest image information from a tag in a container image registry. + +version_added: "2.2.0" + +author: + - Aubin Bikouo (@abikouo) + +description: + - Image streams allow you to control which images are rolled out to your builds and applications. + - This module fetches the latest version of an image from a remote repository and updates the image stream tag + if it does not match the previous value. + - Running the module multiple times will not create duplicate entries. + - When importing an image, only the image metadata is copied, not the image contents. + - Analogous to C(oc import-image). + +extends_documentation_fragment: + - kubernetes.core.k8s_auth_options + +options: + namespace: + description: + - Use to specify namespace for image stream to create/update. + type: str + required: True + name: + description: + - Image stream to import tag into. + - This can be provided as a list of images streams or a single value. + type: raw + required: True + all: + description: + - If set to I(true), import all tags from the provided source on creation or if C(source) is specified. + type: bool + default: False + validate_registry_certs: + description: + - If set to I(true), allow importing from registries that have invalid HTTPS certificates. + or are hosted via HTTP. This parameter will take precedence over the insecure annotation. + type: bool + reference_policy: + description: + - Allow to request pullthrough for external image when set to I(local). + default: source + choices: + - source + - local + type: str + scheduled: + description: + - Set each imported Docker image to be periodically imported from a remote repository. + type: bool + default: False + source: + description: + - A Docker image repository to import images from. + - Should be provided as 'registry.io/repo/image' + type: str + +requirements: + - python >= 3.6 + - kubernetes >= 12.0.0 + - docker-image-py +''' + +EXAMPLES = r''' +# Import tag latest into a new image stream. +- name: Import tag latest into new image stream + community.okd.openshift_import_image: + namespace: testing + name: mystream + source: registry.io/repo/image:latest + +# Update imported data for tag latest in an already existing image stream. +- name: Update imported data for tag latest + community.okd.openshift_import_image: + namespace: testing + name: mystream + +# Update imported data for tag 'stable' in an already existing image stream. +- name: Update imported data for tag latest + community.okd.openshift_import_image: + namespace: testing + name: mystream:stable + +# Update imported data for all tags in an existing image stream. +- name: Update imported data for all tags + community.okd.openshift_import_image: + namespace: testing + name: mystream + all: true + +# Import all tags into a new image stream. +- name: Import all tags into a new image stream. + community.okd.openshift_import_image: + namespace: testing + name: mystream + source: registry.io/repo/image:latest + all: true + +# Import all tags into a new image stream for a list of image streams +- name: Import all tags into a new image stream. + community.okd.openshift_import_image: + namespace: testing + name: + - mystream1 + - mystream2 + - mystream3 + source: registry.io/repo/image:latest + all: true +''' + + +RETURN = r''' +result: + description: + - List with all ImageStreamImport that have been created. + type: list + returned: success + elements: dict + contains: + api_version: + description: The versioned schema of this representation of an object. + returned: success + type: str + kind: + description: Represents the REST resource this object represents. + returned: success + type: str + metadata: + description: Standard object metadata. Includes name, namespace, annotations, labels, etc. + returned: success + type: dict + spec: + description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind). + returned: success + type: dict + status: + description: Current status details for the object. + returned: success + type: dict +''' +# ENDREMOVE (downstream) + +import copy + +from ansible_collections.kubernetes.core.plugins.module_utils.args_common import AUTH_ARG_SPEC + + +def argument_spec(): + args = copy.deepcopy(AUTH_ARG_SPEC) + args.update( + dict( + namespace=dict(type='str', required=True), + name=dict(type='raw', required=True), + all=dict(type='bool', default=False), + validate_registry_certs=dict(type='bool'), + reference_policy=dict(type='str', choices=["source", "local"], default="source"), + scheduled=dict(type='bool', default=False), + source=dict(type='str'), + ) + ) + return args + + +def main(): + + from ansible_collections.community.okd.plugins.module_utils.openshift_import_image import ( + OpenShiftImportImage + ) + + module = OpenShiftImportImage( + argument_spec=argument_spec(), + supports_check_mode=True + ) + module.run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/okd/plugins/modules/openshift_process.py b/ansible_collections/community/okd/plugins/modules/openshift_process.py new file mode 100644 index 000000000..fb00ffbba --- /dev/null +++ b/ansible_collections/community/okd/plugins/modules/openshift_process.py @@ -0,0 +1,236 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +# Copyright (c) 2020-2021, Red Hat +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# STARTREMOVE (downstream) +DOCUMENTATION = r''' +module: openshift_process + +short_description: Process an OpenShift template.openshift.io/v1 Template + +version_added: "0.3.0" + +author: "Fabian von Feilitzsch (@fabianvf)" + +description: + - Processes a specified OpenShift template with the provided template. + - Templates can be provided inline, from a file, or specified by name and namespace in the cluster. + - Analogous to `oc process`. + - For CRUD operations on Template resources themselves, see the community.okd.k8s module. + +extends_documentation_fragment: + - kubernetes.core.k8s_auth_options + - kubernetes.core.k8s_wait_options + - kubernetes.core.k8s_resource_options + +requirements: + - "python >= 3.6" + - "kubernetes >= 12.0.0" + - "PyYAML >= 3.11" + +options: + name: + description: + - The name of the Template to process. + - The Template must be present in the cluster. + - When provided, I(namespace) is required. + - Mutually exclusive with I(resource_definition) or I(src) + type: str + namespace: + description: + - The namespace that the template can be found in. + type: str + namespace_target: + description: + - The namespace that resources should be created, updated, or deleted in. + - Only used when I(state) is present or absent. + parameters: + description: + - 'A set of key: value pairs that will be used to set/override values in the Template.' + - Corresponds to the `--param` argument to oc process. + type: dict + parameter_file: + description: + - A path to a file containing template parameter values to override/set values in the Template. + - Corresponds to the `--param-file` argument to oc process. + type: str + state: + description: + - Determines what to do with the rendered Template. + - The state I(rendered) will render the Template based on the provided parameters, and return the rendered + objects in the I(resources) field. These can then be referenced in future tasks. + - The state I(present) will cause the resources in the rendered Template to be created if they do not + already exist, and patched if they do. + - The state I(absent) will delete the resources in the rendered Template. + type: str + default: rendered + choices: [ absent, present, rendered ] +''' + +EXAMPLES = r''' +- name: Process a template in the cluster + community.okd.openshift_process: + name: nginx-example + namespace: openshift # only needed if using a template already on the server + parameters: + NAMESPACE: openshift + NAME: test123 + state: rendered + register: result + +- name: Create the rendered resources using apply + community.okd.k8s: + namespace: default + definition: '{{ item }}' + wait: yes + apply: yes + loop: '{{ result.resources }}' + +- name: Process a template with parameters from an env file and create the resources + community.okd.openshift_process: + name: nginx-example + namespace: openshift + namespace_target: default + parameter_file: 'files/nginx.env' + state: present + wait: yes + +- name: Process a local template and create the resources + community.okd.openshift_process: + src: files/example-template.yaml + parameter_file: files/example.env + namespace_target: default + state: present + +- name: Process a local template, delete the resources, and wait for them to terminate + community.okd.openshift_process: + src: files/example-template.yaml + parameter_file: files/example.env + namespace_target: default + state: absent + wait: yes +''' + +RETURN = r''' +result: + description: + - The created, patched, or otherwise present object. Will be empty in the case of a deletion. + returned: on success when state is present or absent + type: complex + contains: + apiVersion: + description: The versioned schema of this representation of an object. + returned: success + type: str + kind: + description: Represents the REST resource this object represents. + returned: success + type: str + metadata: + description: Standard object metadata. Includes name, namespace, annotations, labels, etc. + returned: success + type: complex + contains: + name: + description: The name of the resource + type: str + namespace: + description: The namespace of the resource + type: str + spec: + description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind). + returned: success + type: dict + status: + description: Current status details for the object. + returned: success + type: complex + contains: + conditions: + type: complex + description: Array of status conditions for the object. Not guaranteed to be present + items: + description: Returned only when multiple yaml documents are passed to src or resource_definition + returned: when resource_definition or src contains list of objects + type: list + duration: + description: elapsed time of task in seconds + returned: when C(wait) is true + type: int + sample: 48 +resources: + type: complex + description: + - The rendered resources defined in the Template + returned: on success when state is rendered + contains: + apiVersion: + description: The versioned schema of this representation of an object. + returned: success + type: str + kind: + description: Represents the REST resource this object represents. + returned: success + type: str + metadata: + description: Standard object metadata. Includes name, namespace, annotations, labels, etc. + returned: success + type: complex + contains: + name: + description: The name of the resource + type: str + namespace: + description: The namespace of the resource + type: str + spec: + description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind). + returned: success + type: dict + status: + description: Current status details for the object. + returned: success + type: dict + contains: + conditions: + type: complex + description: Array of status conditions for the object. Not guaranteed to be present +''' +# ENDREMOVE (downstream) + +from ansible_collections.kubernetes.core.plugins.module_utils.args_common import ( + AUTH_ARG_SPEC, RESOURCE_ARG_SPEC, WAIT_ARG_SPEC +) + + +def argspec(): + argument_spec = {} + argument_spec.update(AUTH_ARG_SPEC) + argument_spec.update(WAIT_ARG_SPEC) + argument_spec.update(RESOURCE_ARG_SPEC) + argument_spec['state'] = dict(type='str', default='rendered', choices=['present', 'absent', 'rendered']) + argument_spec['namespace'] = dict(type='str') + argument_spec['namespace_target'] = dict(type='str') + argument_spec['parameters'] = dict(type='dict') + argument_spec['name'] = dict(type='str') + argument_spec['parameter_file'] = dict(type='str') + + return argument_spec + + +def main(): + + from ansible_collections.community.okd.plugins.module_utils.openshift_process import ( + OpenShiftProcess) + + module = OpenShiftProcess(argument_spec=argspec(), supports_check_mode=True) + module.run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/okd/plugins/modules/openshift_registry_info.py b/ansible_collections/community/okd/plugins/modules/openshift_registry_info.py new file mode 100644 index 000000000..a455ac50b --- /dev/null +++ b/ansible_collections/community/okd/plugins/modules/openshift_registry_info.py @@ -0,0 +1,114 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, Red Hat +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +# STARTREMOVE (downstream) +DOCUMENTATION = r''' + +module: openshift_registry_info + +short_description: Display information about the integrated registry. + +version_added: "2.2.0" + +author: + - Aubin Bikouo (@abikouo) + +description: + - This module exposes information about the integrated registry. + - Use C(check) to verify your local client can access the registry. + - If the adminstrator has not configured a public hostname for the registry then + this command may fail when run outside of the server. + - Analogous to C(oc registry info). + +extends_documentation_fragment: + - kubernetes.core.k8s_auth_options + +options: + check: + description: + - Attempt to contact the integrated registry using local client. + type: bool + default: False + +requirements: + - python >= 3.6 + - kubernetes >= 12.0.0 + - docker-image-py +''' + +EXAMPLES = r''' +# Get registry information +- name: Read integrated registry information + community.okd.openshift_registry_info: + +# Read registry integrated information and attempt to contact using local client. +- name: Attempt to contact integrated registry using local client + community.okd.openshift_registry_info: + check: yes +''' + + +RETURN = r''' +internal_hostname: + description: + - The internal registry hostname. + type: str + returned: success +public_hostname: + description: + - The public registry hostname. + type: str + returned: success +check: + description: + - Whether the local client can contact or not the registry. + type: dict + returned: success + contains: + reached: + description: Whether the registry has been reached or not. + returned: success + type: str + msg: + description: message describing the ping operation. + returned: always + type: str +''' +# ENDREMOVE (downstream) + +import copy + +from ansible_collections.kubernetes.core.plugins.module_utils.args_common import AUTH_ARG_SPEC + + +def argument_spec(): + args = copy.deepcopy(AUTH_ARG_SPEC) + args.update( + dict( + check=dict(type='bool', default=False) + ) + ) + return args + + +def main(): + + from ansible_collections.community.okd.plugins.module_utils.openshift_registry import ( + OpenShiftRegistry + ) + + module = OpenShiftRegistry( + argument_spec=argument_spec(), + supports_check_mode=True + ) + module.run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/okd/plugins/modules/openshift_route.py b/ansible_collections/community/okd/plugins/modules/openshift_route.py new file mode 100644 index 000000000..e452fc534 --- /dev/null +++ b/ansible_collections/community/okd/plugins/modules/openshift_route.py @@ -0,0 +1,542 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2020, Red Hat +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +# STARTREMOVE (downstream) +DOCUMENTATION = r''' +module: openshift_route + +short_description: Expose a Service as an OpenShift Route. + +version_added: "0.3.0" + +author: "Fabian von Feilitzsch (@fabianvf)" + +description: + - Looks up a Service and creates a new Route based on it. + - Analogous to `oc expose` and `oc create route` for creating Routes, but does not support creating Services. + - For creating Services from other resources, see kubernetes.core.k8s. + +extends_documentation_fragment: + - kubernetes.core.k8s_auth_options + - kubernetes.core.k8s_wait_options + - kubernetes.core.k8s_state_options + +requirements: + - "python >= 3.6" + - "kubernetes >= 12.0.0" + - "PyYAML >= 3.11" + +options: + service: + description: + - The name of the service to expose. + - Required when I(state) is not absent. + type: str + aliases: ['svc'] + namespace: + description: + - The namespace of the resource being targeted. + - The Route will be created in this namespace as well. + required: yes + type: str + labels: + description: + - Specify the labels to apply to the created Route. + - 'A set of key: value pairs.' + type: dict + annotations: + description: + - Specify the Route Annotations. + - 'A set of key: value pairs.' + type: dict + version_added: "2.1.0" + name: + description: + - The desired name of the Route to be created. + - Defaults to the value of I(service) + type: str + hostname: + description: + - The hostname for the Route. + type: str + path: + description: + - The path for the Route + type: str + wildcard_policy: + description: + - The wildcard policy for the hostname. + - Currently only Subdomain is supported. + - If not provided, the default of None will be used. + choices: + - Subdomain + type: str + port: + description: + - Name or number of the port the Route will route traffic to. + type: str + tls: + description: + - TLS configuration for the newly created route. + - Only used when I(termination) is set. + type: dict + suboptions: + ca_certificate: + description: + - Path to a CA certificate file on the target host. + - Not supported when I(termination) is set to passthrough. + type: str + certificate: + description: + - Path to a certificate file on the target host. + - Not supported when I(termination) is set to passthrough. + type: str + destination_ca_certificate: + description: + - Path to a CA certificate file used for securing the connection. + - Only used when I(termination) is set to reencrypt. + - Defaults to the Service CA. + type: str + key: + description: + - Path to a key file on the target host. + - Not supported when I(termination) is set to passthrough. + type: str + insecure_policy: + description: + - Sets the InsecureEdgeTerminationPolicy for the Route. + - Not supported when I(termination) is set to reencrypt. + - When I(termination) is set to passthrough, only redirect is supported. + - If not provided, insecure traffic will be disallowed. + type: str + choices: + - allow + - redirect + - disallow + default: disallow + termination: + description: + - The termination type of the Route. + - If left empty no termination type will be set, and the route will be insecure. + - When set to insecure I(tls) will be ignored. + choices: + - edge + - passthrough + - reencrypt + - insecure + default: insecure + type: str +''' + +EXAMPLES = r''' +- name: Create hello-world deployment + community.okd.k8s: + definition: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: hello-kubernetes + namespace: default + spec: + replicas: 3 + selector: + matchLabels: + app: hello-kubernetes + template: + metadata: + labels: + app: hello-kubernetes + spec: + containers: + - name: hello-kubernetes + image: paulbouwer/hello-kubernetes:1.8 + ports: + - containerPort: 8080 + +- name: Create Service for the hello-world deployment + community.okd.k8s: + definition: + apiVersion: v1 + kind: Service + metadata: + name: hello-kubernetes + namespace: default + spec: + ports: + - port: 80 + targetPort: 8080 + selector: + app: hello-kubernetes + +- name: Expose the insecure hello-world service externally + community.okd.openshift_route: + service: hello-kubernetes + namespace: default + insecure_policy: allow + annotations: + haproxy.router.openshift.io/balance: roundrobin + register: route +''' + +RETURN = r''' +result: + description: + - The Route object that was created or updated. Will be empty in the case of deletion. + returned: success + type: complex + contains: + apiVersion: + description: The versioned schema of this representation of an object. + returned: success + type: str + kind: + description: Represents the REST resource this object represents. + returned: success + type: str + metadata: + description: Standard object metadata. Includes name, namespace, annotations, labels, etc. + returned: success + type: complex + contains: + name: + description: The name of the created Route + type: str + namespace: + description: The namespace of the create Route + type: str + spec: + description: Specification for the Route + returned: success + type: complex + contains: + host: + description: Host is an alias/DNS that points to the service. + type: str + path: + description: Path that the router watches for, to route traffic for to the service. + type: str + port: + description: Defines a port mapping from a router to an endpoint in the service endpoints. + type: complex + contains: + targetPort: + description: The target port on pods selected by the service this route points to. + type: str + tls: + description: Defines config used to secure a route and provide termination. + type: complex + contains: + caCertificate: + description: Provides the cert authority certificate contents. + type: str + certificate: + description: Provides certificate contents. + type: str + destinationCACertificate: + description: Provides the contents of the ca certificate of the final destination. + type: str + insecureEdgeTerminationPolicy: + description: Indicates the desired behavior for insecure connections to a route. + type: str + key: + description: Provides key file contents. + type: str + termination: + description: Indicates termination type. + type: str + to: + description: Specifies the target that resolve into endpoints. + type: complex + contains: + kind: + description: The kind of target that the route is referring to. Currently, only 'Service' is allowed. + type: str + name: + description: Name of the service/target that is being referred to. e.g. name of the service. + type: str + weight: + description: Specifies the target's relative weight against other target reference objects. + type: int + wildcardPolicy: + description: Wildcard policy if any for the route. + type: str + status: + description: Current status details for the Route + returned: success + type: complex + contains: + ingress: + description: List of places where the route may be exposed. + type: complex + contains: + conditions: + description: Array of status conditions for the Route ingress. + type: complex + contains: + type: + description: The type of the condition. Currently only 'Ready'. + type: str + status: + description: The status of the condition. Can be True, False, Unknown. + type: str + host: + description: The host string under which the route is exposed. + type: str + routerCanonicalHostname: + description: The external host name for the router that can be used as a CNAME for the host requested for this route. May not be set. + type: str + routerName: + description: A name chosen by the router to identify itself. + type: str + wildcardPolicy: + description: The wildcard policy that was allowed where this route is exposed. + type: str +duration: + description: elapsed time of task in seconds + returned: when C(wait) is true + type: int + sample: 48 +''' +# ENDREMOVE (downstream) + +import copy + +from ansible.module_utils._text import to_native + +from ansible_collections.community.okd.plugins.module_utils.openshift_common import AnsibleOpenshiftModule + +try: + from ansible_collections.kubernetes.core.plugins.module_utils.k8s.runner import perform_action + from ansible_collections.kubernetes.core.plugins.module_utils.k8s.waiter import Waiter + from ansible_collections.kubernetes.core.plugins.module_utils.args_common import ( + AUTH_ARG_SPEC, WAIT_ARG_SPEC, COMMON_ARG_SPEC + ) +except ImportError as e: + pass + AUTH_ARG_SPEC = WAIT_ARG_SPEC = COMMON_ARG_SPEC = {} + +try: + from kubernetes.dynamic.exceptions import DynamicApiError, NotFoundError +except ImportError: + pass + + +class OpenShiftRoute(AnsibleOpenshiftModule): + + def __init__(self): + super(OpenShiftRoute, self).__init__( + argument_spec=self.argspec, + supports_check_mode=True, + ) + + self.append_hash = False + self.apply = False + self.warnings = [] + self.params['merge_type'] = None + + @property + def argspec(self): + spec = copy.deepcopy(AUTH_ARG_SPEC) + spec.update(copy.deepcopy(WAIT_ARG_SPEC)) + spec.update(copy.deepcopy(COMMON_ARG_SPEC)) + + spec['service'] = dict(type='str', aliases=['svc']) + spec['namespace'] = dict(required=True, type='str') + spec['labels'] = dict(type='dict') + spec['name'] = dict(type='str') + spec['hostname'] = dict(type='str') + spec['path'] = dict(type='str') + spec['wildcard_policy'] = dict(choices=['Subdomain'], type='str') + spec['port'] = dict(type='str') + spec['tls'] = dict(type='dict', options=dict( + ca_certificate=dict(type='str'), + certificate=dict(type='str'), + destination_ca_certificate=dict(type='str'), + key=dict(type='str', no_log=False), + insecure_policy=dict(type='str', choices=['allow', 'redirect', 'disallow'], default='disallow'), + )) + spec['termination'] = dict(choices=['edge', 'passthrough', 'reencrypt', 'insecure'], default='insecure') + spec['annotations'] = dict(type='dict') + + return spec + + def execute_module(self): + + service_name = self.params.get('service') + namespace = self.params['namespace'] + termination_type = self.params.get('termination') + if termination_type == 'insecure': + termination_type = None + state = self.params.get('state') + + if state != 'absent' and not service_name: + self.fail_json("If 'state' is not 'absent' then 'service' must be provided") + + # We need to do something a little wonky to wait if the user doesn't supply a custom condition + custom_wait = self.params.get('wait') and not self.params.get('wait_condition') and state != 'absent' + if custom_wait: + # Don't use default wait logic in perform_action + self.params['wait'] = False + + route_name = self.params.get('name') or service_name + labels = self.params.get('labels') + hostname = self.params.get('hostname') + path = self.params.get('path') + wildcard_policy = self.params.get('wildcard_policy') + port = self.params.get('port') + annotations = self.params.get('annotations') + + if termination_type and self.params.get('tls'): + tls_ca_cert = self.params['tls'].get('ca_certificate') + tls_cert = self.params['tls'].get('certificate') + tls_dest_ca_cert = self.params['tls'].get('destination_ca_certificate') + tls_key = self.params['tls'].get('key') + tls_insecure_policy = self.params['tls'].get('insecure_policy') + if tls_insecure_policy == 'disallow': + tls_insecure_policy = None + else: + tls_ca_cert = tls_cert = tls_dest_ca_cert = tls_key = tls_insecure_policy = None + + route = { + 'apiVersion': 'route.openshift.io/v1', + 'kind': 'Route', + 'metadata': { + 'name': route_name, + 'namespace': namespace, + 'labels': labels, + }, + 'spec': {} + } + + if annotations: + route['metadata']['annotations'] = annotations + + if state != 'absent': + route['spec'] = self.build_route_spec( + service_name, namespace, + port=port, + wildcard_policy=wildcard_policy, + hostname=hostname, + path=path, + termination_type=termination_type, + tls_insecure_policy=tls_insecure_policy, + tls_ca_cert=tls_ca_cert, + tls_cert=tls_cert, + tls_key=tls_key, + tls_dest_ca_cert=tls_dest_ca_cert, + ) + + result = perform_action(self.svc, route, self.params) + timeout = self.params.get('wait_timeout') + sleep = self.params.get('wait_sleep') + if custom_wait: + v1_routes = self.find_resource('Route', 'route.openshift.io/v1', fail=True) + waiter = Waiter(self.client, v1_routes, wait_predicate) + success, result['result'], result['duration'] = waiter.wait(timeout=timeout, sleep=sleep, name=route_name, namespace=namespace) + + self.exit_json(**result) + + def build_route_spec(self, service_name, namespace, port=None, wildcard_policy=None, hostname=None, path=None, termination_type=None, + tls_insecure_policy=None, tls_ca_cert=None, tls_cert=None, tls_key=None, tls_dest_ca_cert=None): + v1_services = self.find_resource('Service', 'v1', fail=True) + try: + target_service = v1_services.get(name=service_name, namespace=namespace) + except NotFoundError: + if not port: + self.fail_json(msg="You need to provide the 'port' argument when exposing a non-existent service") + target_service = None + except DynamicApiError as exc: + self.fail_json(msg='Failed to retrieve service to be exposed: {0}'.format(exc.body), + error=exc.status, status=exc.status, reason=exc.reason) + except Exception as exc: + self.fail_json(msg='Failed to retrieve service to be exposed: {0}'.format(to_native(exc)), + error='', status='', reason='') + + route_spec = { + 'tls': {}, + 'to': { + 'kind': 'Service', + 'name': service_name, + }, + 'port': { + 'targetPort': self.set_port(target_service, port), + }, + 'wildcardPolicy': wildcard_policy + } + + # Want to conditionally add these so we don't overwrite what is automically added when nothing is provided + if termination_type: + route_spec['tls'] = dict(termination=termination_type.capitalize()) + if tls_insecure_policy: + if termination_type == 'edge': + route_spec['tls']['insecureEdgeTerminationPolicy'] = tls_insecure_policy.capitalize() + elif termination_type == 'passthrough': + if tls_insecure_policy != 'redirect': + self.fail_json("'redirect' is the only supported insecureEdgeTerminationPolicy for passthrough routes") + route_spec['tls']['insecureEdgeTerminationPolicy'] = tls_insecure_policy.capitalize() + elif termination_type == 'reencrypt': + self.fail_json("'tls.insecure_policy' is not supported with reencrypt routes") + else: + route_spec['tls']['insecureEdgeTerminationPolicy'] = None + if tls_ca_cert: + if termination_type == 'passthrough': + self.fail_json("'tls.ca_certificate' is not supported with passthrough routes") + route_spec['tls']['caCertificate'] = tls_ca_cert + if tls_cert: + if termination_type == 'passthrough': + self.fail_json("'tls.certificate' is not supported with passthrough routes") + route_spec['tls']['certificate'] = tls_cert + if tls_key: + if termination_type == 'passthrough': + self.fail_json("'tls.key' is not supported with passthrough routes") + route_spec['tls']['key'] = tls_key + if tls_dest_ca_cert: + if termination_type != 'reencrypt': + self.fail_json("'destination_certificate' is only valid for reencrypt routes") + route_spec['tls']['destinationCACertificate'] = tls_dest_ca_cert + else: + route_spec['tls'] = None + if hostname: + route_spec['host'] = hostname + if path: + route_spec['path'] = path + + return route_spec + + def set_port(self, service, port_arg): + if port_arg: + return port_arg + for p in service.spec.ports: + if p.protocol == 'TCP': + if p.name is not None: + return p.name + return p.targetPort + return None + + +def wait_predicate(route): + if not (route.status and route.status.ingress): + return False + for ingress in route.status.ingress: + match = [x for x in ingress.conditions if x.type == 'Admitted'] + if not match: + return False + match = match[0] + if match.status != "True": + return False + return True + + +def main(): + OpenShiftRoute().run_module() + + +if __name__ == '__main__': + main() |