Init: mediaserver

This commit is contained in:
2023-02-08 12:13:28 +01:00
parent 848bc9739c
commit f7c23d4ba9
31914 changed files with 6175775 additions and 0 deletions

View File

@@ -0,0 +1,318 @@
#!/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)
try:
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
except ImportError:
from ansible.module_utils.basic import AnsibleModule
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'),
]
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True, mutually_exclusive=mutually_exclusive)
from ansible_collections.community.okd.plugins.module_utils.k8s import OKDRawModule
okdraw_module = OKDRawModule(module)
# remove_aliases from kubernetes.core's common requires the argspec attribute. Ideally, it should
# read that throught the module class, but we cannot change that.
okdraw_module.argspec = argspec()
okdraw_module.execute_module()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,228 @@
#!/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
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
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.module_utils.basic import AnsibleModule
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():
module = AnsibleModule(argument_spec=argument_spec(), supports_check_mode=True)
from ansible_collections.community.okd.plugins.module_utils.openshift_groups import (
OpenshiftGroupsSync
)
try:
openshift_groups = OpenshiftGroupsSync(module)
openshift_groups.execute_module()
except Exception as e:
module.fail_json(msg=str(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,399 @@
#!/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)
import traceback
from ansible.module_utils._text import to_native
try:
from kubernetes.dynamic.exceptions import DynamicApiError
HAS_KUBERNETES_COLLECTION = True
except ImportError as e:
HAS_KUBERNETES_COLLECTION = False
k8s_collection_import_exception = e
K8S_COLLECTION_ERROR = traceback.format_exc()
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
AUTH_ARG_SPEC,
WAIT_ARG_SPEC,
)
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
K8sAnsibleMixin,
get_api_client,
)
transforms = {
"Build": "build.openshift.io/v1",
"BuildConfig": "build.openshift.io/v1",
"DeploymentConfig": "apps.openshift.io/v1",
"Route": "route.openshift.io/v1",
}
class OpenShiftMigrateTemplateInstances(K8sAnsibleMixin):
def __init__(self, module):
self.module = module
self.fail_json = self.module.fail_json
self.exit_json = self.module.exit_json
if not HAS_KUBERNETES_COLLECTION:
self.module.fail_json(
msg="The kubernetes.core collection must be installed",
exception=K8S_COLLECTION_ERROR,
error=to_native(k8s_collection_import_exception),
)
super(OpenShiftMigrateTemplateInstances, self).__init__(self.module)
self.params = self.module.params
self.check_mode = self.module.check_mode
self.client = get_api_client(self.module)
def patch_template_instance(self, resource, templateinstance):
result = None
try:
result = resource.status.patch(templateinstance)
except Exception as exc:
self.module.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.module.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.module.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.module.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 = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
openshift_process = OpenShiftMigrateTemplateInstances(module)
openshift_process.argspec = argument_spec
openshift_process.execute_module()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,135 @@
#!/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.module_utils.basic import AnsibleModule
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():
module = AnsibleModule(argument_spec=argument_spec(),
mutually_exclusive=[("name", "label_selectors")],
supports_check_mode=True)
from ansible_collections.community.okd.plugins.module_utils.openshift_adm_prune_auth import (
OpenShiftAdmPruneAuth)
adm_prune_auth = OpenShiftAdmPruneAuth(module)
adm_prune_auth.argspec = argument_spec
adm_prune_auth.execute_module()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,102 @@
#!/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
from ansible.module_utils.basic import AnsibleModule
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():
module = AnsibleModule(argument_spec=argument_spec(), supports_check_mode=True)
from ansible_collections.community.okd.plugins.module_utils.openshift_adm_prune_deployments import (
OpenShiftAdmPruneDeployment)
adm_prune_deployments = OpenShiftAdmPruneDeployment(module)
adm_prune_deployments.execute()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,317 @@
#!/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.module_utils.basic import AnsibleModule
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():
module = AnsibleModule(argument_spec=argument_spec(), supports_check_mode=True)
from ansible_collections.community.okd.plugins.module_utils.openshift_adm_prune_images import (
OpenShiftAdmPruneImages)
adm_prune_images = OpenShiftAdmPruneImages(module)
adm_prune_images.argspec = argument_spec()
adm_prune_images.execute_module()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,364 @@
#!/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
resourcespecific 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/k8s:
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
# 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},
}
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()
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:
self.openshift_logout()
result = dict()
# return k8s_auth as well for backwards compatibility
self.exit_json(changed=False, 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):
url = '{0}/apis/oauth.openshift.io/v1/oauthaccesstokens/{1}'.format(self.con_host, self.auth_api_key)
headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Bearer {0}'.format(self.auth_api_key)
}
json = {
"apiVersion": "oauth.openshift.io/v1",
"kind": "DeleteOptions"
}
requests.delete(url, headers=headers, json=json, verify=self.con_verify_ca)
# Ignore errors, the token will time out eventually anyway
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()

View File

@@ -0,0 +1,196 @@
#!/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.module_utils.basic import AnsibleModule
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():
module = AnsibleModule(
argument_spec=argument_spec(),
supports_check_mode=True
)
from ansible_collections.community.okd.plugins.module_utils.openshift_import_image import (
OpenShiftImportImage)
import_image = OpenShiftImportImage(module)
import_image.argspec = argument_spec()
import_image.execute_module()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,246 @@
#!/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)
try:
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
except ImportError:
from ansible.module_utils.basic import AnsibleModule
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():
argument_spec = argspec()
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
from ansible_collections.community.okd.plugins.module_utils.openshift_process import (
OpenShiftProcess)
openshift_process = OpenShiftProcess(module)
# remove_aliases from kubernetes.core's common requires the argspec attribute. Ideally, it should
# read that throught the module class, but we cannot change that.
openshift_process.argspec = argument_spec
openshift_process.execute_module()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,116 @@
#!/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.module_utils.basic import AnsibleModule
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():
module = AnsibleModule(
argument_spec=argument_spec(),
supports_check_mode=True
)
from ansible_collections.community.okd.plugins.module_utils.openshift_registry import (
OpenShiftRegistry)
registry = OpenShiftRegistry(module)
registry.argspec = argument_spec()
registry.info()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,562 @@
#!/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
import traceback
try:
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
except ImportError:
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
try:
from ansible_collections.kubernetes.core.plugins.module_utils.common import K8sAnsibleMixin, get_api_client
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
AUTH_ARG_SPEC, WAIT_ARG_SPEC, COMMON_ARG_SPEC
)
HAS_KUBERNETES_COLLECTION = True
except ImportError as e:
HAS_KUBERNETES_COLLECTION = False
k8s_collection_import_exception = e
K8S_COLLECTION_ERROR = traceback.format_exc()
K8sAnsibleMixin = object
AUTH_ARG_SPEC = WAIT_ARG_SPEC = COMMON_ARG_SPEC = {}
try:
from kubernetes.dynamic.exceptions import DynamicApiError, NotFoundError
except ImportError:
pass
class OpenShiftRoute(K8sAnsibleMixin):
def __init__(self):
self.module = AnsibleModule(
argument_spec=self.argspec,
supports_check_mode=True,
)
self.fail_json = self.module.fail_json
if not HAS_KUBERNETES_COLLECTION:
self.module.fail_json(
msg="The kubernetes.core collection must be installed",
exception=K8S_COLLECTION_ERROR,
error=to_native(k8s_collection_import_exception)
)
super(OpenShiftRoute, self).__init__(self.module)
self.params = self.module.params
# TODO: should probably make it so that at least some of these aren't required for perform_action to work
# Or at least explicitly pass them in
self.append_hash = False
self.apply = False
self.check_mode = self.module.check_mode
self.warnings = []
self.params['merge_type'] = None
self.client = get_api_client(self.module)
@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):
v1_routes = self.find_resource('Route', 'route.openshift.io/v1', fail=True)
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 = self.perform_action(v1_routes, route)
timeout = self.params.get('wait_timeout')
sleep = self.params.get('wait_sleep')
if custom_wait:
success, result['result'], result['duration'] = self._wait_for(v1_routes, route_name, namespace, wait_predicate, sleep, timeout, state)
self.module.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.module.fail_json(msg="You need to provide the 'port' argument when exposing a non-existent service")
target_service = None
except DynamicApiError as exc:
self.module.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.module.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.module.fail_json("'redirect' is the only supported insecureEdgeTerminationPolicy for passthrough routes")
route_spec['tls']['insecureEdgeTerminationPolicy'] = tls_insecure_policy.capitalize()
elif termination_type == 'reencrypt':
self.module.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.module.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.module.fail_json("'tls.certificate' is not supported with passthrough routes")
route_spec['tls']['certificate'] = tls_cert
if tls_key:
if termination_type == 'passthrough':
self.module.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.module.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().execute_module()
if __name__ == '__main__':
main()