mirror of
https://github.com/openshift/community.okd.git
synced 2026-05-06 21:22:36 +00:00
* Upgrade Ansible and OKD versions for CI * Use ubi9 and fix sanity * Use correct pip install * Try using quotes * Ensure python3.9 * Upgrade ansible and molecule versions * Remove DeploymentConfig DeploymentConfigs are deprecated and seem to now be causing idempotence problems. Replacing them with Deployments fixes it. * Attempt to fix ldap integration tests Signed-off-by: Alina Buzachis <abuzachis@redhat.com> * Move sanity and unit tests to GH actions Signed-off-by: Alina Buzachis <abuzachis@redhat.com> * Firt round of sanity fixes Signed-off-by: Alina Buzachis <abuzachis@redhat.com> * Add kubernetes.core collection as sanity requirement Signed-off-by: Alina Buzachis <abuzachis@redhat.com> * Add ignore-2.16.txt Signed-off-by: Alina Buzachis <abuzachis@redhat.com> * Attempt to fix units Signed-off-by: Alina Buzachis <abuzachis@redhat.com> * Add ignore-2.17 Signed-off-by: Alina Buzachis <abuzachis@redhat.com> * Attempt to fix unit tests Signed-off-by: Alina Buzachis <abuzachis@redhat.com> * Add pytest-ansible to test-requirements.txt Signed-off-by: Alina Buzachis <abuzachis@redhat.com> * Add changelog fragment Signed-off-by: Alina Buzachis <abuzachis@redhat.com> * Add workflow for ansible-lint Signed-off-by: Alina Buzachis <abuzachis@redhat.com> * Apply black Signed-off-by: Alina Buzachis <abuzachis@redhat.com> * Fix linters Signed-off-by: Alina Buzachis <abuzachis@redhat.com> * Add # fmt: skip Signed-off-by: Alina Buzachis <abuzachis@redhat.com> * Yet another round of linting Signed-off-by: Alina Buzachis <abuzachis@redhat.com> * Yet another round of linting Signed-off-by: Alina Buzachis <abuzachis@redhat.com> * Remove setup.cfg Signed-off-by: Alina Buzachis <abuzachis@redhat.com> * Revert #fmt Signed-off-by: Alina Buzachis <abuzachis@redhat.com> * Use ansible-core 2.14 Signed-off-by: Alina Buzachis <abuzachis@redhat.com> * Cleanup ansible-lint ignores Signed-off-by: Alina Buzachis <abuzachis@redhat.com> * Try using service instead of pod IP * Fix typo * Actually use the correct port * See if NetworkPolicy is preventing connection * using Pod internal IP * fix adm prune auth roles syntax * adding some retry steps * fix: openshift_builds target * add flag --force-with-deps when building downstream collection * Remove yamllint from tox linters, bump minimum python supported version to 3.9, Remove support for ansible-core < 2.14 --------- Signed-off-by: Alina Buzachis <abuzachis@redhat.com> Co-authored-by: Mike Graves <mgraves@redhat.com> Co-authored-by: Alina Buzachis <abuzachis@redhat.com>
This commit is contained in:
@@ -1,35 +1,46 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
import re
|
||||
import operator
|
||||
from functools import reduce
|
||||
from ansible_collections.community.okd.plugins.module_utils.openshift_common import AnsibleOpenshiftModule
|
||||
from ansible_collections.community.okd.plugins.module_utils.openshift_common import (
|
||||
AnsibleOpenshiftModule,
|
||||
)
|
||||
|
||||
try:
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.resource import create_definitions
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import CoreException
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.resource import (
|
||||
create_definitions,
|
||||
)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
|
||||
CoreException,
|
||||
)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
try:
|
||||
from kubernetes.dynamic.exceptions import DynamicApiError, NotFoundError, ForbiddenError
|
||||
from kubernetes.dynamic.exceptions import (
|
||||
DynamicApiError,
|
||||
NotFoundError,
|
||||
ForbiddenError,
|
||||
)
|
||||
except ImportError as e:
|
||||
pass
|
||||
|
||||
|
||||
TRIGGER_ANNOTATION = 'image.openshift.io/triggers'
|
||||
TRIGGER_CONTAINER = re.compile(r"(?P<path>.*)\[((?P<index>[0-9]+)|\?\(@\.name==[\"'\\]*(?P<name>[a-z0-9]([-a-z0-9]*[a-z0-9])?))")
|
||||
TRIGGER_ANNOTATION = "image.openshift.io/triggers"
|
||||
TRIGGER_CONTAINER = re.compile(
|
||||
r"(?P<path>.*)\[((?P<index>[0-9]+)|\?\(@\.name==[\"'\\]*(?P<name>[a-z0-9]([-a-z0-9]*[a-z0-9])?))"
|
||||
)
|
||||
|
||||
|
||||
class OKDRawModule(AnsibleOpenshiftModule):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
||||
super(OKDRawModule, self).__init__(**kwargs)
|
||||
|
||||
@property
|
||||
@@ -50,36 +61,60 @@ class OKDRawModule(AnsibleOpenshiftModule):
|
||||
result = {"changed": False, "result": {}}
|
||||
warnings = []
|
||||
|
||||
if self.params.get("state") != 'absent':
|
||||
if self.params.get("state") != "absent":
|
||||
existing = None
|
||||
name = definition.get("metadata", {}).get("name")
|
||||
namespace = definition.get("metadata", {}).get("namespace")
|
||||
if definition.get("kind") in ['Project', 'ProjectRequest']:
|
||||
if definition.get("kind") in ["Project", "ProjectRequest"]:
|
||||
try:
|
||||
resource = self.svc.find_resource(kind=definition.get("kind"), api_version=definition.get("apiVersion", "v1"))
|
||||
existing = resource.get(name=name, namespace=namespace).to_dict()
|
||||
resource = self.svc.find_resource(
|
||||
kind=definition.get("kind"),
|
||||
api_version=definition.get("apiVersion", "v1"),
|
||||
)
|
||||
existing = resource.get(
|
||||
name=name, namespace=namespace
|
||||
).to_dict()
|
||||
except (NotFoundError, ForbiddenError):
|
||||
result = self.create_project_request(definition)
|
||||
changed |= result["changed"]
|
||||
results.append(result)
|
||||
continue
|
||||
except DynamicApiError as exc:
|
||||
self.fail_json(msg='Failed to retrieve requested object: {0}'.format(exc.body),
|
||||
error=exc.status, status=exc.status, reason=exc.reason)
|
||||
self.fail_json(
|
||||
msg="Failed to retrieve requested object: {0}".format(
|
||||
exc.body
|
||||
),
|
||||
error=exc.status,
|
||||
status=exc.status,
|
||||
reason=exc.reason,
|
||||
)
|
||||
|
||||
if definition.get("kind") not in ['Project', 'ProjectRequest']:
|
||||
if definition.get("kind") not in ["Project", "ProjectRequest"]:
|
||||
try:
|
||||
resource = self.svc.find_resource(kind=definition.get("kind"), api_version=definition.get("apiVersion", "v1"))
|
||||
existing = resource.get(name=name, namespace=namespace).to_dict()
|
||||
resource = self.svc.find_resource(
|
||||
kind=definition.get("kind"),
|
||||
api_version=definition.get("apiVersion", "v1"),
|
||||
)
|
||||
existing = resource.get(
|
||||
name=name, namespace=namespace
|
||||
).to_dict()
|
||||
except Exception:
|
||||
existing = None
|
||||
|
||||
if existing:
|
||||
if resource.kind == 'DeploymentConfig':
|
||||
if definition.get('spec', {}).get('triggers'):
|
||||
definition = self.resolve_imagestream_triggers(existing, definition)
|
||||
elif existing['metadata'].get('annotations', {}).get(TRIGGER_ANNOTATION):
|
||||
definition = self.resolve_imagestream_trigger_annotation(existing, definition)
|
||||
if resource.kind == "DeploymentConfig":
|
||||
if definition.get("spec", {}).get("triggers"):
|
||||
definition = self.resolve_imagestream_triggers(
|
||||
existing, definition
|
||||
)
|
||||
elif (
|
||||
existing["metadata"]
|
||||
.get("annotations", {})
|
||||
.get(TRIGGER_ANNOTATION)
|
||||
):
|
||||
definition = self.resolve_imagestream_trigger_annotation(
|
||||
existing, definition
|
||||
)
|
||||
|
||||
if self.params.get("validate") is not None:
|
||||
warnings = self.validate(definition)
|
||||
@@ -116,13 +151,15 @@ class OKDRawModule(AnsibleOpenshiftModule):
|
||||
|
||||
@staticmethod
|
||||
def get_index(desired, objects, keys):
|
||||
""" Iterates over keys, returns the first object from objects where the value of the key
|
||||
matches the value in desired
|
||||
"""Iterates over keys, returns the first object from objects where the value of the key
|
||||
matches the value in desired
|
||||
"""
|
||||
# pylint: disable=use-a-generator
|
||||
# Use a generator instead 'all(desired.get(key, True) == item.get(key, False) for key in keys)'
|
||||
for i, item in enumerate(objects):
|
||||
if item and all([desired.get(key, True) == item.get(key, False) for key in keys]):
|
||||
if item and all(
|
||||
[desired.get(key, True) == item.get(key, False) for key in keys]
|
||||
):
|
||||
return i
|
||||
|
||||
def resolve_imagestream_trigger_annotation(self, existing, definition):
|
||||
@@ -137,84 +174,148 @@ class OKDRawModule(AnsibleOpenshiftModule):
|
||||
def set_from_fields(d, fields, value):
|
||||
get_from_fields(d, fields[:-1])[fields[-1]] = value
|
||||
|
||||
if TRIGGER_ANNOTATION in definition['metadata'].get('annotations', {}).keys():
|
||||
triggers = yaml.safe_load(definition['metadata']['annotations'][TRIGGER_ANNOTATION] or '[]')
|
||||
if TRIGGER_ANNOTATION in definition["metadata"].get("annotations", {}).keys():
|
||||
triggers = yaml.safe_load(
|
||||
definition["metadata"]["annotations"][TRIGGER_ANNOTATION] or "[]"
|
||||
)
|
||||
else:
|
||||
triggers = yaml.safe_load(existing['metadata'].get('annotations', '{}').get(TRIGGER_ANNOTATION, '[]'))
|
||||
triggers = yaml.safe_load(
|
||||
existing["metadata"]
|
||||
.get("annotations", "{}")
|
||||
.get(TRIGGER_ANNOTATION, "[]")
|
||||
)
|
||||
|
||||
if not isinstance(triggers, list):
|
||||
return definition
|
||||
|
||||
for trigger in triggers:
|
||||
if trigger.get('fieldPath'):
|
||||
parsed = self.parse_trigger_fieldpath(trigger['fieldPath'])
|
||||
path = parsed.get('path', '').split('.')
|
||||
if trigger.get("fieldPath"):
|
||||
parsed = self.parse_trigger_fieldpath(trigger["fieldPath"])
|
||||
path = parsed.get("path", "").split(".")
|
||||
if path:
|
||||
existing_containers = get_from_fields(existing, path)
|
||||
new_containers = get_from_fields(definition, path)
|
||||
if parsed.get('name'):
|
||||
existing_index = self.get_index({'name': parsed['name']}, existing_containers, ['name'])
|
||||
new_index = self.get_index({'name': parsed['name']}, new_containers, ['name'])
|
||||
elif parsed.get('index') is not None:
|
||||
existing_index = new_index = int(parsed['index'])
|
||||
if parsed.get("name"):
|
||||
existing_index = self.get_index(
|
||||
{"name": parsed["name"]}, existing_containers, ["name"]
|
||||
)
|
||||
new_index = self.get_index(
|
||||
{"name": parsed["name"]}, new_containers, ["name"]
|
||||
)
|
||||
elif parsed.get("index") is not None:
|
||||
existing_index = new_index = int(parsed["index"])
|
||||
else:
|
||||
existing_index = new_index = None
|
||||
if existing_index is not None and new_index is not None:
|
||||
if existing_index < len(existing_containers) and new_index < len(new_containers):
|
||||
set_from_fields(definition, path + [new_index, 'image'], get_from_fields(existing, path + [existing_index, 'image']))
|
||||
if existing_index < len(
|
||||
existing_containers
|
||||
) and new_index < len(new_containers):
|
||||
set_from_fields(
|
||||
definition,
|
||||
path + [new_index, "image"],
|
||||
get_from_fields(
|
||||
existing, path + [existing_index, "image"]
|
||||
),
|
||||
)
|
||||
return definition
|
||||
|
||||
def resolve_imagestream_triggers(self, existing, definition):
|
||||
|
||||
existing_triggers = existing.get('spec', {}).get('triggers')
|
||||
new_triggers = definition['spec']['triggers']
|
||||
existing_containers = existing.get('spec', {}).get('template', {}).get('spec', {}).get('containers', [])
|
||||
new_containers = definition.get('spec', {}).get('template', {}).get('spec', {}).get('containers', [])
|
||||
existing_triggers = existing.get("spec", {}).get("triggers")
|
||||
new_triggers = definition["spec"]["triggers"]
|
||||
existing_containers = (
|
||||
existing.get("spec", {})
|
||||
.get("template", {})
|
||||
.get("spec", {})
|
||||
.get("containers", [])
|
||||
)
|
||||
new_containers = (
|
||||
definition.get("spec", {})
|
||||
.get("template", {})
|
||||
.get("spec", {})
|
||||
.get("containers", [])
|
||||
)
|
||||
for i, trigger in enumerate(new_triggers):
|
||||
if trigger.get('type') == 'ImageChange' and trigger.get('imageChangeParams'):
|
||||
names = trigger['imageChangeParams'].get('containerNames', [])
|
||||
if trigger.get("type") == "ImageChange" and trigger.get(
|
||||
"imageChangeParams"
|
||||
):
|
||||
names = trigger["imageChangeParams"].get("containerNames", [])
|
||||
for name in names:
|
||||
old_container_index = self.get_index({'name': name}, existing_containers, ['name'])
|
||||
new_container_index = self.get_index({'name': name}, new_containers, ['name'])
|
||||
if old_container_index is not None and new_container_index is not None:
|
||||
image = existing['spec']['template']['spec']['containers'][old_container_index]['image']
|
||||
definition['spec']['template']['spec']['containers'][new_container_index]['image'] = image
|
||||
old_container_index = self.get_index(
|
||||
{"name": name}, existing_containers, ["name"]
|
||||
)
|
||||
new_container_index = self.get_index(
|
||||
{"name": name}, new_containers, ["name"]
|
||||
)
|
||||
if (
|
||||
old_container_index is not None
|
||||
and new_container_index is not None
|
||||
):
|
||||
image = existing["spec"]["template"]["spec"]["containers"][
|
||||
old_container_index
|
||||
]["image"]
|
||||
definition["spec"]["template"]["spec"]["containers"][
|
||||
new_container_index
|
||||
]["image"] = image
|
||||
|
||||
existing_index = self.get_index(trigger['imageChangeParams'],
|
||||
[x.get('imageChangeParams') for x in existing_triggers],
|
||||
['containerNames'])
|
||||
existing_index = self.get_index(
|
||||
trigger["imageChangeParams"],
|
||||
[x.get("imageChangeParams") for x in existing_triggers],
|
||||
["containerNames"],
|
||||
)
|
||||
if existing_index is not None:
|
||||
existing_image = existing_triggers[existing_index].get('imageChangeParams', {}).get('lastTriggeredImage')
|
||||
existing_image = (
|
||||
existing_triggers[existing_index]
|
||||
.get("imageChangeParams", {})
|
||||
.get("lastTriggeredImage")
|
||||
)
|
||||
if existing_image:
|
||||
definition['spec']['triggers'][i]['imageChangeParams']['lastTriggeredImage'] = existing_image
|
||||
existing_from = existing_triggers[existing_index].get('imageChangeParams', {}).get('from', {})
|
||||
new_from = trigger['imageChangeParams'].get('from', {})
|
||||
existing_namespace = existing_from.get('namespace')
|
||||
existing_name = existing_from.get('name', False)
|
||||
new_name = new_from.get('name', True)
|
||||
add_namespace = existing_namespace and 'namespace' not in new_from.keys() and existing_name == new_name
|
||||
definition["spec"]["triggers"][i]["imageChangeParams"][
|
||||
"lastTriggeredImage"
|
||||
] = existing_image
|
||||
existing_from = (
|
||||
existing_triggers[existing_index]
|
||||
.get("imageChangeParams", {})
|
||||
.get("from", {})
|
||||
)
|
||||
new_from = trigger["imageChangeParams"].get("from", {})
|
||||
existing_namespace = existing_from.get("namespace")
|
||||
existing_name = existing_from.get("name", False)
|
||||
new_name = new_from.get("name", True)
|
||||
add_namespace = (
|
||||
existing_namespace
|
||||
and "namespace" not in new_from.keys()
|
||||
and existing_name == new_name
|
||||
)
|
||||
if add_namespace:
|
||||
definition['spec']['triggers'][i]['imageChangeParams']['from']['namespace'] = existing_from['namespace']
|
||||
definition["spec"]["triggers"][i]["imageChangeParams"][
|
||||
"from"
|
||||
]["namespace"] = existing_from["namespace"]
|
||||
|
||||
return definition
|
||||
|
||||
def parse_trigger_fieldpath(self, expression):
|
||||
parsed = TRIGGER_CONTAINER.search(expression).groupdict()
|
||||
if parsed.get('index'):
|
||||
parsed['index'] = int(parsed['index'])
|
||||
if parsed.get("index"):
|
||||
parsed["index"] = int(parsed["index"])
|
||||
return parsed
|
||||
|
||||
def create_project_request(self, definition):
|
||||
definition['kind'] = 'ProjectRequest'
|
||||
result = {'changed': False, 'result': {}}
|
||||
resource = self.svc.find_resource(kind='ProjectRequest', api_version=definition['apiVersion'], fail=True)
|
||||
definition["kind"] = "ProjectRequest"
|
||||
result = {"changed": False, "result": {}}
|
||||
resource = self.svc.find_resource(
|
||||
kind="ProjectRequest", api_version=definition["apiVersion"], fail=True
|
||||
)
|
||||
if not self.check_mode:
|
||||
try:
|
||||
k8s_obj = resource.create(definition)
|
||||
result['result'] = k8s_obj.to_dict()
|
||||
result["result"] = k8s_obj.to_dict()
|
||||
except DynamicApiError as exc:
|
||||
self.fail_json(msg="Failed to create object: {0}".format(exc.body),
|
||||
error=exc.status, status=exc.status, reason=exc.reason)
|
||||
result['changed'] = True
|
||||
result['method'] = 'create'
|
||||
self.fail_json(
|
||||
msg="Failed to create object: {0}".format(exc.body),
|
||||
error=exc.status,
|
||||
status=exc.status,
|
||||
reason=exc.reason,
|
||||
)
|
||||
result["changed"] = True
|
||||
result["method"] = "create"
|
||||
return result
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
from ansible_collections.community.okd.plugins.module_utils.openshift_common import AnsibleOpenshiftModule
|
||||
from ansible_collections.community.okd.plugins.module_utils.openshift_common import (
|
||||
AnsibleOpenshiftModule,
|
||||
)
|
||||
|
||||
try:
|
||||
from kubernetes import client
|
||||
@@ -18,31 +21,36 @@ class OpenShiftAdmPruneAuth(AnsibleOpenshiftModule):
|
||||
def __init__(self, **kwargs):
|
||||
super(OpenShiftAdmPruneAuth, self).__init__(**kwargs)
|
||||
|
||||
def prune_resource_binding(self, kind, api_version, ref_kind, ref_namespace_names, propagation_policy=None):
|
||||
|
||||
def prune_resource_binding(
|
||||
self, kind, api_version, ref_kind, ref_namespace_names, propagation_policy=None
|
||||
):
|
||||
resource = self.find_resource(kind=kind, api_version=api_version, fail=True)
|
||||
candidates = []
|
||||
for ref_namespace, ref_name in ref_namespace_names:
|
||||
try:
|
||||
result = resource.get(name=None, namespace=ref_namespace)
|
||||
result = result.to_dict()
|
||||
result = result.get('items') if 'items' in result else [result]
|
||||
result = result.get("items") if "items" in result else [result]
|
||||
for obj in result:
|
||||
namespace = obj['metadata'].get('namespace', None)
|
||||
name = obj['metadata'].get('name')
|
||||
if ref_kind and obj['roleRef']['kind'] != ref_kind:
|
||||
namespace = obj["metadata"].get("namespace", None)
|
||||
name = obj["metadata"].get("name")
|
||||
if ref_kind and obj["roleRef"]["kind"] != ref_kind:
|
||||
# skip this binding as the roleRef.kind does not match
|
||||
continue
|
||||
if obj['roleRef']['name'] == ref_name:
|
||||
if obj["roleRef"]["name"] == ref_name:
|
||||
# select this binding as the roleRef.name match
|
||||
candidates.append((namespace, name))
|
||||
except NotFoundError:
|
||||
continue
|
||||
except DynamicApiError as exc:
|
||||
msg = "Failed to get {kind} resource due to: {msg}".format(kind=kind, msg=exc.body)
|
||||
msg = "Failed to get {kind} resource due to: {msg}".format(
|
||||
kind=kind, msg=exc.body
|
||||
)
|
||||
self.fail_json(msg=msg)
|
||||
except Exception as e:
|
||||
msg = "Failed to get {kind} due to: {msg}".format(kind=kind, msg=to_native(e))
|
||||
msg = "Failed to get {kind} due to: {msg}".format(
|
||||
kind=kind, msg=to_native(e)
|
||||
)
|
||||
self.fail_json(msg=msg)
|
||||
|
||||
if len(candidates) == 0 or self.check_mode:
|
||||
@@ -54,24 +62,29 @@ class OpenShiftAdmPruneAuth(AnsibleOpenshiftModule):
|
||||
|
||||
for namespace, name in candidates:
|
||||
try:
|
||||
result = resource.delete(name=name, namespace=namespace, body=delete_options)
|
||||
result = resource.delete(
|
||||
name=name, namespace=namespace, body=delete_options
|
||||
)
|
||||
except DynamicApiError as exc:
|
||||
msg = "Failed to delete {kind} {namespace}/{name} due to: {msg}".format(kind=kind, namespace=namespace, name=name, msg=exc.body)
|
||||
msg = "Failed to delete {kind} {namespace}/{name} due to: {msg}".format(
|
||||
kind=kind, namespace=namespace, name=name, msg=exc.body
|
||||
)
|
||||
self.fail_json(msg=msg)
|
||||
except Exception as e:
|
||||
msg = "Failed to delete {kind} {namespace}/{name} due to: {msg}".format(kind=kind, namespace=namespace, name=name, msg=to_native(e))
|
||||
msg = "Failed to delete {kind} {namespace}/{name} due to: {msg}".format(
|
||||
kind=kind, namespace=namespace, name=name, msg=to_native(e)
|
||||
)
|
||||
self.fail_json(msg=msg)
|
||||
return [y if x is None else x + "/" + y for x, y in candidates]
|
||||
|
||||
def update_resource_binding(self, ref_kind, ref_names, namespaced=False):
|
||||
|
||||
kind = 'ClusterRoleBinding'
|
||||
api_version = "rbac.authorization.k8s.io/v1",
|
||||
kind = "ClusterRoleBinding"
|
||||
api_version = "rbac.authorization.k8s.io/v1"
|
||||
if namespaced:
|
||||
kind = "RoleBinding"
|
||||
resource = self.find_resource(kind=kind, api_version=api_version, fail=True)
|
||||
result = resource.get(name=None, namespace=None).to_dict()
|
||||
result = result.get('items') if 'items' in result else [result]
|
||||
result = result.get("items") if "items" in result else [result]
|
||||
|
||||
if len(result) == 0:
|
||||
return [], False
|
||||
@@ -79,29 +92,40 @@ class OpenShiftAdmPruneAuth(AnsibleOpenshiftModule):
|
||||
def _update_user_group(binding_namespace, subjects):
|
||||
users, groups = [], []
|
||||
for x in subjects:
|
||||
if x['kind'] == 'User':
|
||||
users.append(x['name'])
|
||||
elif x['kind'] == 'Group':
|
||||
groups.append(x['name'])
|
||||
elif x['kind'] == 'ServiceAccount':
|
||||
if x["kind"] == "User":
|
||||
users.append(x["name"])
|
||||
elif x["kind"] == "Group":
|
||||
groups.append(x["name"])
|
||||
elif x["kind"] == "ServiceAccount":
|
||||
namespace = binding_namespace
|
||||
if x.get('namespace') is not None:
|
||||
namespace = x.get('namespace')
|
||||
if x.get("namespace") is not None:
|
||||
namespace = x.get("namespace")
|
||||
if namespace is not None:
|
||||
users.append("system:serviceaccount:%s:%s" % (namespace, x['name']))
|
||||
users.append(
|
||||
"system:serviceaccount:%s:%s" % (namespace, x["name"])
|
||||
)
|
||||
return users, groups
|
||||
|
||||
candidates = []
|
||||
changed = False
|
||||
for item in result:
|
||||
subjects = item.get('subjects', [])
|
||||
retainedSubjects = [x for x in subjects if x['kind'] == ref_kind and x['name'] in ref_names]
|
||||
subjects = item.get("subjects", [])
|
||||
retainedSubjects = [
|
||||
x for x in subjects if x["kind"] == ref_kind and x["name"] in ref_names
|
||||
]
|
||||
if len(subjects) != len(retainedSubjects):
|
||||
updated_binding = item
|
||||
updated_binding['subjects'] = retainedSubjects
|
||||
binding_namespace = item['metadata'].get('namespace', None)
|
||||
updated_binding['userNames'], updated_binding['groupNames'] = _update_user_group(binding_namespace, retainedSubjects)
|
||||
candidates.append(binding_namespace + "/" + item['metadata']['name'] if binding_namespace else item['metadata']['name'])
|
||||
updated_binding["subjects"] = retainedSubjects
|
||||
binding_namespace = item["metadata"].get("namespace", None)
|
||||
(
|
||||
updated_binding["userNames"],
|
||||
updated_binding["groupNames"],
|
||||
) = _update_user_group(binding_namespace, retainedSubjects)
|
||||
candidates.append(
|
||||
binding_namespace + "/" + item["metadata"]["name"]
|
||||
if binding_namespace
|
||||
else item["metadata"]["name"]
|
||||
)
|
||||
changed = True
|
||||
if not self.check_mode:
|
||||
try:
|
||||
@@ -112,20 +136,25 @@ class OpenShiftAdmPruneAuth(AnsibleOpenshiftModule):
|
||||
return candidates, changed
|
||||
|
||||
def update_security_context(self, ref_names, key):
|
||||
params = {'kind': 'SecurityContextConstraints', 'api_version': 'security.openshift.io/v1'}
|
||||
params = {
|
||||
"kind": "SecurityContextConstraints",
|
||||
"api_version": "security.openshift.io/v1",
|
||||
}
|
||||
sccs = self.kubernetes_facts(**params)
|
||||
if not sccs['api_found']:
|
||||
self.fail_json(msg=sccs['msg'])
|
||||
sccs = sccs.get('resources')
|
||||
if not sccs["api_found"]:
|
||||
self.fail_json(msg=sccs["msg"])
|
||||
sccs = sccs.get("resources")
|
||||
|
||||
candidates = []
|
||||
changed = False
|
||||
resource = self.find_resource(kind="SecurityContextConstraints", api_version="security.openshift.io/v1")
|
||||
resource = self.find_resource(
|
||||
kind="SecurityContextConstraints", api_version="security.openshift.io/v1"
|
||||
)
|
||||
for item in sccs:
|
||||
subjects = item.get(key, [])
|
||||
retainedSubjects = [x for x in subjects if x not in ref_names]
|
||||
if len(subjects) != len(retainedSubjects):
|
||||
candidates.append(item['metadata']['name'])
|
||||
candidates.append(item["metadata"]["name"])
|
||||
changed = True
|
||||
if not self.check_mode:
|
||||
upd_sec_ctx = item
|
||||
@@ -138,94 +167,116 @@ class OpenShiftAdmPruneAuth(AnsibleOpenshiftModule):
|
||||
return candidates, changed
|
||||
|
||||
def auth_prune_roles(self):
|
||||
params = {'kind': 'Role', 'api_version': 'rbac.authorization.k8s.io/v1', 'namespace': self.params.get('namespace')}
|
||||
for attr in ('name', 'label_selectors'):
|
||||
params = {
|
||||
"kind": "Role",
|
||||
"api_version": "rbac.authorization.k8s.io/v1",
|
||||
"namespace": self.params.get("namespace"),
|
||||
}
|
||||
for attr in ("name", "label_selectors"):
|
||||
if self.params.get(attr):
|
||||
params[attr] = self.params.get(attr)
|
||||
|
||||
result = self.kubernetes_facts(**params)
|
||||
if not result['api_found']:
|
||||
self.fail_json(msg=result['msg'])
|
||||
if not result["api_found"]:
|
||||
self.fail_json(msg=result["msg"])
|
||||
|
||||
roles = result.get('resources')
|
||||
roles = result.get("resources")
|
||||
if len(roles) == 0:
|
||||
self.exit_json(changed=False, msg="No candidate rolebinding to prune from namespace %s." % self.params.get('namespace'))
|
||||
self.exit_json(
|
||||
changed=False,
|
||||
msg="No candidate rolebinding to prune from namespace %s."
|
||||
% self.params.get("namespace"),
|
||||
)
|
||||
|
||||
ref_roles = [(x['metadata']['namespace'], x['metadata']['name']) for x in roles]
|
||||
candidates = self.prune_resource_binding(kind="RoleBinding",
|
||||
api_version="rbac.authorization.k8s.io/v1",
|
||||
ref_kind="Role",
|
||||
ref_namespace_names=ref_roles,
|
||||
propagation_policy='Foreground')
|
||||
ref_roles = [(x["metadata"]["namespace"], x["metadata"]["name"]) for x in roles]
|
||||
candidates = self.prune_resource_binding(
|
||||
kind="RoleBinding",
|
||||
api_version="rbac.authorization.k8s.io/v1",
|
||||
ref_kind="Role",
|
||||
ref_namespace_names=ref_roles,
|
||||
propagation_policy="Foreground",
|
||||
)
|
||||
if len(candidates) == 0:
|
||||
self.exit_json(changed=False, role_binding=candidates)
|
||||
|
||||
self.exit_json(changed=True, role_binding=candidates)
|
||||
|
||||
def auth_prune_clusterroles(self):
|
||||
params = {'kind': 'ClusterRole', 'api_version': 'rbac.authorization.k8s.io/v1'}
|
||||
for attr in ('name', 'label_selectors'):
|
||||
params = {"kind": "ClusterRole", "api_version": "rbac.authorization.k8s.io/v1"}
|
||||
for attr in ("name", "label_selectors"):
|
||||
if self.params.get(attr):
|
||||
params[attr] = self.params.get(attr)
|
||||
|
||||
result = self.kubernetes_facts(**params)
|
||||
if not result['api_found']:
|
||||
self.fail_json(msg=result['msg'])
|
||||
if not result["api_found"]:
|
||||
self.fail_json(msg=result["msg"])
|
||||
|
||||
clusterroles = result.get('resources')
|
||||
clusterroles = result.get("resources")
|
||||
if len(clusterroles) == 0:
|
||||
self.exit_json(changed=False, msg="No clusterroles found matching input criteria.")
|
||||
self.exit_json(
|
||||
changed=False, msg="No clusterroles found matching input criteria."
|
||||
)
|
||||
|
||||
ref_clusterroles = [(None, x['metadata']['name']) for x in clusterroles]
|
||||
ref_clusterroles = [(None, x["metadata"]["name"]) for x in clusterroles]
|
||||
|
||||
# Prune ClusterRoleBinding
|
||||
candidates_cluster_binding = self.prune_resource_binding(kind="ClusterRoleBinding",
|
||||
api_version="rbac.authorization.k8s.io/v1",
|
||||
ref_kind=None,
|
||||
ref_namespace_names=ref_clusterroles)
|
||||
candidates_cluster_binding = self.prune_resource_binding(
|
||||
kind="ClusterRoleBinding",
|
||||
api_version="rbac.authorization.k8s.io/v1",
|
||||
ref_kind=None,
|
||||
ref_namespace_names=ref_clusterroles,
|
||||
)
|
||||
|
||||
# Prune Role Binding
|
||||
candidates_namespaced_binding = self.prune_resource_binding(kind="RoleBinding",
|
||||
api_version="rbac.authorization.k8s.io/v1",
|
||||
ref_kind='ClusterRole',
|
||||
ref_namespace_names=ref_clusterroles)
|
||||
candidates_namespaced_binding = self.prune_resource_binding(
|
||||
kind="RoleBinding",
|
||||
api_version="rbac.authorization.k8s.io/v1",
|
||||
ref_kind="ClusterRole",
|
||||
ref_namespace_names=ref_clusterroles,
|
||||
)
|
||||
|
||||
self.exit_json(changed=True,
|
||||
cluster_role_binding=candidates_cluster_binding,
|
||||
role_binding=candidates_namespaced_binding)
|
||||
self.exit_json(
|
||||
changed=True,
|
||||
cluster_role_binding=candidates_cluster_binding,
|
||||
role_binding=candidates_namespaced_binding,
|
||||
)
|
||||
|
||||
def list_groups(self, params=None):
|
||||
options = {'kind': 'Group', 'api_version': 'user.openshift.io/v1'}
|
||||
options = {"kind": "Group", "api_version": "user.openshift.io/v1"}
|
||||
if params:
|
||||
for attr in ('name', 'label_selectors'):
|
||||
for attr in ("name", "label_selectors"):
|
||||
if params.get(attr):
|
||||
options[attr] = params.get(attr)
|
||||
return self.kubernetes_facts(**options)
|
||||
|
||||
def auth_prune_users(self):
|
||||
params = {'kind': 'User', 'api_version': 'user.openshift.io/v1'}
|
||||
for attr in ('name', 'label_selectors'):
|
||||
params = {"kind": "User", "api_version": "user.openshift.io/v1"}
|
||||
for attr in ("name", "label_selectors"):
|
||||
if self.params.get(attr):
|
||||
params[attr] = self.params.get(attr)
|
||||
|
||||
users = self.kubernetes_facts(**params)
|
||||
if len(users) == 0:
|
||||
self.exit_json(changed=False, msg="No resource type 'User' found matching input criteria.")
|
||||
self.exit_json(
|
||||
changed=False,
|
||||
msg="No resource type 'User' found matching input criteria.",
|
||||
)
|
||||
|
||||
names = [x['metadata']['name'] for x in users]
|
||||
names = [x["metadata"]["name"] for x in users]
|
||||
changed = False
|
||||
# Remove the user role binding
|
||||
rolebinding, changed_role = self.update_resource_binding(ref_kind="User",
|
||||
ref_names=names,
|
||||
namespaced=True)
|
||||
rolebinding, changed_role = self.update_resource_binding(
|
||||
ref_kind="User", ref_names=names, namespaced=True
|
||||
)
|
||||
changed = changed or changed_role
|
||||
# Remove the user cluster role binding
|
||||
clusterrolesbinding, changed_cr = self.update_resource_binding(ref_kind="User",
|
||||
ref_names=names)
|
||||
clusterrolesbinding, changed_cr = self.update_resource_binding(
|
||||
ref_kind="User", ref_names=names
|
||||
)
|
||||
changed = changed or changed_cr
|
||||
|
||||
# Remove the user from security context constraints
|
||||
sccs, changed_sccs = self.update_security_context(names, 'users')
|
||||
sccs, changed_sccs = self.update_security_context(names, "users")
|
||||
changed = changed or changed_sccs
|
||||
|
||||
# Remove the user from groups
|
||||
@@ -233,14 +284,14 @@ class OpenShiftAdmPruneAuth(AnsibleOpenshiftModule):
|
||||
deleted_groups = []
|
||||
resource = self.find_resource(kind="Group", api_version="user.openshift.io/v1")
|
||||
for grp in groups:
|
||||
subjects = grp.get('users', [])
|
||||
subjects = grp.get("users", [])
|
||||
retainedSubjects = [x for x in subjects if x not in names]
|
||||
if len(subjects) != len(retainedSubjects):
|
||||
deleted_groups.append(grp['metadata']['name'])
|
||||
deleted_groups.append(grp["metadata"]["name"])
|
||||
changed = True
|
||||
if not self.check_mode:
|
||||
upd_group = grp
|
||||
upd_group.update({'users': retainedSubjects})
|
||||
upd_group.update({"users": retainedSubjects})
|
||||
try:
|
||||
resource.apply(upd_group, namespace=None)
|
||||
except DynamicApiError as exc:
|
||||
@@ -248,62 +299,82 @@ class OpenShiftAdmPruneAuth(AnsibleOpenshiftModule):
|
||||
self.fail_json(msg=msg)
|
||||
|
||||
# Remove the user's OAuthClientAuthorizations
|
||||
oauth = self.kubernetes_facts(kind='OAuthClientAuthorization', api_version='oauth.openshift.io/v1')
|
||||
oauth = self.kubernetes_facts(
|
||||
kind="OAuthClientAuthorization", api_version="oauth.openshift.io/v1"
|
||||
)
|
||||
deleted_auths = []
|
||||
resource = self.find_resource(kind="OAuthClientAuthorization", api_version="oauth.openshift.io/v1")
|
||||
resource = self.find_resource(
|
||||
kind="OAuthClientAuthorization", api_version="oauth.openshift.io/v1"
|
||||
)
|
||||
for authorization in oauth:
|
||||
if authorization.get('userName', None) in names:
|
||||
auth_name = authorization['metadata']['name']
|
||||
if authorization.get("userName", None) in names:
|
||||
auth_name = authorization["metadata"]["name"]
|
||||
deleted_auths.append(auth_name)
|
||||
changed = True
|
||||
if not self.check_mode:
|
||||
try:
|
||||
resource.delete(name=auth_name, namespace=None, body=client.V1DeleteOptions())
|
||||
resource.delete(
|
||||
name=auth_name,
|
||||
namespace=None,
|
||||
body=client.V1DeleteOptions(),
|
||||
)
|
||||
except DynamicApiError as exc:
|
||||
msg = "Failed to delete OAuthClientAuthorization {name} due to: {msg}".format(name=auth_name, msg=exc.body)
|
||||
msg = "Failed to delete OAuthClientAuthorization {name} due to: {msg}".format(
|
||||
name=auth_name, msg=exc.body
|
||||
)
|
||||
self.fail_json(msg=msg)
|
||||
except Exception as e:
|
||||
msg = "Failed to delete OAuthClientAuthorization {name} due to: {msg}".format(name=auth_name, msg=to_native(e))
|
||||
msg = "Failed to delete OAuthClientAuthorization {name} due to: {msg}".format(
|
||||
name=auth_name, msg=to_native(e)
|
||||
)
|
||||
self.fail_json(msg=msg)
|
||||
|
||||
self.exit_json(changed=changed,
|
||||
cluster_role_binding=clusterrolesbinding,
|
||||
role_binding=rolebinding,
|
||||
security_context_constraints=sccs,
|
||||
authorization=deleted_auths,
|
||||
group=deleted_groups)
|
||||
self.exit_json(
|
||||
changed=changed,
|
||||
cluster_role_binding=clusterrolesbinding,
|
||||
role_binding=rolebinding,
|
||||
security_context_constraints=sccs,
|
||||
authorization=deleted_auths,
|
||||
group=deleted_groups,
|
||||
)
|
||||
|
||||
def auth_prune_groups(self):
|
||||
groups = self.list_groups(params=self.params)
|
||||
if len(groups) == 0:
|
||||
self.exit_json(changed=False, result="No resource type 'Group' found matching input criteria.")
|
||||
self.exit_json(
|
||||
changed=False,
|
||||
result="No resource type 'Group' found matching input criteria.",
|
||||
)
|
||||
|
||||
names = [x['metadata']['name'] for x in groups]
|
||||
names = [x["metadata"]["name"] for x in groups]
|
||||
|
||||
changed = False
|
||||
# Remove the groups role binding
|
||||
rolebinding, changed_role = self.update_resource_binding(ref_kind="Group",
|
||||
ref_names=names,
|
||||
namespaced=True)
|
||||
rolebinding, changed_role = self.update_resource_binding(
|
||||
ref_kind="Group", ref_names=names, namespaced=True
|
||||
)
|
||||
changed = changed or changed_role
|
||||
# Remove the groups cluster role binding
|
||||
clusterrolesbinding, changed_cr = self.update_resource_binding(ref_kind="Group",
|
||||
ref_names=names)
|
||||
clusterrolesbinding, changed_cr = self.update_resource_binding(
|
||||
ref_kind="Group", ref_names=names
|
||||
)
|
||||
changed = changed or changed_cr
|
||||
# Remove the groups security context constraints
|
||||
sccs, changed_sccs = self.update_security_context(names, 'groups')
|
||||
sccs, changed_sccs = self.update_security_context(names, "groups")
|
||||
changed = changed or changed_sccs
|
||||
|
||||
self.exit_json(changed=changed,
|
||||
cluster_role_binding=clusterrolesbinding,
|
||||
role_binding=rolebinding,
|
||||
security_context_constraints=sccs)
|
||||
self.exit_json(
|
||||
changed=changed,
|
||||
cluster_role_binding=clusterrolesbinding,
|
||||
role_binding=rolebinding,
|
||||
security_context_constraints=sccs,
|
||||
)
|
||||
|
||||
def execute_module(self):
|
||||
auth_prune = {
|
||||
'roles': self.auth_prune_roles,
|
||||
'clusterroles': self.auth_prune_clusterroles,
|
||||
'users': self.auth_prune_users,
|
||||
'groups': self.auth_prune_groups,
|
||||
"roles": self.auth_prune_roles,
|
||||
"clusterroles": self.auth_prune_clusterroles,
|
||||
"users": self.auth_prune_users,
|
||||
"groups": self.auth_prune_groups,
|
||||
}
|
||||
auth_prune[self.params.get('resource')]()
|
||||
auth_prune[self.params.get("resource")]()
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
from datetime import datetime, timezone
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
from ansible_collections.community.okd.plugins.module_utils.openshift_common import AnsibleOpenshiftModule
|
||||
from ansible_collections.community.okd.plugins.module_utils.openshift_common import (
|
||||
AnsibleOpenshiftModule,
|
||||
)
|
||||
|
||||
try:
|
||||
from kubernetes import client
|
||||
@@ -23,7 +25,9 @@ def get_deploymentconfig_for_replicationcontroller(replica_controller):
|
||||
# This is set on replication controller pod template by deployer controller.
|
||||
DeploymentConfigAnnotation = "openshift.io/deployment-config.name"
|
||||
try:
|
||||
deploymentconfig_name = replica_controller['metadata']['annotations'].get(DeploymentConfigAnnotation)
|
||||
deploymentconfig_name = replica_controller["metadata"]["annotations"].get(
|
||||
DeploymentConfigAnnotation
|
||||
)
|
||||
if deploymentconfig_name is None or deploymentconfig_name == "":
|
||||
return None
|
||||
return deploymentconfig_name
|
||||
@@ -32,7 +36,6 @@ def get_deploymentconfig_for_replicationcontroller(replica_controller):
|
||||
|
||||
|
||||
class OpenShiftAdmPruneDeployment(AnsibleOpenshiftModule):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(OpenShiftAdmPruneDeployment, self).__init__(**kwargs)
|
||||
|
||||
@@ -41,27 +44,33 @@ class OpenShiftAdmPruneDeployment(AnsibleOpenshiftModule):
|
||||
return get_deploymentconfig_for_replicationcontroller(obj) is not None
|
||||
|
||||
def _zeroReplicaSize(obj):
|
||||
return obj['spec']['replicas'] == 0 and obj['status']['replicas'] == 0
|
||||
return obj["spec"]["replicas"] == 0 and obj["status"]["replicas"] == 0
|
||||
|
||||
def _complete_failed(obj):
|
||||
DeploymentStatusAnnotation = "openshift.io/deployment.phase"
|
||||
try:
|
||||
# validate that replication controller status is either 'Complete' or 'Failed'
|
||||
deployment_phase = obj['metadata']['annotations'].get(DeploymentStatusAnnotation)
|
||||
return deployment_phase in ('Failed', 'Complete')
|
||||
deployment_phase = obj["metadata"]["annotations"].get(
|
||||
DeploymentStatusAnnotation
|
||||
)
|
||||
return deployment_phase in ("Failed", "Complete")
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _younger(obj):
|
||||
creation_timestamp = datetime.strptime(obj['metadata']['creationTimestamp'], '%Y-%m-%dT%H:%M:%SZ')
|
||||
creation_timestamp = datetime.strptime(
|
||||
obj["metadata"]["creationTimestamp"], "%Y-%m-%dT%H:%M:%SZ"
|
||||
)
|
||||
now = datetime.now(timezone.utc).replace(tzinfo=None)
|
||||
age = (now - creation_timestamp).seconds / 60
|
||||
return age > self.params['keep_younger_than']
|
||||
return age > self.params["keep_younger_than"]
|
||||
|
||||
def _orphan(obj):
|
||||
try:
|
||||
# verify if the deploymentconfig associated to the replication controller is still existing
|
||||
deploymentconfig_name = get_deploymentconfig_for_replicationcontroller(obj)
|
||||
deploymentconfig_name = get_deploymentconfig_for_replicationcontroller(
|
||||
obj
|
||||
)
|
||||
params = dict(
|
||||
kind="DeploymentConfig",
|
||||
api_version="apps.openshift.io/v1",
|
||||
@@ -69,14 +78,14 @@ class OpenShiftAdmPruneDeployment(AnsibleOpenshiftModule):
|
||||
namespace=obj["metadata"]["name"],
|
||||
)
|
||||
exists = self.kubernetes_facts(**params)
|
||||
return not (exists.get['api_found'] and len(exists['resources']) > 0)
|
||||
return not (exists.get["api_found"] and len(exists["resources"]) > 0)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
predicates = [_deployment, _zeroReplicaSize, _complete_failed]
|
||||
if self.params['orphans']:
|
||||
if self.params["orphans"]:
|
||||
predicates.append(_orphan)
|
||||
if self.params['keep_younger_than']:
|
||||
if self.params["keep_younger_than"]:
|
||||
predicates.append(_younger)
|
||||
|
||||
results = replicacontrollers.copy()
|
||||
@@ -86,8 +95,8 @@ class OpenShiftAdmPruneDeployment(AnsibleOpenshiftModule):
|
||||
|
||||
def execute_module(self):
|
||||
# list replicationcontroller candidate for pruning
|
||||
kind = 'ReplicationController'
|
||||
api_version = 'v1'
|
||||
kind = "ReplicationController"
|
||||
api_version = "v1"
|
||||
resource = self.find_resource(kind=kind, api_version=api_version, fail=True)
|
||||
|
||||
# Get ReplicationController
|
||||
@@ -103,7 +112,7 @@ class OpenShiftAdmPruneDeployment(AnsibleOpenshiftModule):
|
||||
self.exit_json(changed=False, replication_controllers=[])
|
||||
|
||||
changed = True
|
||||
delete_options = client.V1DeleteOptions(propagation_policy='Background')
|
||||
delete_options = client.V1DeleteOptions(propagation_policy="Background")
|
||||
replication_controllers = []
|
||||
for replica in candidates:
|
||||
try:
|
||||
@@ -111,12 +120,18 @@ class OpenShiftAdmPruneDeployment(AnsibleOpenshiftModule):
|
||||
if not self.check_mode:
|
||||
name = replica["metadata"]["name"]
|
||||
namespace = replica["metadata"]["namespace"]
|
||||
result = resource.delete(name=name, namespace=namespace, body=delete_options).to_dict()
|
||||
result = resource.delete(
|
||||
name=name, namespace=namespace, body=delete_options
|
||||
).to_dict()
|
||||
replication_controllers.append(result)
|
||||
except DynamicApiError as exc:
|
||||
msg = "Failed to delete ReplicationController {namespace}/{name} due to: {msg}".format(namespace=namespace, name=name, msg=exc.body)
|
||||
msg = "Failed to delete ReplicationController {namespace}/{name} due to: {msg}".format(
|
||||
namespace=namespace, name=name, msg=exc.body
|
||||
)
|
||||
self.fail_json(msg=msg)
|
||||
except Exception as e:
|
||||
msg = "Failed to delete ReplicationController {namespace}/{name} due to: {msg}".format(namespace=namespace, name=name, msg=to_native(e))
|
||||
msg = "Failed to delete ReplicationController {namespace}/{name} due to: {msg}".format(
|
||||
namespace=namespace, name=name, msg=to_native(e)
|
||||
)
|
||||
self.fail_json(msg=msg)
|
||||
self.exit_json(changed=changed, replication_controllers=replication_controllers)
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
from datetime import datetime, timezone, timedelta
|
||||
import traceback
|
||||
import copy
|
||||
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.module_utils.parsing.convert_bool import boolean
|
||||
from ansible.module_utils.six import iteritems
|
||||
|
||||
from ansible_collections.community.okd.plugins.module_utils.openshift_common import AnsibleOpenshiftModule
|
||||
from ansible_collections.community.okd.plugins.module_utils.openshift_common import (
|
||||
AnsibleOpenshiftModule,
|
||||
)
|
||||
|
||||
from ansible_collections.community.okd.plugins.module_utils.openshift_images_common import (
|
||||
OpenShiftAnalyzeImageStream,
|
||||
@@ -30,7 +32,7 @@ try:
|
||||
from kubernetes.dynamic.exceptions import (
|
||||
DynamicApiError,
|
||||
NotFoundError,
|
||||
ApiException
|
||||
ApiException,
|
||||
)
|
||||
except ImportError:
|
||||
pass
|
||||
@@ -67,18 +69,20 @@ def determine_host_registry(module, images, image_streams):
|
||||
managed_images = list(filter(_f_managed_images, images))
|
||||
|
||||
# Be sure to pick up the newest managed image which should have an up to date information
|
||||
sorted_images = sorted(managed_images,
|
||||
key=lambda x: x["metadata"]["creationTimestamp"],
|
||||
reverse=True)
|
||||
sorted_images = sorted(
|
||||
managed_images, key=lambda x: x["metadata"]["creationTimestamp"], reverse=True
|
||||
)
|
||||
docker_image_ref = ""
|
||||
if len(sorted_images) > 0:
|
||||
docker_image_ref = sorted_images[0].get("dockerImageReference", "")
|
||||
else:
|
||||
# 2nd try to get the pull spec from any image stream
|
||||
# Sorting by creation timestamp may not get us up to date info. Modification time would be much
|
||||
sorted_image_streams = sorted(image_streams,
|
||||
key=lambda x: x["metadata"]["creationTimestamp"],
|
||||
reverse=True)
|
||||
sorted_image_streams = sorted(
|
||||
image_streams,
|
||||
key=lambda x: x["metadata"]["creationTimestamp"],
|
||||
reverse=True,
|
||||
)
|
||||
for i_stream in sorted_image_streams:
|
||||
docker_image_ref = i_stream["status"].get("dockerImageRepository", "")
|
||||
if len(docker_image_ref) > 0:
|
||||
@@ -88,7 +92,7 @@ def determine_host_registry(module, images, image_streams):
|
||||
module.exit_json(changed=False, result="no managed image found")
|
||||
|
||||
result, error = parse_docker_image_ref(docker_image_ref, module)
|
||||
return result['hostname']
|
||||
return result["hostname"]
|
||||
|
||||
|
||||
class OpenShiftAdmPruneImages(AnsibleOpenshiftModule):
|
||||
@@ -97,7 +101,7 @@ class OpenShiftAdmPruneImages(AnsibleOpenshiftModule):
|
||||
|
||||
self.max_creation_timestamp = self.get_max_creation_timestamp()
|
||||
self._rest_client = None
|
||||
self.registryhost = self.params.get('registry_url')
|
||||
self.registryhost = self.params.get("registry_url")
|
||||
self.changed = False
|
||||
|
||||
def list_objects(self):
|
||||
@@ -107,9 +111,9 @@ class OpenShiftAdmPruneImages(AnsibleOpenshiftModule):
|
||||
if self.params.get("namespace") and kind.lower() == "imagestream":
|
||||
namespace = self.params.get("namespace")
|
||||
try:
|
||||
result[kind] = self.kubernetes_facts(kind=kind,
|
||||
api_version=version,
|
||||
namespace=namespace).get('resources')
|
||||
result[kind] = self.kubernetes_facts(
|
||||
kind=kind, api_version=version, namespace=namespace
|
||||
).get("resources")
|
||||
except DynamicApiError as e:
|
||||
self.fail_json(
|
||||
msg="An error occurred while trying to list objects.",
|
||||
@@ -119,7 +123,7 @@ class OpenShiftAdmPruneImages(AnsibleOpenshiftModule):
|
||||
except Exception as e:
|
||||
self.fail_json(
|
||||
msg="An error occurred while trying to list objects.",
|
||||
error=to_native(e)
|
||||
error=to_native(e),
|
||||
)
|
||||
return result
|
||||
|
||||
@@ -134,8 +138,8 @@ class OpenShiftAdmPruneImages(AnsibleOpenshiftModule):
|
||||
def rest_client(self):
|
||||
if not self._rest_client:
|
||||
configuration = copy.deepcopy(self.client.configuration)
|
||||
validate_certs = self.params.get('registry_validate_certs')
|
||||
ssl_ca_cert = self.params.get('registry_ca_cert')
|
||||
validate_certs = self.params.get("registry_validate_certs")
|
||||
ssl_ca_cert = self.params.get("registry_ca_cert")
|
||||
if validate_certs is not None:
|
||||
configuration.verify_ssl = validate_certs
|
||||
if ssl_ca_cert is not None:
|
||||
@@ -146,7 +150,9 @@ class OpenShiftAdmPruneImages(AnsibleOpenshiftModule):
|
||||
|
||||
def delete_from_registry(self, url):
|
||||
try:
|
||||
response = self.rest_client.DELETE(url=url, headers=self.client.configuration.api_key)
|
||||
response = self.rest_client.DELETE(
|
||||
url=url, headers=self.client.configuration.api_key
|
||||
)
|
||||
if response.status == 404:
|
||||
# Unable to delete layer
|
||||
return None
|
||||
@@ -156,8 +162,9 @@ class OpenShiftAdmPruneImages(AnsibleOpenshiftModule):
|
||||
if response.status != 202 and response.status != 204:
|
||||
self.fail_json(
|
||||
msg="Delete URL {0}: Unexpected status code in response: {1}".format(
|
||||
response.status, url),
|
||||
reason=response.reason
|
||||
response.status, url
|
||||
),
|
||||
reason=response.reason,
|
||||
)
|
||||
return None
|
||||
except ApiException as e:
|
||||
@@ -204,9 +211,7 @@ class OpenShiftAdmPruneImages(AnsibleOpenshiftModule):
|
||||
result = self.request(
|
||||
"PUT",
|
||||
"/apis/{api_version}/namespaces/{namespace}/imagestreams/{name}/status".format(
|
||||
api_version=api_version,
|
||||
namespace=namespace,
|
||||
name=name
|
||||
api_version=api_version, namespace=namespace, name=name
|
||||
),
|
||||
body=definition,
|
||||
content_type="application/json",
|
||||
@@ -237,11 +242,10 @@ class OpenShiftAdmPruneImages(AnsibleOpenshiftModule):
|
||||
pass
|
||||
except DynamicApiError as exc:
|
||||
self.fail_json(
|
||||
msg="Failed to delete object %s/%s due to: %s" % (
|
||||
kind, name, exc.body
|
||||
),
|
||||
msg="Failed to delete object %s/%s due to: %s"
|
||||
% (kind, name, exc.body),
|
||||
reason=exc.reason,
|
||||
status=exc.status
|
||||
status=exc.status,
|
||||
)
|
||||
else:
|
||||
existing = resource.get(name=name)
|
||||
@@ -285,9 +289,11 @@ class OpenShiftAdmPruneImages(AnsibleOpenshiftModule):
|
||||
continue
|
||||
|
||||
if idx == 0:
|
||||
istag = "%s/%s:%s" % (stream_namespace,
|
||||
stream_name,
|
||||
tag_event_list["tag"])
|
||||
istag = "%s/%s:%s" % (
|
||||
stream_namespace,
|
||||
stream_name,
|
||||
tag_event_list["tag"],
|
||||
)
|
||||
if istag in self.used_tags:
|
||||
# keeping because tag is used
|
||||
filtered_items.append(item)
|
||||
@@ -302,20 +308,20 @@ class OpenShiftAdmPruneImages(AnsibleOpenshiftModule):
|
||||
|
||||
image = self.image_mapping[item["image"]]
|
||||
# check prune over limit size
|
||||
if prune_over_size_limit and not self.exceeds_limits(stream_namespace, image):
|
||||
if prune_over_size_limit and not self.exceeds_limits(
|
||||
stream_namespace, image
|
||||
):
|
||||
filtered_items.append(item)
|
||||
continue
|
||||
|
||||
image_ref = "%s/%s@%s" % (stream_namespace,
|
||||
stream_name,
|
||||
item["image"])
|
||||
image_ref = "%s/%s@%s" % (stream_namespace, stream_name, item["image"])
|
||||
if image_ref in self.used_images:
|
||||
# keeping because tag is used
|
||||
filtered_items.append(item)
|
||||
continue
|
||||
|
||||
images_to_delete.append(item["image"])
|
||||
if self.params.get('prune_registry'):
|
||||
if self.params.get("prune_registry"):
|
||||
manifests_to_delete.append(image["metadata"]["name"])
|
||||
path = stream_namespace + "/" + stream_name
|
||||
image_blobs, err = get_image_blobs(image)
|
||||
@@ -325,21 +331,25 @@ class OpenShiftAdmPruneImages(AnsibleOpenshiftModule):
|
||||
return filtered_items, manifests_to_delete, images_to_delete
|
||||
|
||||
def prune_image_streams(self, stream):
|
||||
name = stream['metadata']['namespace'] + "/" + stream['metadata']['name']
|
||||
name = stream["metadata"]["namespace"] + "/" + stream["metadata"]["name"]
|
||||
if is_too_young_object(stream, self.max_creation_timestamp):
|
||||
# keeping all images because of image stream too young
|
||||
return None, []
|
||||
facts = self.kubernetes_facts(kind="ImageStream",
|
||||
api_version=ApiConfiguration.get("ImageStream"),
|
||||
name=stream["metadata"]["name"],
|
||||
namespace=stream["metadata"]["namespace"])
|
||||
image_stream = facts.get('resources')
|
||||
facts = self.kubernetes_facts(
|
||||
kind="ImageStream",
|
||||
api_version=ApiConfiguration.get("ImageStream"),
|
||||
name=stream["metadata"]["name"],
|
||||
namespace=stream["metadata"]["namespace"],
|
||||
)
|
||||
image_stream = facts.get("resources")
|
||||
if len(image_stream) != 1:
|
||||
# skipping because it does not exist anymore
|
||||
return None, []
|
||||
stream = image_stream[0]
|
||||
namespace = self.params.get("namespace")
|
||||
stream_to_update = not namespace or (stream["metadata"]["namespace"] == namespace)
|
||||
stream_to_update = not namespace or (
|
||||
stream["metadata"]["namespace"] == namespace
|
||||
)
|
||||
|
||||
manifests_to_delete, images_to_delete = [], []
|
||||
deleted_items = False
|
||||
@@ -351,9 +361,9 @@ class OpenShiftAdmPruneImages(AnsibleOpenshiftModule):
|
||||
(
|
||||
filtered_tag_event,
|
||||
tag_manifests_to_delete,
|
||||
tag_images_to_delete
|
||||
tag_images_to_delete,
|
||||
) = self.prune_image_stream_tag(stream, tag_event_list)
|
||||
stream['status']['tags'][idx]['items'] = filtered_tag_event
|
||||
stream["status"]["tags"][idx]["items"] = filtered_tag_event
|
||||
manifests_to_delete += tag_manifests_to_delete
|
||||
images_to_delete += tag_images_to_delete
|
||||
deleted_items = deleted_items or (len(tag_images_to_delete) > 0)
|
||||
@@ -361,11 +371,11 @@ class OpenShiftAdmPruneImages(AnsibleOpenshiftModule):
|
||||
# Deleting tags without items
|
||||
tags = []
|
||||
for tag in stream["status"].get("tags", []):
|
||||
if tag['items'] is None or len(tag['items']) == 0:
|
||||
if tag["items"] is None or len(tag["items"]) == 0:
|
||||
continue
|
||||
tags.append(tag)
|
||||
|
||||
stream['status']['tags'] = tags
|
||||
stream["status"]["tags"] = tags
|
||||
result = None
|
||||
# Update ImageStream
|
||||
if stream_to_update:
|
||||
@@ -402,19 +412,23 @@ class OpenShiftAdmPruneImages(AnsibleOpenshiftModule):
|
||||
|
||||
def execute_module(self):
|
||||
resources = self.list_objects()
|
||||
if not self.check_mode and self.params.get('prune_registry'):
|
||||
if not self.check_mode and self.params.get("prune_registry"):
|
||||
if not self.registryhost:
|
||||
self.registryhost = determine_host_registry(self.module, resources['Image'], resources['ImageStream'])
|
||||
self.registryhost = determine_host_registry(
|
||||
self.module, resources["Image"], resources["ImageStream"]
|
||||
)
|
||||
# validate that host has a scheme
|
||||
if "://" not in self.registryhost:
|
||||
self.registryhost = "https://" + self.registryhost
|
||||
# Analyze Image Streams
|
||||
analyze_ref = OpenShiftAnalyzeImageStream(
|
||||
ignore_invalid_refs=self.params.get('ignore_invalid_refs'),
|
||||
ignore_invalid_refs=self.params.get("ignore_invalid_refs"),
|
||||
max_creation_timestamp=self.max_creation_timestamp,
|
||||
module=self.module
|
||||
module=self.module,
|
||||
)
|
||||
self.used_tags, self.used_images, error = analyze_ref.analyze_image_stream(
|
||||
resources
|
||||
)
|
||||
self.used_tags, self.used_images, error = analyze_ref.analyze_image_stream(resources)
|
||||
if error:
|
||||
self.fail_json(msg=error)
|
||||
|
||||
@@ -435,16 +449,20 @@ class OpenShiftAdmPruneImages(AnsibleOpenshiftModule):
|
||||
updated_image_streams = []
|
||||
deleted_tags_images = []
|
||||
updated_is_mapping = {}
|
||||
for stream in resources['ImageStream']:
|
||||
for stream in resources["ImageStream"]:
|
||||
result, images_to_delete = self.prune_image_streams(stream)
|
||||
if result:
|
||||
updated_is_mapping[result["metadata"]["namespace"] + "/" + result["metadata"]["name"]] = result
|
||||
updated_is_mapping[
|
||||
result["metadata"]["namespace"] + "/" + result["metadata"]["name"]
|
||||
] = result
|
||||
updated_image_streams.append(result)
|
||||
deleted_tags_images += images_to_delete
|
||||
|
||||
# Create a list with images referenced on image stream
|
||||
self.referenced_images = []
|
||||
for item in self.kubernetes_facts(kind="ImageStream", api_version="image.openshift.io/v1")["resources"]:
|
||||
for item in self.kubernetes_facts(
|
||||
kind="ImageStream", api_version="image.openshift.io/v1"
|
||||
)["resources"]:
|
||||
name = "%s/%s" % (item["metadata"]["namespace"], item["metadata"]["name"])
|
||||
if name in updated_is_mapping:
|
||||
item = updated_is_mapping[name]
|
||||
@@ -453,7 +471,7 @@ class OpenShiftAdmPruneImages(AnsibleOpenshiftModule):
|
||||
|
||||
# Stage 2: delete images
|
||||
images = []
|
||||
images_to_delete = [x["metadata"]["name"] for x in resources['Image']]
|
||||
images_to_delete = [x["metadata"]["name"] for x in resources["Image"]]
|
||||
if self.params.get("namespace") is not None:
|
||||
# When namespace is defined, prune only images that were referenced by ImageStream
|
||||
# from the corresponding namespace
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
from datetime import datetime, timezone, timedelta
|
||||
import traceback
|
||||
import time
|
||||
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
from ansible_collections.community.okd.plugins.module_utils.openshift_common import AnsibleOpenshiftModule
|
||||
from ansible_collections.community.okd.plugins.module_utils.openshift_common import (
|
||||
AnsibleOpenshiftModule,
|
||||
)
|
||||
|
||||
try:
|
||||
from kubernetes.dynamic.exceptions import DynamicApiError
|
||||
@@ -36,8 +38,7 @@ class OpenShiftBuilds(AnsibleOpenshiftModule):
|
||||
result = self.request(
|
||||
method="POST",
|
||||
path="/apis/build.openshift.io/v1/namespaces/{namespace}/builds/{name}/clone".format(
|
||||
namespace=namespace,
|
||||
name=name
|
||||
namespace=namespace, name=name
|
||||
),
|
||||
body=request,
|
||||
content_type="application/json",
|
||||
@@ -47,7 +48,11 @@ class OpenShiftBuilds(AnsibleOpenshiftModule):
|
||||
msg = "Failed to clone Build %s/%s due to: %s" % (namespace, name, exc.body)
|
||||
self.fail_json(msg=msg, status=exc.status, reason=exc.reason)
|
||||
except Exception as e:
|
||||
msg = "Failed to clone Build %s/%s due to: %s" % (namespace, name, to_native(e))
|
||||
msg = "Failed to clone Build %s/%s due to: %s" % (
|
||||
namespace,
|
||||
name,
|
||||
to_native(e),
|
||||
)
|
||||
self.fail_json(msg=msg, error=to_native(e), exception=e)
|
||||
|
||||
def instantiate_build_config(self, name, namespace, request):
|
||||
@@ -55,22 +60,28 @@ class OpenShiftBuilds(AnsibleOpenshiftModule):
|
||||
result = self.request(
|
||||
method="POST",
|
||||
path="/apis/build.openshift.io/v1/namespaces/{namespace}/buildconfigs/{name}/instantiate".format(
|
||||
namespace=namespace,
|
||||
name=name
|
||||
namespace=namespace, name=name
|
||||
),
|
||||
body=request,
|
||||
content_type="application/json",
|
||||
)
|
||||
return result.to_dict()
|
||||
except DynamicApiError as exc:
|
||||
msg = "Failed to instantiate BuildConfig %s/%s due to: %s" % (namespace, name, exc.body)
|
||||
msg = "Failed to instantiate BuildConfig %s/%s due to: %s" % (
|
||||
namespace,
|
||||
name,
|
||||
exc.body,
|
||||
)
|
||||
self.fail_json(msg=msg, status=exc.status, reason=exc.reason)
|
||||
except Exception as e:
|
||||
msg = "Failed to instantiate BuildConfig %s/%s due to: %s" % (namespace, name, to_native(e))
|
||||
msg = "Failed to instantiate BuildConfig %s/%s due to: %s" % (
|
||||
namespace,
|
||||
name,
|
||||
to_native(e),
|
||||
)
|
||||
self.fail_json(msg=msg, error=to_native(e), exception=e)
|
||||
|
||||
def start_build(self):
|
||||
|
||||
result = None
|
||||
name = self.params.get("build_config_name")
|
||||
if not name:
|
||||
@@ -79,32 +90,20 @@ class OpenShiftBuilds(AnsibleOpenshiftModule):
|
||||
build_request = {
|
||||
"kind": "BuildRequest",
|
||||
"apiVersion": "build.openshift.io/v1",
|
||||
"metadata": {
|
||||
"name": name
|
||||
},
|
||||
"triggeredBy": [
|
||||
{"message": "Manually triggered"}
|
||||
],
|
||||
"metadata": {"name": name},
|
||||
"triggeredBy": [{"message": "Manually triggered"}],
|
||||
}
|
||||
|
||||
# Overrides incremental
|
||||
incremental = self.params.get("incremental")
|
||||
if incremental is not None:
|
||||
build_request.update(
|
||||
{
|
||||
"sourceStrategyOptions": {
|
||||
"incremental": incremental
|
||||
}
|
||||
}
|
||||
{"sourceStrategyOptions": {"incremental": incremental}}
|
||||
)
|
||||
|
||||
# Environment variable
|
||||
if self.params.get("env_vars"):
|
||||
build_request.update(
|
||||
{
|
||||
"env": self.params.get("env_vars")
|
||||
}
|
||||
)
|
||||
build_request.update({"env": self.params.get("env_vars")})
|
||||
|
||||
# Docker strategy option
|
||||
if self.params.get("build_args"):
|
||||
@@ -121,22 +120,14 @@ class OpenShiftBuilds(AnsibleOpenshiftModule):
|
||||
if no_cache is not None:
|
||||
build_request.update(
|
||||
{
|
||||
"dockerStrategyOptions": {
|
||||
"noCache": no_cache
|
||||
},
|
||||
"dockerStrategyOptions": {"noCache": no_cache},
|
||||
}
|
||||
)
|
||||
|
||||
# commit
|
||||
if self.params.get("commit"):
|
||||
build_request.update(
|
||||
{
|
||||
"revision": {
|
||||
"git": {
|
||||
"commit": self.params.get("commit")
|
||||
}
|
||||
}
|
||||
}
|
||||
{"revision": {"git": {"commit": self.params.get("commit")}}}
|
||||
)
|
||||
|
||||
if self.params.get("build_config_name"):
|
||||
@@ -144,7 +135,7 @@ class OpenShiftBuilds(AnsibleOpenshiftModule):
|
||||
result = self.instantiate_build_config(
|
||||
name=self.params.get("build_config_name"),
|
||||
namespace=self.params.get("namespace"),
|
||||
request=build_request
|
||||
request=build_request,
|
||||
)
|
||||
|
||||
else:
|
||||
@@ -152,7 +143,7 @@ class OpenShiftBuilds(AnsibleOpenshiftModule):
|
||||
result = self.clone_build(
|
||||
name=self.params.get("build_name"),
|
||||
namespace=self.params.get("namespace"),
|
||||
request=build_request
|
||||
request=build_request,
|
||||
)
|
||||
|
||||
if result and self.params.get("wait"):
|
||||
@@ -179,10 +170,11 @@ class OpenShiftBuilds(AnsibleOpenshiftModule):
|
||||
break
|
||||
elif last_status_phase in ("Cancelled", "Error", "Failed"):
|
||||
self.fail_json(
|
||||
msg="Unexpected status for Build %s/%s: %s" % (
|
||||
msg="Unexpected status for Build %s/%s: %s"
|
||||
% (
|
||||
result["metadata"]["name"],
|
||||
result["metadata"]["namespace"],
|
||||
last_status_phase
|
||||
last_status_phase,
|
||||
)
|
||||
)
|
||||
time.sleep(wait_sleep)
|
||||
@@ -190,8 +182,11 @@ class OpenShiftBuilds(AnsibleOpenshiftModule):
|
||||
if last_status_phase != "Complete":
|
||||
name = result["metadata"]["name"]
|
||||
namespace = result["metadata"]["namespace"]
|
||||
msg = "Build %s/%s has not complete after %d second(s)," \
|
||||
"current status is %s" % (namespace, name, wait_timeout, last_status_phase)
|
||||
msg = (
|
||||
"Build %s/%s has not complete after %d second(s),"
|
||||
"current status is %s"
|
||||
% (namespace, name, wait_timeout, last_status_phase)
|
||||
)
|
||||
|
||||
self.fail_json(msg=msg)
|
||||
|
||||
@@ -199,9 +194,8 @@ class OpenShiftBuilds(AnsibleOpenshiftModule):
|
||||
self.exit_json(changed=True, builds=result)
|
||||
|
||||
def cancel_build(self, restart):
|
||||
|
||||
kind = 'Build'
|
||||
api_version = 'build.openshift.io/v1'
|
||||
kind = "Build"
|
||||
api_version = "build.openshift.io/v1"
|
||||
|
||||
namespace = self.params.get("namespace")
|
||||
phases = ["new", "pending", "running"]
|
||||
@@ -215,16 +209,18 @@ class OpenShiftBuilds(AnsibleOpenshiftModule):
|
||||
else:
|
||||
build_config = self.params.get("build_config_name")
|
||||
# list all builds from namespace
|
||||
params = dict(
|
||||
kind=kind,
|
||||
api_version=api_version,
|
||||
namespace=namespace
|
||||
)
|
||||
params = dict(kind=kind, api_version=api_version, namespace=namespace)
|
||||
resources = self.kubernetes_facts(**params).get("resources", [])
|
||||
|
||||
def _filter_builds(build):
|
||||
config = build["metadata"].get("labels", {}).get("openshift.io/build-config.name")
|
||||
return build_config is None or (build_config is not None and config in build_config)
|
||||
config = (
|
||||
build["metadata"]
|
||||
.get("labels", {})
|
||||
.get("openshift.io/build-config.name")
|
||||
)
|
||||
return build_config is None or (
|
||||
build_config is not None and config in build_config
|
||||
)
|
||||
|
||||
for item in list(filter(_filter_builds, resources)):
|
||||
name = item["metadata"]["name"]
|
||||
@@ -232,16 +228,15 @@ class OpenShiftBuilds(AnsibleOpenshiftModule):
|
||||
names.append(name)
|
||||
|
||||
if len(names) == 0:
|
||||
self.exit_json(changed=False, msg="No Build found from namespace %s" % namespace)
|
||||
self.exit_json(
|
||||
changed=False, msg="No Build found from namespace %s" % namespace
|
||||
)
|
||||
|
||||
warning = []
|
||||
builds_to_cancel = []
|
||||
for name in names:
|
||||
params = dict(
|
||||
kind=kind,
|
||||
api_version=api_version,
|
||||
name=name,
|
||||
namespace=namespace
|
||||
kind=kind, api_version=api_version, name=name, namespace=namespace
|
||||
)
|
||||
|
||||
resource = self.kubernetes_facts(**params).get("resources", [])
|
||||
@@ -256,7 +251,10 @@ class OpenShiftBuilds(AnsibleOpenshiftModule):
|
||||
if phase in phases:
|
||||
builds_to_cancel.append(resource)
|
||||
else:
|
||||
warning.append("build %s/%s is not in expected phase, found %s" % (namespace, name, phase))
|
||||
warning.append(
|
||||
"build %s/%s is not in expected phase, found %s"
|
||||
% (namespace, name, phase)
|
||||
)
|
||||
|
||||
changed = False
|
||||
result = []
|
||||
@@ -278,9 +276,10 @@ class OpenShiftBuilds(AnsibleOpenshiftModule):
|
||||
result.append(cancelled_build)
|
||||
except DynamicApiError as exc:
|
||||
self.fail_json(
|
||||
msg="Failed to cancel Build %s/%s due to: %s" % (namespace, name, exc),
|
||||
msg="Failed to cancel Build %s/%s due to: %s"
|
||||
% (namespace, name, exc),
|
||||
reason=exc.reason,
|
||||
status=exc.status
|
||||
status=exc.status,
|
||||
)
|
||||
except Exception as e:
|
||||
self.fail_json(
|
||||
@@ -294,10 +293,7 @@ class OpenShiftBuilds(AnsibleOpenshiftModule):
|
||||
name = build["metadata"]["name"]
|
||||
while (datetime.now() - start).seconds < wait_timeout:
|
||||
params = dict(
|
||||
kind=kind,
|
||||
api_version=api_version,
|
||||
name=name,
|
||||
namespace=namespace
|
||||
kind=kind, api_version=api_version, name=name, namespace=namespace
|
||||
)
|
||||
resource = self.kubernetes_facts(**params).get("resources", [])
|
||||
if len(resource) == 0:
|
||||
@@ -307,7 +303,11 @@ class OpenShiftBuilds(AnsibleOpenshiftModule):
|
||||
if last_phase == "Cancelled":
|
||||
return resource, None
|
||||
time.sleep(wait_sleep)
|
||||
return None, "Build %s/%s is not cancelled as expected, current state is %s" % (namespace, name, last_phase)
|
||||
return (
|
||||
None,
|
||||
"Build %s/%s is not cancelled as expected, current state is %s"
|
||||
% (namespace, name, last_phase),
|
||||
)
|
||||
|
||||
if result and self.params.get("wait"):
|
||||
wait_timeout = self.params.get("wait_timeout")
|
||||
@@ -341,8 +341,8 @@ class OpenShiftPruneBuilds(OpenShiftBuilds):
|
||||
|
||||
def execute_module(self):
|
||||
# list replicationcontroller candidate for pruning
|
||||
kind = 'Build'
|
||||
api_version = 'build.openshift.io/v1'
|
||||
kind = "Build"
|
||||
api_version = "build.openshift.io/v1"
|
||||
resource = self.find_resource(kind=kind, api_version=api_version, fail=True)
|
||||
|
||||
self.max_creation_timestamp = None
|
||||
@@ -352,7 +352,12 @@ class OpenShiftPruneBuilds(OpenShiftBuilds):
|
||||
self.max_creation_timestamp = now - timedelta(minutes=keep_younger_than)
|
||||
|
||||
def _prunable_build(build):
|
||||
return build["status"]["phase"] in ("Complete", "Failed", "Error", "Cancelled")
|
||||
return build["status"]["phase"] in (
|
||||
"Complete",
|
||||
"Failed",
|
||||
"Error",
|
||||
"Cancelled",
|
||||
)
|
||||
|
||||
def _orphan_build(build):
|
||||
if not _prunable_build(build):
|
||||
@@ -367,7 +372,9 @@ class OpenShiftPruneBuilds(OpenShiftBuilds):
|
||||
def _younger_build(build):
|
||||
if not self.max_creation_timestamp:
|
||||
return False
|
||||
creation_timestamp = datetime.strptime(build['metadata']['creationTimestamp'], '%Y-%m-%dT%H:%M:%SZ')
|
||||
creation_timestamp = datetime.strptime(
|
||||
build["metadata"]["creationTimestamp"], "%Y-%m-%dT%H:%M:%SZ"
|
||||
)
|
||||
return creation_timestamp < self.max_creation_timestamp
|
||||
|
||||
predicates = [
|
||||
@@ -401,9 +408,17 @@ class OpenShiftPruneBuilds(OpenShiftBuilds):
|
||||
namespace = build["metadata"]["namespace"]
|
||||
resource.delete(name=name, namespace=namespace, body={})
|
||||
except DynamicApiError as exc:
|
||||
msg = "Failed to delete Build %s/%s due to: %s" % (namespace, name, exc.body)
|
||||
msg = "Failed to delete Build %s/%s due to: %s" % (
|
||||
namespace,
|
||||
name,
|
||||
exc.body,
|
||||
)
|
||||
self.fail_json(msg=msg, status=exc.status, reason=exc.reason)
|
||||
except Exception as e:
|
||||
msg = "Failed to delete Build %s/%s due to: %s" % (namespace, name, to_native(e))
|
||||
msg = "Failed to delete Build %s/%s due to: %s" % (
|
||||
namespace,
|
||||
name,
|
||||
to_native(e),
|
||||
)
|
||||
self.fail_json(msg=msg, error=to_native(e), exception=e)
|
||||
self.exit_json(changed=changed, builds=candidates)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
import traceback
|
||||
@@ -9,8 +10,12 @@ from abc import abstractmethod
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
try:
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import get_api_client
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import AnsibleK8SModule
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
|
||||
get_api_client,
|
||||
)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
|
||||
AnsibleK8SModule,
|
||||
)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
|
||||
K8sService,
|
||||
diff_objects,
|
||||
@@ -24,7 +29,10 @@ try:
|
||||
merge_params,
|
||||
flatten_list_kind,
|
||||
)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import CoreException
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
|
||||
CoreException,
|
||||
)
|
||||
|
||||
HAS_KUBERNETES_COLLECTION = True
|
||||
k8s_collection_import_exception = None
|
||||
K8S_COLLECTION_ERROR = None
|
||||
@@ -35,7 +43,6 @@ except ImportError as e:
|
||||
|
||||
|
||||
class AnsibleOpenshiftModule(AnsibleK8SModule):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(AnsibleOpenshiftModule, self).__init__(**kwargs)
|
||||
|
||||
@@ -86,7 +93,6 @@ class AnsibleOpenshiftModule(AnsibleK8SModule):
|
||||
return diff_objects(existing, new)
|
||||
|
||||
def run_module(self):
|
||||
|
||||
try:
|
||||
self.execute_module()
|
||||
except CoreException as e:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
import re
|
||||
@@ -23,62 +24,68 @@ def convert_storage_to_bytes(value):
|
||||
|
||||
|
||||
def is_valid_digest(digest):
|
||||
|
||||
digest_algorithm_size = dict(
|
||||
sha256=64, sha384=96, sha512=128,
|
||||
sha256=64,
|
||||
sha384=96,
|
||||
sha512=128,
|
||||
)
|
||||
|
||||
m = re.match(r'[a-zA-Z0-9-_+.]+:[a-fA-F0-9]+', digest)
|
||||
m = re.match(r"[a-zA-Z0-9-_+.]+:[a-fA-F0-9]+", digest)
|
||||
if not m:
|
||||
return "Docker digest does not match expected format %s" % digest
|
||||
|
||||
idx = digest.find(':')
|
||||
idx = digest.find(":")
|
||||
# case: "sha256:" with no hex.
|
||||
if idx < 0 or idx == (len(digest) - 1):
|
||||
return "Invalid docker digest %s, no hex value define" % digest
|
||||
|
||||
algorithm = digest[:idx]
|
||||
if algorithm not in digest_algorithm_size:
|
||||
return "Unsupported digest algorithm value %s for digest %s" % (algorithm, digest)
|
||||
return "Unsupported digest algorithm value %s for digest %s" % (
|
||||
algorithm,
|
||||
digest,
|
||||
)
|
||||
|
||||
hex_value = digest[idx + 1:]
|
||||
hex_value = digest[idx + 1:] # fmt: skip
|
||||
if len(hex_value) != digest_algorithm_size.get(algorithm):
|
||||
return "Invalid length for digest hex expected %d found %d (digest is %s)" % (
|
||||
digest_algorithm_size.get(algorithm), len(hex_value), digest
|
||||
digest_algorithm_size.get(algorithm),
|
||||
len(hex_value),
|
||||
digest,
|
||||
)
|
||||
|
||||
|
||||
def parse_docker_image_ref(image_ref, module=None):
|
||||
"""
|
||||
Docker Grammar Reference
|
||||
Reference => name [ ":" tag ] [ "@" digest ]
|
||||
name => [hostname '/'] component ['/' component]*
|
||||
hostname => hostcomponent ['.' hostcomponent]* [':' port-number]
|
||||
hostcomponent => /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
|
||||
port-number => /[0-9]+/
|
||||
component => alpha-numeric [separator alpha-numeric]*
|
||||
alpha-numeric => /[a-z0-9]+/
|
||||
separator => /[_.]|__|[-]*/
|
||||
Docker Grammar Reference
|
||||
Reference => name [ ":" tag ] [ "@" digest ]
|
||||
name => [hostname '/'] component ['/' component]*
|
||||
hostname => hostcomponent ['.' hostcomponent]* [':' port-number]
|
||||
hostcomponent => /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
|
||||
port-number => /[0-9]+/
|
||||
component => alpha-numeric [separator alpha-numeric]*
|
||||
alpha-numeric => /[a-z0-9]+/
|
||||
separator => /[_.]|__|[-]*/
|
||||
"""
|
||||
idx = image_ref.find("/")
|
||||
|
||||
def _contains_any(src, values):
|
||||
return any(x in src for x in values)
|
||||
|
||||
result = {
|
||||
"tag": None, "digest": None
|
||||
}
|
||||
result = {"tag": None, "digest": None}
|
||||
default_domain = "docker.io"
|
||||
if idx < 0 or (not _contains_any(image_ref[:idx], ":.") and image_ref[:idx] != "localhost"):
|
||||
if idx < 0 or (
|
||||
not _contains_any(image_ref[:idx], ":.") and image_ref[:idx] != "localhost"
|
||||
):
|
||||
result["hostname"], remainder = default_domain, image_ref
|
||||
else:
|
||||
result["hostname"], remainder = image_ref[:idx], image_ref[idx + 1:]
|
||||
result["hostname"], remainder = image_ref[:idx], image_ref[idx + 1:] # fmt: skip
|
||||
|
||||
# Parse remainder information
|
||||
idx = remainder.find("@")
|
||||
if idx > 0 and len(remainder) > (idx + 1):
|
||||
# docker image reference with digest
|
||||
component, result["digest"] = remainder[:idx], remainder[idx + 1:]
|
||||
component, result["digest"] = remainder[:idx], remainder[idx + 1:] # fmt: skip
|
||||
err = is_valid_digest(result["digest"])
|
||||
if err:
|
||||
if module:
|
||||
@@ -88,7 +95,7 @@ def parse_docker_image_ref(image_ref, module=None):
|
||||
idx = remainder.find(":")
|
||||
if idx > 0 and len(remainder) > (idx + 1):
|
||||
# docker image reference with tag
|
||||
component, result["tag"] = remainder[:idx], remainder[idx + 1:]
|
||||
component, result["tag"] = remainder[:idx], remainder[idx + 1:] # fmt: skip
|
||||
else:
|
||||
# name only
|
||||
component = remainder
|
||||
@@ -96,8 +103,6 @@ def parse_docker_image_ref(image_ref, module=None):
|
||||
namespace = None
|
||||
if len(v) > 1:
|
||||
namespace = v[0]
|
||||
result.update({
|
||||
"namespace": namespace, "name": v[-1]
|
||||
})
|
||||
result.update({"namespace": namespace, "name": v[-1]})
|
||||
|
||||
return result, None
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
# 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)
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
|
||||
from ansible.module_utils.parsing.convert_bool import boolean
|
||||
@@ -19,18 +19,21 @@ from ansible_collections.community.okd.plugins.module_utils.openshift_ldap impor
|
||||
ldap_split_host_port,
|
||||
OpenshiftLDAPRFC2307,
|
||||
OpenshiftLDAPActiveDirectory,
|
||||
OpenshiftLDAPAugmentedActiveDirectory
|
||||
OpenshiftLDAPAugmentedActiveDirectory,
|
||||
)
|
||||
|
||||
try:
|
||||
import ldap
|
||||
|
||||
HAS_PYTHON_LDAP = True
|
||||
PYTHON_LDAP_ERROR = None
|
||||
except ImportError as e:
|
||||
HAS_PYTHON_LDAP = False
|
||||
PYTHON_LDAP_ERROR = e
|
||||
|
||||
from ansible_collections.community.okd.plugins.module_utils.openshift_common import AnsibleOpenshiftModule
|
||||
from ansible_collections.community.okd.plugins.module_utils.openshift_common import (
|
||||
AnsibleOpenshiftModule,
|
||||
)
|
||||
|
||||
try:
|
||||
from kubernetes.dynamic.exceptions import DynamicApiError
|
||||
@@ -44,7 +47,9 @@ LDAP_OPENSHIFT_UID_ANNOTATION = "openshift.io/ldap.uid"
|
||||
LDAP_OPENSHIFT_SYNCTIME_ANNOTATION = "openshift.io/ldap.sync-time"
|
||||
|
||||
|
||||
def connect_to_ldap(module, server_uri, bind_dn=None, bind_pw=None, insecure=True, ca_file=None):
|
||||
def connect_to_ldap(
|
||||
module, server_uri, bind_dn=None, bind_pw=None, insecure=True, ca_file=None
|
||||
):
|
||||
if insecure:
|
||||
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
|
||||
elif ca_file:
|
||||
@@ -56,27 +61,36 @@ def connect_to_ldap(module, server_uri, bind_dn=None, bind_pw=None, insecure=Tru
|
||||
connection.simple_bind_s(bind_dn, bind_pw)
|
||||
return connection
|
||||
except ldap.LDAPError as e:
|
||||
module.fail_json(msg="Cannot bind to the LDAP server '{0}' due to: {1}".format(server_uri, e))
|
||||
module.fail_json(
|
||||
msg="Cannot bind to the LDAP server '{0}' due to: {1}".format(server_uri, e)
|
||||
)
|
||||
|
||||
|
||||
def validate_group_annotation(definition, host_ip):
|
||||
name = definition['metadata']['name']
|
||||
name = definition["metadata"]["name"]
|
||||
# Validate LDAP URL Annotation
|
||||
annotate_url = definition['metadata'].get('annotations', {}).get(LDAP_OPENSHIFT_URL_ANNOTATION)
|
||||
annotate_url = (
|
||||
definition["metadata"].get("annotations", {}).get(LDAP_OPENSHIFT_URL_ANNOTATION)
|
||||
)
|
||||
if host_ip:
|
||||
if not annotate_url:
|
||||
return "group '{0}' marked as having been synced did not have an '{1}' annotation".format(name, LDAP_OPENSHIFT_URL_ANNOTATION)
|
||||
return "group '{0}' marked as having been synced did not have an '{1}' annotation".format(
|
||||
name, LDAP_OPENSHIFT_URL_ANNOTATION
|
||||
)
|
||||
elif annotate_url != host_ip:
|
||||
return "group '{0}' was not synchronized from: '{1}'".format(name, host_ip)
|
||||
# Validate LDAP UID Annotation
|
||||
annotate_uid = definition['metadata']['annotations'].get(LDAP_OPENSHIFT_UID_ANNOTATION)
|
||||
annotate_uid = definition["metadata"]["annotations"].get(
|
||||
LDAP_OPENSHIFT_UID_ANNOTATION
|
||||
)
|
||||
if not annotate_uid:
|
||||
return "group '{0}' marked as having been synced did not have an '{1}' annotation".format(name, LDAP_OPENSHIFT_UID_ANNOTATION)
|
||||
return "group '{0}' marked as having been synced did not have an '{1}' annotation".format(
|
||||
name, LDAP_OPENSHIFT_UID_ANNOTATION
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
class OpenshiftLDAPGroups(object):
|
||||
|
||||
kind = "Group"
|
||||
version = "user.openshift.io/v1"
|
||||
|
||||
@@ -88,11 +102,7 @@ class OpenshiftLDAPGroups(object):
|
||||
@property
|
||||
def k8s_group_api(self):
|
||||
if not self.__group_api:
|
||||
params = dict(
|
||||
kind=self.kind,
|
||||
api_version=self.version,
|
||||
fail=True
|
||||
)
|
||||
params = dict(kind=self.kind, api_version=self.version, fail=True)
|
||||
self.__group_api = self.module.find_resource(**params)
|
||||
return self.__group_api
|
||||
|
||||
@@ -139,16 +149,26 @@ class OpenshiftLDAPGroups(object):
|
||||
|
||||
if missing:
|
||||
self.module.fail_json(
|
||||
msg="The following groups were not found: %s" % ''.join(missing)
|
||||
msg="The following groups were not found: %s" % "".join(missing)
|
||||
)
|
||||
else:
|
||||
label_selector = "%s=%s" % (LDAP_OPENSHIFT_HOST_LABEL, host)
|
||||
resources = self.get_group_info(label_selectors=[label_selector], return_list=True)
|
||||
resources = self.get_group_info(
|
||||
label_selectors=[label_selector], return_list=True
|
||||
)
|
||||
if not resources:
|
||||
return None, "Unable to find Group matching label selector '%s'" % label_selector
|
||||
return (
|
||||
None,
|
||||
"Unable to find Group matching label selector '%s'"
|
||||
% label_selector,
|
||||
)
|
||||
groups = resources
|
||||
if deny_groups:
|
||||
groups = [item for item in groups if item["metadata"]["name"] not in deny_groups]
|
||||
groups = [
|
||||
item
|
||||
for item in groups
|
||||
if item["metadata"]["name"] not in deny_groups
|
||||
]
|
||||
|
||||
uids = []
|
||||
for grp in groups:
|
||||
@@ -156,7 +176,9 @@ class OpenshiftLDAPGroups(object):
|
||||
if err and allow_groups:
|
||||
# We raise an error for group part of the allow_group not matching LDAP sync criteria
|
||||
return None, err
|
||||
group_uid = grp['metadata']['annotations'].get(LDAP_OPENSHIFT_UID_ANNOTATION)
|
||||
group_uid = grp["metadata"]["annotations"].get(
|
||||
LDAP_OPENSHIFT_UID_ANNOTATION
|
||||
)
|
||||
self.cache[group_uid] = grp
|
||||
uids.append(group_uid)
|
||||
return uids, None
|
||||
@@ -174,38 +196,65 @@ class OpenshiftLDAPGroups(object):
|
||||
"kind": "Group",
|
||||
"metadata": {
|
||||
"name": group_name,
|
||||
"labels": {
|
||||
LDAP_OPENSHIFT_HOST_LABEL: self.module.host
|
||||
},
|
||||
"labels": {LDAP_OPENSHIFT_HOST_LABEL: self.module.host},
|
||||
"annotations": {
|
||||
LDAP_OPENSHIFT_URL_ANNOTATION: self.module.netlocation,
|
||||
LDAP_OPENSHIFT_UID_ANNOTATION: group_uid,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
# Make sure we aren't taking over an OpenShift group that is already related to a different LDAP group
|
||||
ldaphost_label = group["metadata"].get("labels", {}).get(LDAP_OPENSHIFT_HOST_LABEL)
|
||||
ldaphost_label = (
|
||||
group["metadata"].get("labels", {}).get(LDAP_OPENSHIFT_HOST_LABEL)
|
||||
)
|
||||
if not ldaphost_label or ldaphost_label != self.module.host:
|
||||
return None, "Group %s: %s label did not match sync host: wanted %s, got %s" % (
|
||||
group_name, LDAP_OPENSHIFT_HOST_LABEL, self.module.host, ldaphost_label
|
||||
return (
|
||||
None,
|
||||
"Group %s: %s label did not match sync host: wanted %s, got %s"
|
||||
% (
|
||||
group_name,
|
||||
LDAP_OPENSHIFT_HOST_LABEL,
|
||||
self.module.host,
|
||||
ldaphost_label,
|
||||
),
|
||||
)
|
||||
|
||||
ldapurl_annotation = group["metadata"].get("annotations", {}).get(LDAP_OPENSHIFT_URL_ANNOTATION)
|
||||
ldapurl_annotation = (
|
||||
group["metadata"].get("annotations", {}).get(LDAP_OPENSHIFT_URL_ANNOTATION)
|
||||
)
|
||||
if not ldapurl_annotation or ldapurl_annotation != self.module.netlocation:
|
||||
return None, "Group %s: %s annotation did not match sync host: wanted %s, got %s" % (
|
||||
group_name, LDAP_OPENSHIFT_URL_ANNOTATION, self.module.netlocation, ldapurl_annotation
|
||||
return (
|
||||
None,
|
||||
"Group %s: %s annotation did not match sync host: wanted %s, got %s"
|
||||
% (
|
||||
group_name,
|
||||
LDAP_OPENSHIFT_URL_ANNOTATION,
|
||||
self.module.netlocation,
|
||||
ldapurl_annotation,
|
||||
),
|
||||
)
|
||||
|
||||
ldapuid_annotation = group["metadata"].get("annotations", {}).get(LDAP_OPENSHIFT_UID_ANNOTATION)
|
||||
ldapuid_annotation = (
|
||||
group["metadata"].get("annotations", {}).get(LDAP_OPENSHIFT_UID_ANNOTATION)
|
||||
)
|
||||
if not ldapuid_annotation or ldapuid_annotation != group_uid:
|
||||
return None, "Group %s: %s annotation did not match LDAP UID: wanted %s, got %s" % (
|
||||
group_name, LDAP_OPENSHIFT_UID_ANNOTATION, group_uid, ldapuid_annotation
|
||||
return (
|
||||
None,
|
||||
"Group %s: %s annotation did not match LDAP UID: wanted %s, got %s"
|
||||
% (
|
||||
group_name,
|
||||
LDAP_OPENSHIFT_UID_ANNOTATION,
|
||||
group_uid,
|
||||
ldapuid_annotation,
|
||||
),
|
||||
)
|
||||
|
||||
# Overwrite Group Users data
|
||||
group["users"] = usernames
|
||||
group["metadata"]["annotations"][LDAP_OPENSHIFT_SYNCTIME_ANNOTATION] = datetime.now().isoformat()
|
||||
group["metadata"]["annotations"][
|
||||
LDAP_OPENSHIFT_SYNCTIME_ANNOTATION
|
||||
] = datetime.now().isoformat()
|
||||
return group, None
|
||||
|
||||
def create_openshift_groups(self, groups: list):
|
||||
@@ -223,9 +272,15 @@ class OpenshiftLDAPGroups(object):
|
||||
else:
|
||||
definition = self.k8s_group_api.create(definition).to_dict()
|
||||
except DynamicApiError as exc:
|
||||
self.module.fail_json(msg="Failed to %s Group '%s' due to: %s" % (method, name, exc.body))
|
||||
self.module.fail_json(
|
||||
msg="Failed to %s Group '%s' due to: %s"
|
||||
% (method, name, exc.body)
|
||||
)
|
||||
except Exception as exc:
|
||||
self.module.fail_json(msg="Failed to %s Group '%s' due to: %s" % (method, name, to_native(exc)))
|
||||
self.module.fail_json(
|
||||
msg="Failed to %s Group '%s' due to: %s"
|
||||
% (method, name, to_native(exc))
|
||||
)
|
||||
equals = False
|
||||
if existing:
|
||||
equals, diff = self.module.diff_objects(existing, definition)
|
||||
@@ -235,27 +290,27 @@ class OpenshiftLDAPGroups(object):
|
||||
return results, diffs, changed
|
||||
|
||||
def delete_openshift_group(self, name: str):
|
||||
result = dict(
|
||||
kind=self.kind,
|
||||
apiVersion=self.version,
|
||||
metadata=dict(
|
||||
name=name
|
||||
)
|
||||
)
|
||||
result = dict(kind=self.kind, apiVersion=self.version, metadata=dict(name=name))
|
||||
if not self.module.check_mode:
|
||||
try:
|
||||
result = self.k8s_group_api.delete(name=name).to_dict()
|
||||
except DynamicApiError as exc:
|
||||
self.module.fail_json(msg="Failed to delete Group '{0}' due to: {1}".format(name, exc.body))
|
||||
self.module.fail_json(
|
||||
msg="Failed to delete Group '{0}' due to: {1}".format(
|
||||
name, exc.body
|
||||
)
|
||||
)
|
||||
except Exception as exc:
|
||||
self.module.fail_json(msg="Failed to delete Group '{0}' due to: {1}".format(name, to_native(exc)))
|
||||
self.module.fail_json(
|
||||
msg="Failed to delete Group '{0}' due to: {1}".format(
|
||||
name, to_native(exc)
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
class OpenshiftGroupsSync(AnsibleOpenshiftModule):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
||||
super(OpenshiftGroupsSync, self).__init__(**kwargs)
|
||||
self.__k8s_group_api = None
|
||||
self.__ldap_connection = None
|
||||
@@ -267,17 +322,14 @@ class OpenshiftGroupsSync(AnsibleOpenshiftModule):
|
||||
|
||||
if not HAS_PYTHON_LDAP:
|
||||
self.fail_json(
|
||||
msg=missing_required_lib('python-ldap'), error=to_native(PYTHON_LDAP_ERROR)
|
||||
msg=missing_required_lib("python-ldap"),
|
||||
error=to_native(PYTHON_LDAP_ERROR),
|
||||
)
|
||||
|
||||
@property
|
||||
def k8s_group_api(self):
|
||||
if not self.__k8s_group_api:
|
||||
params = dict(
|
||||
kind="Group",
|
||||
api_version="user.openshift.io/v1",
|
||||
fail=True
|
||||
)
|
||||
params = dict(kind="Group", api_version="user.openshift.io/v1", fail=True)
|
||||
self.__k8s_group_api = self.find_resource(**params)
|
||||
return self.__k8s_group_api
|
||||
|
||||
@@ -291,11 +343,11 @@ class OpenshiftGroupsSync(AnsibleOpenshiftModule):
|
||||
# Create connection object
|
||||
params = dict(
|
||||
module=self,
|
||||
server_uri=self.config.get('url'),
|
||||
bind_dn=self.config.get('bindDN'),
|
||||
bind_pw=self.config.get('bindPassword'),
|
||||
insecure=boolean(self.config.get('insecure')),
|
||||
ca_file=self.config.get('ca')
|
||||
server_uri=self.config.get("url"),
|
||||
bind_dn=self.config.get("bindDN"),
|
||||
bind_pw=self.config.get("bindPassword"),
|
||||
insecure=boolean(self.config.get("insecure")),
|
||||
ca_file=self.config.get("ca"),
|
||||
)
|
||||
self.__ldap_connection = connect_to_ldap(**params)
|
||||
return self.__ldap_connection
|
||||
@@ -327,7 +379,6 @@ class OpenshiftGroupsSync(AnsibleOpenshiftModule):
|
||||
return syncer
|
||||
|
||||
def synchronize(self):
|
||||
|
||||
sync_group_type = self.module.params.get("type")
|
||||
|
||||
groups_uids = []
|
||||
@@ -365,7 +416,8 @@ class OpenshiftGroupsSync(AnsibleOpenshiftModule):
|
||||
name, err = syncer.get_username_for_entry(entry)
|
||||
if err:
|
||||
self.exit_json(
|
||||
msg="Unable to determine username for entry %s: %s" % (entry, err)
|
||||
msg="Unable to determine username for entry %s: %s"
|
||||
% (entry, err)
|
||||
)
|
||||
if isinstance(name, list):
|
||||
usernames.extend(name)
|
||||
@@ -380,13 +432,17 @@ class OpenshiftGroupsSync(AnsibleOpenshiftModule):
|
||||
self.exit_json(msg=err)
|
||||
|
||||
# Make Openshift group
|
||||
group, err = ldap_openshift_group.make_openshift_group(uid, group_name, usernames)
|
||||
group, err = ldap_openshift_group.make_openshift_group(
|
||||
uid, group_name, usernames
|
||||
)
|
||||
if err:
|
||||
self.fail_json(msg=err)
|
||||
openshift_groups.append(group)
|
||||
|
||||
# Create Openshift Groups
|
||||
results, diffs, changed = ldap_openshift_group.create_openshift_groups(openshift_groups)
|
||||
results, diffs, changed = ldap_openshift_group.create_openshift_groups(
|
||||
openshift_groups
|
||||
)
|
||||
self.module.exit_json(changed=True, groups=results)
|
||||
|
||||
def prune(self):
|
||||
@@ -404,7 +460,10 @@ class OpenshiftGroupsSync(AnsibleOpenshiftModule):
|
||||
# Check if LDAP group exist
|
||||
exists, err = syncer.is_ldapgroup_exists(uid)
|
||||
if err:
|
||||
msg = "Error determining LDAP group existence for group %s: %s" % (uid, err)
|
||||
msg = "Error determining LDAP group existence for group %s: %s" % (
|
||||
uid,
|
||||
err,
|
||||
)
|
||||
self.module.fail_json(msg=msg)
|
||||
|
||||
if exists:
|
||||
@@ -429,14 +488,22 @@ class OpenshiftGroupsSync(AnsibleOpenshiftModule):
|
||||
self.fail_json(msg="Invalid LDAP Sync config: %s" % error)
|
||||
|
||||
# Split host/port
|
||||
if self.config.get('url'):
|
||||
result, error = ldap_split_host_port(self.config.get('url'))
|
||||
if self.config.get("url"):
|
||||
result, error = ldap_split_host_port(self.config.get("url"))
|
||||
if error:
|
||||
self.fail_json(msg="Failed to parse url='{0}': {1}".format(self.config.get('url'), error))
|
||||
self.netlocation, self.host, self.port = result["netlocation"], result["host"], result["port"]
|
||||
self.fail_json(
|
||||
msg="Failed to parse url='{0}': {1}".format(
|
||||
self.config.get("url"), error
|
||||
)
|
||||
)
|
||||
self.netlocation, self.host, self.port = (
|
||||
result["netlocation"],
|
||||
result["host"],
|
||||
result["port"],
|
||||
)
|
||||
self.scheme = result["scheme"]
|
||||
|
||||
if self.params.get('state') == 'present':
|
||||
if self.params.get("state") == "present":
|
||||
self.synchronize()
|
||||
else:
|
||||
self.prune()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
from datetime import datetime
|
||||
@@ -17,9 +18,9 @@ def get_image_blobs(image):
|
||||
return blobs, "failed to read metadata for image %s" % image["metadata"]["name"]
|
||||
media_type_manifest = (
|
||||
"application/vnd.docker.distribution.manifest.v2+json",
|
||||
"application/vnd.oci.image.manifest.v1+json"
|
||||
"application/vnd.oci.image.manifest.v1+json",
|
||||
)
|
||||
media_type_has_config = image['dockerImageManifestMediaType'] in media_type_manifest
|
||||
media_type_has_config = image["dockerImageManifestMediaType"] in media_type_manifest
|
||||
docker_image_id = docker_image_metadata.get("Id")
|
||||
if media_type_has_config and docker_image_id and len(docker_image_id) > 0:
|
||||
blobs.append(docker_image_id)
|
||||
@@ -29,19 +30,18 @@ def get_image_blobs(image):
|
||||
def is_created_after(creation_timestamp, max_creation_timestamp):
|
||||
if not max_creation_timestamp:
|
||||
return False
|
||||
creationTimestamp = datetime.strptime(creation_timestamp, '%Y-%m-%dT%H:%M:%SZ')
|
||||
creationTimestamp = datetime.strptime(creation_timestamp, "%Y-%m-%dT%H:%M:%SZ")
|
||||
return creationTimestamp > max_creation_timestamp
|
||||
|
||||
|
||||
def is_too_young_object(obj, max_creation_timestamp):
|
||||
return is_created_after(obj['metadata']['creationTimestamp'],
|
||||
max_creation_timestamp)
|
||||
return is_created_after(
|
||||
obj["metadata"]["creationTimestamp"], max_creation_timestamp
|
||||
)
|
||||
|
||||
|
||||
class OpenShiftAnalyzeImageStream(object):
|
||||
|
||||
def __init__(self, ignore_invalid_refs, max_creation_timestamp, module):
|
||||
|
||||
self.max_creationTimestamp = max_creation_timestamp
|
||||
self.used_tags = {}
|
||||
self.used_images = {}
|
||||
@@ -53,32 +53,34 @@ class OpenShiftAnalyzeImageStream(object):
|
||||
if error:
|
||||
return error
|
||||
|
||||
if not result['hostname'] or not result['namespace']:
|
||||
if not result["hostname"] or not result["namespace"]:
|
||||
# image reference does not match hostname/namespace/name pattern - skipping
|
||||
return None
|
||||
|
||||
if not result['digest']:
|
||||
if not result["digest"]:
|
||||
# Attempt to dereference istag. Since we cannot be sure whether the reference refers to the
|
||||
# integrated registry or not, we ignore the host part completely. As a consequence, we may keep
|
||||
# image otherwise sentenced for a removal just because its pull spec accidentally matches one of
|
||||
# our imagestreamtags.
|
||||
|
||||
# set the tag if empty
|
||||
if result['tag'] == "":
|
||||
result['tag'] = 'latest'
|
||||
key = "%s/%s:%s" % (result['namespace'], result['name'], result['tag'])
|
||||
if result["tag"] == "":
|
||||
result["tag"] = "latest"
|
||||
key = "%s/%s:%s" % (result["namespace"], result["name"], result["tag"])
|
||||
if key not in self.used_tags:
|
||||
self.used_tags[key] = []
|
||||
self.used_tags[key].append(referrer)
|
||||
else:
|
||||
key = "%s/%s@%s" % (result['namespace'], result['name'], result['digest'])
|
||||
key = "%s/%s@%s" % (result["namespace"], result["name"], result["digest"])
|
||||
if key not in self.used_images:
|
||||
self.used_images[key] = []
|
||||
self.used_images[key].append(referrer)
|
||||
|
||||
def analyze_refs_from_pod_spec(self, podSpec, referrer):
|
||||
for container in podSpec.get('initContainers', []) + podSpec.get('containers', []):
|
||||
image = container.get('image')
|
||||
for container in podSpec.get("initContainers", []) + podSpec.get(
|
||||
"containers", []
|
||||
):
|
||||
image = container.get("image")
|
||||
if len(image.strip()) == 0:
|
||||
# Ignoring container because it has no reference to image
|
||||
continue
|
||||
@@ -93,29 +95,35 @@ class OpenShiftAnalyzeImageStream(object):
|
||||
# pending or running. Additionally, it has to be at least as old as the minimum
|
||||
# age threshold defined by the algorithm.
|
||||
too_young = is_too_young_object(pod, self.max_creationTimestamp)
|
||||
if pod['status']['phase'] not in ("Running", "Pending") and too_young:
|
||||
if pod["status"]["phase"] not in ("Running", "Pending") and too_young:
|
||||
continue
|
||||
referrer = {
|
||||
"kind": pod["kind"],
|
||||
"namespace": pod["metadata"]["namespace"],
|
||||
"name": pod["metadata"]["name"],
|
||||
}
|
||||
err = self.analyze_refs_from_pod_spec(pod['spec'], referrer)
|
||||
err = self.analyze_refs_from_pod_spec(pod["spec"], referrer)
|
||||
if err:
|
||||
return err
|
||||
return None
|
||||
|
||||
def analyze_refs_pod_creators(self, resources):
|
||||
keys = (
|
||||
"ReplicationController", "DeploymentConfig", "DaemonSet",
|
||||
"Deployment", "ReplicaSet", "StatefulSet", "Job", "CronJob"
|
||||
"ReplicationController",
|
||||
"DeploymentConfig",
|
||||
"DaemonSet",
|
||||
"Deployment",
|
||||
"ReplicaSet",
|
||||
"StatefulSet",
|
||||
"Job",
|
||||
"CronJob",
|
||||
)
|
||||
|
||||
for k, objects in iteritems(resources):
|
||||
if k not in keys:
|
||||
continue
|
||||
for obj in objects:
|
||||
if k == 'CronJob':
|
||||
if k == "CronJob":
|
||||
spec = obj["spec"]["jobTemplate"]["spec"]["template"]["spec"]
|
||||
else:
|
||||
spec = obj["spec"]["template"]["spec"]
|
||||
@@ -132,64 +140,84 @@ class OpenShiftAnalyzeImageStream(object):
|
||||
def analyze_refs_from_strategy(self, build_strategy, namespace, referrer):
|
||||
# Determine 'from' reference
|
||||
def _determine_source_strategy():
|
||||
for src in ('sourceStrategy', 'dockerStrategy', 'customStrategy'):
|
||||
for src in ("sourceStrategy", "dockerStrategy", "customStrategy"):
|
||||
strategy = build_strategy.get(src)
|
||||
if strategy:
|
||||
return strategy.get('from')
|
||||
return strategy.get("from")
|
||||
return None
|
||||
|
||||
def _parse_image_stream_image_name(name):
|
||||
v = name.split('@')
|
||||
v = name.split("@")
|
||||
if len(v) != 2:
|
||||
return None, None, "expected exactly one @ in the isimage name %s" % name
|
||||
return (
|
||||
None,
|
||||
None,
|
||||
"expected exactly one @ in the isimage name %s" % name,
|
||||
)
|
||||
name = v[0]
|
||||
tag = v[1]
|
||||
if len(name) == 0 or len(tag) == 0:
|
||||
return None, None, "image stream image name %s must have a name and ID" % name
|
||||
return (
|
||||
None,
|
||||
None,
|
||||
"image stream image name %s must have a name and ID" % name,
|
||||
)
|
||||
return name, tag, None
|
||||
|
||||
def _parse_image_stream_tag_name(name):
|
||||
if "@" in name:
|
||||
return None, None, "%s is an image stream image, not an image stream tag" % name
|
||||
return (
|
||||
None,
|
||||
None,
|
||||
"%s is an image stream image, not an image stream tag" % name,
|
||||
)
|
||||
v = name.split(":")
|
||||
if len(v) != 2:
|
||||
return None, None, "expected exactly one : delimiter in the istag %s" % name
|
||||
return (
|
||||
None,
|
||||
None,
|
||||
"expected exactly one : delimiter in the istag %s" % name,
|
||||
)
|
||||
name = v[0]
|
||||
tag = v[1]
|
||||
if len(name) == 0 or len(tag) == 0:
|
||||
return None, None, "image stream tag name %s must have a name and a tag" % name
|
||||
return (
|
||||
None,
|
||||
None,
|
||||
"image stream tag name %s must have a name and a tag" % name,
|
||||
)
|
||||
return name, tag, None
|
||||
|
||||
from_strategy = _determine_source_strategy()
|
||||
if from_strategy:
|
||||
if from_strategy.get('kind') == "DockerImage":
|
||||
docker_image_ref = from_strategy.get('name').strip()
|
||||
if from_strategy.get("kind") == "DockerImage":
|
||||
docker_image_ref = from_strategy.get("name").strip()
|
||||
if len(docker_image_ref) > 0:
|
||||
err = self.analyze_reference_image(docker_image_ref, referrer)
|
||||
elif from_strategy.get('kind') == "ImageStreamImage":
|
||||
name, tag, error = _parse_image_stream_image_name(from_strategy.get('name'))
|
||||
elif from_strategy.get("kind") == "ImageStreamImage":
|
||||
name, tag, error = _parse_image_stream_image_name(
|
||||
from_strategy.get("name")
|
||||
)
|
||||
if error:
|
||||
if not self.ignore_invalid_refs:
|
||||
return error
|
||||
else:
|
||||
namespace = from_strategy.get('namespace') or namespace
|
||||
self.used_images.append({
|
||||
'namespace': namespace,
|
||||
'name': name,
|
||||
'tag': tag
|
||||
})
|
||||
elif from_strategy.get('kind') == "ImageStreamTag":
|
||||
name, tag, error = _parse_image_stream_tag_name(from_strategy.get('name'))
|
||||
namespace = from_strategy.get("namespace") or namespace
|
||||
self.used_images.append(
|
||||
{"namespace": namespace, "name": name, "tag": tag}
|
||||
)
|
||||
elif from_strategy.get("kind") == "ImageStreamTag":
|
||||
name, tag, error = _parse_image_stream_tag_name(
|
||||
from_strategy.get("name")
|
||||
)
|
||||
if error:
|
||||
if not self.ignore_invalid_refs:
|
||||
return error
|
||||
else:
|
||||
namespace = from_strategy.get('namespace') or namespace
|
||||
self.used_tags.append({
|
||||
'namespace': namespace,
|
||||
'name': name,
|
||||
'tag': tag
|
||||
})
|
||||
namespace = from_strategy.get("namespace") or namespace
|
||||
self.used_tags.append(
|
||||
{"namespace": namespace, "name": name, "tag": tag}
|
||||
)
|
||||
|
||||
def analyze_refs_from_build_strategy(self, resources):
|
||||
# Json Path is always spec.strategy
|
||||
@@ -203,16 +231,20 @@ class OpenShiftAnalyzeImageStream(object):
|
||||
"namespace": obj["metadata"]["namespace"],
|
||||
"name": obj["metadata"]["name"],
|
||||
}
|
||||
error = self.analyze_refs_from_strategy(obj['spec']['strategy'],
|
||||
obj['metadata']['namespace'],
|
||||
referrer)
|
||||
error = self.analyze_refs_from_strategy(
|
||||
obj["spec"]["strategy"], obj["metadata"]["namespace"], referrer
|
||||
)
|
||||
if error is not None:
|
||||
return "%s/%s/%s: %s" % (referrer["kind"], referrer["namespace"], referrer["name"], error)
|
||||
return "%s/%s/%s: %s" % (
|
||||
referrer["kind"],
|
||||
referrer["namespace"],
|
||||
referrer["name"],
|
||||
error,
|
||||
)
|
||||
|
||||
def analyze_image_stream(self, resources):
|
||||
|
||||
# Analyze image reference from Pods
|
||||
error = self.analyze_refs_from_pods(resources['Pod'])
|
||||
error = self.analyze_refs_from_pods(resources["Pod"])
|
||||
if error:
|
||||
return None, None, error
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
import traceback
|
||||
import copy
|
||||
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.module_utils.parsing.convert_bool import boolean
|
||||
from ansible.module_utils.six import string_types
|
||||
|
||||
from ansible_collections.community.okd.plugins.module_utils.openshift_common import AnsibleOpenshiftModule
|
||||
from ansible_collections.community.okd.plugins.module_utils.openshift_common import (
|
||||
AnsibleOpenshiftModule,
|
||||
)
|
||||
|
||||
try:
|
||||
from kubernetes.dynamic.exceptions import DynamicApiError
|
||||
@@ -44,10 +45,17 @@ def follow_imagestream_tag_reference(stream, tag):
|
||||
return name, tag, len(parts) == 2
|
||||
|
||||
content = []
|
||||
err_cross_stream_ref = "tag %s points to an imagestreamtag from another ImageStream" % tag
|
||||
err_cross_stream_ref = (
|
||||
"tag %s points to an imagestreamtag from another ImageStream" % tag
|
||||
)
|
||||
while True:
|
||||
if tag in content:
|
||||
return tag, None, multiple, "tag %s on the image stream is a reference to same tag" % tag
|
||||
return (
|
||||
tag,
|
||||
None,
|
||||
multiple,
|
||||
"tag %s on the image stream is a reference to same tag" % tag,
|
||||
)
|
||||
content.append(tag)
|
||||
tag_ref = _imagestream_has_tag()
|
||||
if not tag_ref:
|
||||
@@ -56,7 +64,10 @@ def follow_imagestream_tag_reference(stream, tag):
|
||||
if not tag_ref.get("from") or tag_ref["from"]["kind"] != "ImageStreamTag":
|
||||
return tag, tag_ref, multiple, None
|
||||
|
||||
if tag_ref["from"]["namespace"] != "" and tag_ref["from"]["namespace"] != stream["metadata"]["namespace"]:
|
||||
if (
|
||||
tag_ref["from"]["namespace"] != ""
|
||||
and tag_ref["from"]["namespace"] != stream["metadata"]["namespace"]
|
||||
):
|
||||
return tag, None, multiple, err_cross_stream_ref
|
||||
|
||||
# The reference needs to be followed with two format patterns:
|
||||
@@ -64,7 +75,12 @@ def follow_imagestream_tag_reference(stream, tag):
|
||||
if ":" in tag_ref["from"]["name"]:
|
||||
name, tagref, result = _imagestream_split_tag(tag_ref["from"]["name"])
|
||||
if not result:
|
||||
return tag, None, multiple, "tag %s points to an invalid imagestreamtag" % tag
|
||||
return (
|
||||
tag,
|
||||
None,
|
||||
multiple,
|
||||
"tag %s points to an invalid imagestreamtag" % tag,
|
||||
)
|
||||
if name != stream["metadata"]["namespace"]:
|
||||
# anotheris:sometag - this should not happen.
|
||||
return tag, None, multiple, err_cross_stream_ref
|
||||
@@ -80,7 +96,7 @@ class OpenShiftImportImage(AnsibleOpenshiftModule):
|
||||
super(OpenShiftImportImage, self).__init__(**kwargs)
|
||||
|
||||
self._rest_client = None
|
||||
self.registryhost = self.params.get('registry_url')
|
||||
self.registryhost = self.params.get("registry_url")
|
||||
self.changed = False
|
||||
|
||||
ref_policy = self.params.get("reference_policy")
|
||||
@@ -90,9 +106,7 @@ class OpenShiftImportImage(AnsibleOpenshiftModule):
|
||||
elif ref_policy == "local":
|
||||
ref_policy_type = "Local"
|
||||
|
||||
self.ref_policy = {
|
||||
"type": ref_policy_type
|
||||
}
|
||||
self.ref_policy = {"type": ref_policy_type}
|
||||
|
||||
self.validate_certs = self.params.get("validate_registry_certs")
|
||||
self.cluster_resources = {}
|
||||
@@ -104,15 +118,15 @@ class OpenShiftImportImage(AnsibleOpenshiftModule):
|
||||
"metadata": {
|
||||
"name": stream["metadata"]["name"],
|
||||
"namespace": stream["metadata"]["namespace"],
|
||||
"resourceVersion": stream["metadata"].get("resourceVersion")
|
||||
"resourceVersion": stream["metadata"].get("resourceVersion"),
|
||||
},
|
||||
"spec": {
|
||||
"import": True
|
||||
}
|
||||
"spec": {"import": True},
|
||||
}
|
||||
|
||||
annotations = stream.get("annotations", {})
|
||||
insecure = boolean(annotations.get("openshift.io/image.insecureRepository", True))
|
||||
insecure = boolean(
|
||||
annotations.get("openshift.io/image.insecureRepository", True)
|
||||
)
|
||||
if self.validate_certs is not None:
|
||||
insecure = not self.validate_certs
|
||||
return isi, insecure
|
||||
@@ -126,7 +140,7 @@ class OpenShiftImportImage(AnsibleOpenshiftModule):
|
||||
},
|
||||
"importPolicy": {
|
||||
"insecure": insecure,
|
||||
"scheduled": self.params.get("scheduled")
|
||||
"scheduled": self.params.get("scheduled"),
|
||||
},
|
||||
"referencePolicy": self.ref_policy,
|
||||
}
|
||||
@@ -149,26 +163,23 @@ class OpenShiftImportImage(AnsibleOpenshiftModule):
|
||||
scheduled = scheduled or old_tag["importPolicy"].get("scheduled")
|
||||
|
||||
images = isi["spec"].get("images", [])
|
||||
images.append({
|
||||
"from": {
|
||||
"kind": "DockerImage",
|
||||
"name": tags.get(k),
|
||||
},
|
||||
"to": {
|
||||
"name": k
|
||||
},
|
||||
"importPolicy": {
|
||||
"insecure": insecure,
|
||||
"scheduled": scheduled
|
||||
},
|
||||
"referencePolicy": self.ref_policy,
|
||||
})
|
||||
images.append(
|
||||
{
|
||||
"from": {
|
||||
"kind": "DockerImage",
|
||||
"name": tags.get(k),
|
||||
},
|
||||
"to": {"name": k},
|
||||
"importPolicy": {"insecure": insecure, "scheduled": scheduled},
|
||||
"referencePolicy": self.ref_policy,
|
||||
}
|
||||
)
|
||||
isi["spec"]["images"] = images
|
||||
return isi
|
||||
|
||||
def create_image_stream(self, ref):
|
||||
"""
|
||||
Create new ImageStream and accompanying ImageStreamImport
|
||||
Create new ImageStream and accompanying ImageStreamImport
|
||||
"""
|
||||
source = self.params.get("source")
|
||||
if not source:
|
||||
@@ -183,27 +194,20 @@ class OpenShiftImportImage(AnsibleOpenshiftModule):
|
||||
),
|
||||
)
|
||||
if self.params.get("all") and not ref["tag"]:
|
||||
spec = dict(
|
||||
dockerImageRepository=source
|
||||
)
|
||||
spec = dict(dockerImageRepository=source)
|
||||
isi = self.create_image_stream_import_all(stream, source)
|
||||
else:
|
||||
spec = dict(
|
||||
tags=[
|
||||
{
|
||||
"from": {
|
||||
"kind": "DockerImage",
|
||||
"name": source
|
||||
},
|
||||
"referencePolicy": self.ref_policy
|
||||
"from": {"kind": "DockerImage", "name": source},
|
||||
"referencePolicy": self.ref_policy,
|
||||
}
|
||||
]
|
||||
)
|
||||
tags = {ref["tag"]: source}
|
||||
isi = self.create_image_stream_import_tags(stream, tags)
|
||||
stream.update(
|
||||
dict(spec=spec)
|
||||
)
|
||||
stream.update(dict(spec=spec))
|
||||
return stream, isi
|
||||
|
||||
def import_all(self, istream):
|
||||
@@ -220,8 +224,9 @@ class OpenShiftImportImage(AnsibleOpenshiftModule):
|
||||
if t.get("from") and t["from"].get("kind") == "DockerImage":
|
||||
tags[t.get("name")] = t["from"].get("name")
|
||||
if tags == {}:
|
||||
msg = "image stream %s/%s does not have tags pointing to external container images" % (
|
||||
stream["metadata"]["namespace"], stream["metadata"]["name"]
|
||||
msg = (
|
||||
"image stream %s/%s does not have tags pointing to external container images"
|
||||
% (stream["metadata"]["namespace"], stream["metadata"]["name"])
|
||||
)
|
||||
self.fail_json(msg=msg)
|
||||
isi = self.create_image_stream_import_tags(stream, tags)
|
||||
@@ -236,7 +241,9 @@ class OpenShiftImportImage(AnsibleOpenshiftModule):
|
||||
source = self.params.get("source")
|
||||
|
||||
# Follow any referential tags to the destination
|
||||
final_tag, existing, multiple, err = follow_imagestream_tag_reference(stream, tag)
|
||||
final_tag, existing, multiple, err = follow_imagestream_tag_reference(
|
||||
stream, tag
|
||||
)
|
||||
if err:
|
||||
if err == err_stream_not_found_ref:
|
||||
# Create a new tag
|
||||
@@ -245,7 +252,10 @@ class OpenShiftImportImage(AnsibleOpenshiftModule):
|
||||
# if the from is still empty this means there's no such tag defined
|
||||
# nor we can't create any from .spec.dockerImageRepository
|
||||
if not source:
|
||||
msg = "the tag %s does not exist on the image stream - choose an existing tag to import" % tag
|
||||
msg = (
|
||||
"the tag %s does not exist on the image stream - choose an existing tag to import"
|
||||
% tag
|
||||
)
|
||||
self.fail_json(msg=msg)
|
||||
existing = {
|
||||
"from": {
|
||||
@@ -257,13 +267,21 @@ class OpenShiftImportImage(AnsibleOpenshiftModule):
|
||||
self.fail_json(msg=err)
|
||||
else:
|
||||
# Disallow re-importing anything other than DockerImage
|
||||
if existing.get("from", {}) and existing["from"].get("kind") != "DockerImage":
|
||||
if (
|
||||
existing.get("from", {})
|
||||
and existing["from"].get("kind") != "DockerImage"
|
||||
):
|
||||
msg = "tag {tag} points to existing {kind}/={name}, it cannot be re-imported.".format(
|
||||
tag=tag, kind=existing["from"]["kind"], name=existing["from"]["name"]
|
||||
tag=tag,
|
||||
kind=existing["from"]["kind"],
|
||||
name=existing["from"]["name"],
|
||||
)
|
||||
# disallow changing an existing tag
|
||||
if not existing.get("from", {}):
|
||||
msg = "tag %s already exists - you cannot change the source using this module." % tag
|
||||
msg = (
|
||||
"tag %s already exists - you cannot change the source using this module."
|
||||
% tag
|
||||
)
|
||||
self.fail_json(msg=msg)
|
||||
if source and source != existing["from"]["name"]:
|
||||
if multiple:
|
||||
@@ -271,7 +289,10 @@ class OpenShiftImportImage(AnsibleOpenshiftModule):
|
||||
tag, final_tag, existing["from"]["name"]
|
||||
)
|
||||
else:
|
||||
msg = "the tag %s points to %s you cannot change the source using this module." % (tag, final_tag)
|
||||
msg = (
|
||||
"the tag %s points to %s you cannot change the source using this module."
|
||||
% (tag, final_tag)
|
||||
)
|
||||
self.fail_json(msg=msg)
|
||||
|
||||
# Set the target item to import
|
||||
@@ -309,13 +330,13 @@ class OpenShiftImportImage(AnsibleOpenshiftModule):
|
||||
kind=kind,
|
||||
api_version=api_version,
|
||||
name=ref.get("name"),
|
||||
namespace=self.params.get("namespace")
|
||||
namespace=self.params.get("namespace"),
|
||||
)
|
||||
result = self.kubernetes_facts(**params)
|
||||
if not result["api_found"]:
|
||||
msg = 'Failed to find API for resource with apiVersion "{0}" and kind "{1}"'.format(
|
||||
api_version, kind
|
||||
),
|
||||
)
|
||||
self.fail_json(msg=msg)
|
||||
imagestream = None
|
||||
if len(result["resources"]) > 0:
|
||||
@@ -335,7 +356,9 @@ class OpenShiftImportImage(AnsibleOpenshiftModule):
|
||||
def parse_image_reference(self, image_ref):
|
||||
result, err = parse_docker_image_ref(image_ref, self.module)
|
||||
if result.get("digest"):
|
||||
self.fail_json(msg="Cannot import by ID, error with definition: %s" % image_ref)
|
||||
self.fail_json(
|
||||
msg="Cannot import by ID, error with definition: %s" % image_ref
|
||||
)
|
||||
tag = result.get("tag") or None
|
||||
if not self.params.get("all") and not tag:
|
||||
tag = "latest"
|
||||
@@ -345,7 +368,6 @@ class OpenShiftImportImage(AnsibleOpenshiftModule):
|
||||
return dict(name=result.get("name"), tag=tag, source=image_ref)
|
||||
|
||||
def execute_module(self):
|
||||
|
||||
names = []
|
||||
name = self.params.get("name")
|
||||
if isinstance(name, string_types):
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
# 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)
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
@@ -24,109 +25,119 @@ LDAP_SEARCH_OUT_OF_SCOPE_ERROR = "trying to search by DN for an entry that exist
|
||||
|
||||
def validate_ldap_sync_config(config):
|
||||
# Validate url
|
||||
url = config.get('url')
|
||||
url = config.get("url")
|
||||
if not url:
|
||||
return "url should be non empty attribute."
|
||||
|
||||
# Make sure bindDN and bindPassword are both set, or both unset
|
||||
bind_dn = config.get('bindDN', "")
|
||||
bind_password = config.get('bindPassword', "")
|
||||
bind_dn = config.get("bindDN", "")
|
||||
bind_password = config.get("bindPassword", "")
|
||||
if (len(bind_dn) == 0) != (len(bind_password) == 0):
|
||||
return "bindDN and bindPassword must both be specified, or both be empty."
|
||||
|
||||
insecure = boolean(config.get('insecure'))
|
||||
ca_file = config.get('ca')
|
||||
insecure = boolean(config.get("insecure"))
|
||||
ca_file = config.get("ca")
|
||||
if insecure:
|
||||
if url.startswith('ldaps://'):
|
||||
if url.startswith("ldaps://"):
|
||||
return "Cannot use ldaps scheme with insecure=true."
|
||||
if ca_file:
|
||||
return "Cannot specify a ca with insecure=true."
|
||||
elif ca_file and not os.path.isfile(ca_file):
|
||||
return "could not read ca file: {0}.".format(ca_file)
|
||||
|
||||
nameMapping = config.get('groupUIDNameMapping', {})
|
||||
nameMapping = config.get("groupUIDNameMapping", {})
|
||||
for k, v in iteritems(nameMapping):
|
||||
if len(k) == 0 or len(v) == 0:
|
||||
return "groupUIDNameMapping has empty key or value"
|
||||
|
||||
schemas = []
|
||||
schema_list = ('rfc2307', 'activeDirectory', 'augmentedActiveDirectory')
|
||||
schema_list = ("rfc2307", "activeDirectory", "augmentedActiveDirectory")
|
||||
for schema in schema_list:
|
||||
if schema in config:
|
||||
schemas.append(schema)
|
||||
|
||||
if len(schemas) == 0:
|
||||
return "No schema-specific config was provided, should be one of %s" % ", ".join(schema_list)
|
||||
return (
|
||||
"No schema-specific config was provided, should be one of %s"
|
||||
% ", ".join(schema_list)
|
||||
)
|
||||
if len(schemas) > 1:
|
||||
return "Exactly one schema-specific config is required; found (%d) %s" % (len(schemas), ','.join(schemas))
|
||||
return "Exactly one schema-specific config is required; found (%d) %s" % (
|
||||
len(schemas),
|
||||
",".join(schemas),
|
||||
)
|
||||
|
||||
if schemas[0] == 'rfc2307':
|
||||
if schemas[0] == "rfc2307":
|
||||
return validate_RFC2307(config.get("rfc2307"))
|
||||
elif schemas[0] == 'activeDirectory':
|
||||
elif schemas[0] == "activeDirectory":
|
||||
return validate_ActiveDirectory(config.get("activeDirectory"))
|
||||
elif schemas[0] == 'augmentedActiveDirectory':
|
||||
elif schemas[0] == "augmentedActiveDirectory":
|
||||
return validate_AugmentedActiveDirectory(config.get("augmentedActiveDirectory"))
|
||||
|
||||
|
||||
def validate_ldap_query(qry, isDNOnly=False):
|
||||
|
||||
# validate query scope
|
||||
scope = qry.get('scope')
|
||||
scope = qry.get("scope")
|
||||
if scope and scope not in ("", "sub", "one", "base"):
|
||||
return "invalid scope %s" % scope
|
||||
|
||||
# validate deref aliases
|
||||
derefAlias = qry.get('derefAliases')
|
||||
derefAlias = qry.get("derefAliases")
|
||||
if derefAlias and derefAlias not in ("never", "search", "base", "always"):
|
||||
return "not a valid LDAP alias dereferncing behavior: %s", derefAlias
|
||||
|
||||
# validate timeout
|
||||
timeout = qry.get('timeout')
|
||||
timeout = qry.get("timeout")
|
||||
if timeout and float(timeout) < 0:
|
||||
return "timeout must be equal to or greater than zero"
|
||||
|
||||
# Validate DN only
|
||||
qry_filter = qry.get('filter', "")
|
||||
qry_filter = qry.get("filter", "")
|
||||
if isDNOnly:
|
||||
if len(qry_filter) > 0:
|
||||
return 'cannot specify a filter when using "dn" as the UID attribute'
|
||||
else:
|
||||
# validate filter
|
||||
if len(qry_filter) == 0 or qry_filter[0] != '(':
|
||||
if len(qry_filter) == 0 or qry_filter[0] != "(":
|
||||
return "filter does not start with an '('"
|
||||
return None
|
||||
|
||||
|
||||
def validate_RFC2307(config):
|
||||
qry = config.get('groupsQuery')
|
||||
qry = config.get("groupsQuery")
|
||||
if not qry or not isinstance(qry, dict):
|
||||
return "RFC2307: groupsQuery requires a dictionary"
|
||||
error = validate_ldap_query(qry)
|
||||
if not error:
|
||||
return error
|
||||
for field in ('groupUIDAttribute', 'groupNameAttributes', 'groupMembershipAttributes',
|
||||
'userUIDAttribute', 'userNameAttributes'):
|
||||
for field in (
|
||||
"groupUIDAttribute",
|
||||
"groupNameAttributes",
|
||||
"groupMembershipAttributes",
|
||||
"userUIDAttribute",
|
||||
"userNameAttributes",
|
||||
):
|
||||
value = config.get(field)
|
||||
if not value:
|
||||
return "RFC2307: {0} is required.".format(field)
|
||||
|
||||
users_qry = config.get('usersQuery')
|
||||
users_qry = config.get("usersQuery")
|
||||
if not users_qry or not isinstance(users_qry, dict):
|
||||
return "RFC2307: usersQuery requires a dictionary"
|
||||
|
||||
isUserDNOnly = (config.get('userUIDAttribute').strip() == 'dn')
|
||||
isUserDNOnly = config.get("userUIDAttribute").strip() == "dn"
|
||||
return validate_ldap_query(users_qry, isDNOnly=isUserDNOnly)
|
||||
|
||||
|
||||
def validate_ActiveDirectory(config, label="ActiveDirectory"):
|
||||
users_qry = config.get('usersQuery')
|
||||
users_qry = config.get("usersQuery")
|
||||
if not users_qry or not isinstance(users_qry, dict):
|
||||
return "{0}: usersQuery requires as dictionnary".format(label)
|
||||
error = validate_ldap_query(users_qry)
|
||||
if not error:
|
||||
return error
|
||||
|
||||
for field in ('userNameAttributes', 'groupMembershipAttributes'):
|
||||
for field in ("userNameAttributes", "groupMembershipAttributes"):
|
||||
value = config.get(field)
|
||||
if not value:
|
||||
return "{0}: {1} is required.".format(field, label)
|
||||
@@ -138,24 +149,24 @@ def validate_AugmentedActiveDirectory(config):
|
||||
error = validate_ActiveDirectory(config, label="AugmentedActiveDirectory")
|
||||
if not error:
|
||||
return error
|
||||
for field in ('groupUIDAttribute', 'groupNameAttributes'):
|
||||
for field in ("groupUIDAttribute", "groupNameAttributes"):
|
||||
value = config.get(field)
|
||||
if not value:
|
||||
return "AugmentedActiveDirectory: {0} is required".format(field)
|
||||
groups_qry = config.get('groupsQuery')
|
||||
groups_qry = config.get("groupsQuery")
|
||||
if not groups_qry or not isinstance(groups_qry, dict):
|
||||
return "AugmentedActiveDirectory: groupsQuery requires as dictionnary."
|
||||
|
||||
isGroupDNOnly = (config.get('groupUIDAttribute').strip() == 'dn')
|
||||
isGroupDNOnly = config.get("groupUIDAttribute").strip() == "dn"
|
||||
return validate_ldap_query(groups_qry, isDNOnly=isGroupDNOnly)
|
||||
|
||||
|
||||
def determine_ldap_scope(scope):
|
||||
if scope in ("", "sub"):
|
||||
return ldap.SCOPE_SUBTREE
|
||||
elif scope == 'base':
|
||||
elif scope == "base":
|
||||
return ldap.SCOPE_BASE
|
||||
elif scope == 'one':
|
||||
elif scope == "one":
|
||||
return ldap.SCOPE_ONELEVEL
|
||||
return None
|
||||
|
||||
@@ -175,28 +186,28 @@ def determine_deref_aliases(derefAlias):
|
||||
|
||||
def openshift_ldap_build_base_query(config):
|
||||
qry = {}
|
||||
if config.get('baseDN'):
|
||||
qry['base'] = config.get('baseDN')
|
||||
if config.get("baseDN"):
|
||||
qry["base"] = config.get("baseDN")
|
||||
|
||||
scope = determine_ldap_scope(config.get('scope'))
|
||||
scope = determine_ldap_scope(config.get("scope"))
|
||||
if scope:
|
||||
qry['scope'] = scope
|
||||
qry["scope"] = scope
|
||||
|
||||
pageSize = config.get('pageSize')
|
||||
pageSize = config.get("pageSize")
|
||||
if pageSize and int(pageSize) > 0:
|
||||
qry['sizelimit'] = int(pageSize)
|
||||
qry["sizelimit"] = int(pageSize)
|
||||
|
||||
timeout = config.get('timeout')
|
||||
timeout = config.get("timeout")
|
||||
if timeout and int(timeout) > 0:
|
||||
qry['timeout'] = int(timeout)
|
||||
qry["timeout"] = int(timeout)
|
||||
|
||||
filter = config.get('filter')
|
||||
filter = config.get("filter")
|
||||
if filter:
|
||||
qry['filterstr'] = filter
|
||||
qry["filterstr"] = filter
|
||||
|
||||
derefAlias = determine_deref_aliases(config.get('derefAliases'))
|
||||
derefAlias = determine_deref_aliases(config.get("derefAliases"))
|
||||
if derefAlias:
|
||||
qry['derefAlias'] = derefAlias
|
||||
qry["derefAlias"] = derefAlias
|
||||
return qry
|
||||
|
||||
|
||||
@@ -205,32 +216,30 @@ def openshift_ldap_get_attribute_for_entry(entry, attribute):
|
||||
if isinstance(attribute, list):
|
||||
attributes = attribute
|
||||
for k in attributes:
|
||||
if k.lower() == 'dn':
|
||||
if k.lower() == "dn":
|
||||
return entry[0]
|
||||
v = entry[1].get(k, None)
|
||||
if v:
|
||||
if isinstance(v, list):
|
||||
result = []
|
||||
for x in v:
|
||||
if hasattr(x, 'decode'):
|
||||
result.append(x.decode('utf-8'))
|
||||
if hasattr(x, "decode"):
|
||||
result.append(x.decode("utf-8"))
|
||||
else:
|
||||
result.append(x)
|
||||
return result
|
||||
else:
|
||||
return v.decode('utf-8') if hasattr(v, 'decode') else v
|
||||
return v.decode("utf-8") if hasattr(v, "decode") else v
|
||||
return ""
|
||||
|
||||
|
||||
def ldap_split_host_port(hostport):
|
||||
"""
|
||||
ldap_split_host_port splits a network address of the form "host:port",
|
||||
"host%zone:port", "[host]:port" or "[host%zone]:port" into host or
|
||||
host%zone and port.
|
||||
ldap_split_host_port splits a network address of the form "host:port",
|
||||
"host%zone:port", "[host]:port" or "[host%zone]:port" into host or
|
||||
host%zone and port.
|
||||
"""
|
||||
result = dict(
|
||||
scheme=None, netlocation=None, host=None, port=None
|
||||
)
|
||||
result = dict(scheme=None, netlocation=None, host=None, port=None)
|
||||
if not hostport:
|
||||
return result, None
|
||||
|
||||
@@ -240,10 +249,10 @@ def ldap_split_host_port(hostport):
|
||||
if "://" in hostport:
|
||||
idx = hostport.find(scheme_l)
|
||||
result["scheme"] = hostport[:idx]
|
||||
netlocation = hostport[idx + len(scheme_l):]
|
||||
netlocation = hostport[idx + len(scheme_l):] # fmt: skip
|
||||
result["netlocation"] = netlocation
|
||||
|
||||
if netlocation[-1] == ']':
|
||||
if netlocation[-1] == "]":
|
||||
# ipv6 literal (with no port)
|
||||
result["host"] = netlocation
|
||||
|
||||
@@ -259,21 +268,32 @@ def ldap_split_host_port(hostport):
|
||||
|
||||
def openshift_ldap_query_for_entries(connection, qry, unique_entry=True):
|
||||
# set deref alias (TODO: need to set a default value to reset for each transaction)
|
||||
derefAlias = qry.pop('derefAlias', None)
|
||||
derefAlias = qry.pop("derefAlias", None)
|
||||
if derefAlias:
|
||||
ldap.set_option(ldap.OPT_DEREF, derefAlias)
|
||||
try:
|
||||
result = connection.search_ext_s(**qry)
|
||||
if not result or len(result) == 0:
|
||||
return None, "Entry not found for base='{0}' and filter='{1}'".format(qry['base'], qry['filterstr'])
|
||||
return None, "Entry not found for base='{0}' and filter='{1}'".format(
|
||||
qry["base"], qry["filterstr"]
|
||||
)
|
||||
if len(result) > 1 and unique_entry:
|
||||
if qry.get('scope') == ldap.SCOPE_BASE:
|
||||
return None, "multiple entries found matching dn={0}: {1}".format(qry['base'], result)
|
||||
if qry.get("scope") == ldap.SCOPE_BASE:
|
||||
return None, "multiple entries found matching dn={0}: {1}".format(
|
||||
qry["base"], result
|
||||
)
|
||||
else:
|
||||
return None, "multiple entries found matching filter {0}: {1}".format(qry['filterstr'], result)
|
||||
return None, "multiple entries found matching filter {0}: {1}".format(
|
||||
qry["filterstr"], result
|
||||
)
|
||||
return result, None
|
||||
except ldap.NO_SUCH_OBJECT:
|
||||
return None, "search for entry with base dn='{0}' refers to a non-existent entry".format(qry['base'])
|
||||
return (
|
||||
None,
|
||||
"search for entry with base dn='{0}' refers to a non-existent entry".format(
|
||||
qry["base"]
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def openshift_equal_dn_objects(dn_obj, other_dn_obj):
|
||||
@@ -303,7 +323,9 @@ def openshift_ancestorof_dn(dn, other):
|
||||
if len(dn_obj) >= len(other_dn_obj):
|
||||
return False
|
||||
# Take the last attribute from the other DN to compare against
|
||||
return openshift_equal_dn_objects(dn_obj, other_dn_obj[len(other_dn_obj) - len(dn_obj):])
|
||||
return openshift_equal_dn_objects(
|
||||
dn_obj, other_dn_obj[len(other_dn_obj) - len(dn_obj):] # fmt: skip
|
||||
)
|
||||
|
||||
|
||||
class OpenshiftLDAPQueryOnAttribute(object):
|
||||
@@ -324,33 +346,38 @@ class OpenshiftLDAPQueryOnAttribute(object):
|
||||
output = []
|
||||
hex_string = "0123456789abcdef"
|
||||
for c in buffer:
|
||||
if ord(c) > 0x7f or c in ('(', ')', '\\', '*') or c == 0:
|
||||
if ord(c) > 0x7F or c in ("(", ")", "\\", "*") or c == 0:
|
||||
first = ord(c) >> 4
|
||||
second = ord(c) & 0xf
|
||||
output += ['\\', hex_string[first], hex_string[second]]
|
||||
second = ord(c) & 0xF
|
||||
output += ["\\", hex_string[first], hex_string[second]]
|
||||
else:
|
||||
output.append(c)
|
||||
return ''.join(output)
|
||||
return "".join(output)
|
||||
|
||||
def build_request(self, ldapuid, attributes):
|
||||
params = copy.deepcopy(self.qry)
|
||||
if self.query_attribute.lower() == 'dn':
|
||||
if self.query_attribute.lower() == "dn":
|
||||
if ldapuid:
|
||||
if not openshift_equal_dn(ldapuid, params['base']) and not openshift_ancestorof_dn(params['base'], ldapuid):
|
||||
if not openshift_equal_dn(
|
||||
ldapuid, params["base"]
|
||||
) and not openshift_ancestorof_dn(params["base"], ldapuid):
|
||||
return None, LDAP_SEARCH_OUT_OF_SCOPE_ERROR
|
||||
params['base'] = ldapuid
|
||||
params['scope'] = ldap.SCOPE_BASE
|
||||
params["base"] = ldapuid
|
||||
params["scope"] = ldap.SCOPE_BASE
|
||||
# filter that returns all values
|
||||
params['filterstr'] = "(objectClass=*)"
|
||||
params['attrlist'] = attributes
|
||||
params["filterstr"] = "(objectClass=*)"
|
||||
params["attrlist"] = attributes
|
||||
else:
|
||||
# Builds the query containing a filter that conjoins the common filter given
|
||||
# in the configuration with the specific attribute filter for which the attribute value is given
|
||||
specificFilter = "%s=%s" % (self.escape_filter(self.query_attribute), self.escape_filter(ldapuid))
|
||||
qry_filter = params.get('filterstr', None)
|
||||
specificFilter = "%s=%s" % (
|
||||
self.escape_filter(self.query_attribute),
|
||||
self.escape_filter(ldapuid),
|
||||
)
|
||||
qry_filter = params.get("filterstr", None)
|
||||
if qry_filter:
|
||||
params['filterstr'] = "(&%s(%s))" % (qry_filter, specificFilter)
|
||||
params['attrlist'] = attributes
|
||||
params["filterstr"] = "(&%s(%s))" % (qry_filter, specificFilter)
|
||||
params["attrlist"] = attributes
|
||||
return params, None
|
||||
|
||||
def ldap_search(self, connection, ldapuid, required_attributes, unique_entry=True):
|
||||
@@ -358,21 +385,29 @@ class OpenshiftLDAPQueryOnAttribute(object):
|
||||
if error:
|
||||
return None, error
|
||||
# set deref alias (TODO: need to set a default value to reset for each transaction)
|
||||
derefAlias = query.pop('derefAlias', None)
|
||||
derefAlias = query.pop("derefAlias", None)
|
||||
if derefAlias:
|
||||
ldap.set_option(ldap.OPT_DEREF, derefAlias)
|
||||
|
||||
try:
|
||||
result = connection.search_ext_s(**query)
|
||||
if not result or len(result) == 0:
|
||||
return None, "Entry not found for base='{0}' and filter='{1}'".format(query['base'], query['filterstr'])
|
||||
return None, "Entry not found for base='{0}' and filter='{1}'".format(
|
||||
query["base"], query["filterstr"]
|
||||
)
|
||||
if unique_entry:
|
||||
if len(result) > 1:
|
||||
return None, "Multiple Entries found matching search criteria: %s (%s)" % (query, result)
|
||||
return (
|
||||
None,
|
||||
"Multiple Entries found matching search criteria: %s (%s)"
|
||||
% (query, result),
|
||||
)
|
||||
result = result[0]
|
||||
return result, None
|
||||
except ldap.NO_SUCH_OBJECT:
|
||||
return None, "Entry not found for base='{0}' and filter='{1}'".format(query['base'], query['filterstr'])
|
||||
return None, "Entry not found for base='{0}' and filter='{1}'".format(
|
||||
query["base"], query["filterstr"]
|
||||
)
|
||||
except Exception as err:
|
||||
return None, "Request %s failed due to: %s" % (query, err)
|
||||
|
||||
@@ -384,30 +419,43 @@ class OpenshiftLDAPQuery(object):
|
||||
|
||||
def build_request(self, attributes):
|
||||
params = copy.deepcopy(self.qry)
|
||||
params['attrlist'] = attributes
|
||||
params["attrlist"] = attributes
|
||||
return params
|
||||
|
||||
def ldap_search(self, connection, required_attributes):
|
||||
query = self.build_request(required_attributes)
|
||||
# set deref alias (TODO: need to set a default value to reset for each transaction)
|
||||
derefAlias = query.pop('derefAlias', None)
|
||||
derefAlias = query.pop("derefAlias", None)
|
||||
if derefAlias:
|
||||
ldap.set_option(ldap.OPT_DEREF, derefAlias)
|
||||
|
||||
try:
|
||||
result = connection.search_ext_s(**query)
|
||||
if not result or len(result) == 0:
|
||||
return None, "Entry not found for base='{0}' and filter='{1}'".format(query['base'], query['filterstr'])
|
||||
return None, "Entry not found for base='{0}' and filter='{1}'".format(
|
||||
query["base"], query["filterstr"]
|
||||
)
|
||||
return result, None
|
||||
except ldap.NO_SUCH_OBJECT:
|
||||
return None, "search for entry with base dn='{0}' refers to a non-existent entry".format(query['base'])
|
||||
return (
|
||||
None,
|
||||
"search for entry with base dn='{0}' refers to a non-existent entry".format(
|
||||
query["base"]
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class OpenshiftLDAPInterface(object):
|
||||
|
||||
def __init__(self, connection, groupQuery, groupNameAttributes, groupMembershipAttributes,
|
||||
userQuery, userNameAttributes, config):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
connection,
|
||||
groupQuery,
|
||||
groupNameAttributes,
|
||||
groupMembershipAttributes,
|
||||
userQuery,
|
||||
userNameAttributes,
|
||||
config,
|
||||
):
|
||||
self.connection = connection
|
||||
self.groupQuery = copy.deepcopy(groupQuery)
|
||||
self.groupNameAttributes = groupNameAttributes
|
||||
@@ -416,8 +464,12 @@ class OpenshiftLDAPInterface(object):
|
||||
self.userNameAttributes = userNameAttributes
|
||||
self.config = config
|
||||
|
||||
self.tolerate_not_found = boolean(config.get('tolerateMemberNotFoundErrors', False))
|
||||
self.tolerate_out_of_scope = boolean(config.get('tolerateMemberOutOfScopeErrors', False))
|
||||
self.tolerate_not_found = boolean(
|
||||
config.get("tolerateMemberNotFoundErrors", False)
|
||||
)
|
||||
self.tolerate_out_of_scope = boolean(
|
||||
config.get("tolerateMemberOutOfScopeErrors", False)
|
||||
)
|
||||
|
||||
self.required_group_attributes = [self.groupQuery.query_attribute]
|
||||
for x in self.groupNameAttributes + self.groupMembershipAttributes:
|
||||
@@ -434,13 +486,15 @@ class OpenshiftLDAPInterface(object):
|
||||
|
||||
def get_group_entry(self, uid):
|
||||
"""
|
||||
get_group_entry returns an LDAP group entry for the given group UID by searching the internal cache
|
||||
of the LDAPInterface first, then sending an LDAP query if the cache did not contain the entry.
|
||||
get_group_entry returns an LDAP group entry for the given group UID by searching the internal cache
|
||||
of the LDAPInterface first, then sending an LDAP query if the cache did not contain the entry.
|
||||
"""
|
||||
if uid in self.cached_groups:
|
||||
return self.cached_groups.get(uid), None
|
||||
|
||||
group, err = self.groupQuery.ldap_search(self.connection, uid, self.required_group_attributes)
|
||||
group, err = self.groupQuery.ldap_search(
|
||||
self.connection, uid, self.required_group_attributes
|
||||
)
|
||||
if err:
|
||||
return None, err
|
||||
self.cached_groups[uid] = group
|
||||
@@ -448,13 +502,15 @@ class OpenshiftLDAPInterface(object):
|
||||
|
||||
def get_user_entry(self, uid):
|
||||
"""
|
||||
get_user_entry returns an LDAP group entry for the given user UID by searching the internal cache
|
||||
of the LDAPInterface first, then sending an LDAP query if the cache did not contain the entry.
|
||||
get_user_entry returns an LDAP group entry for the given user UID by searching the internal cache
|
||||
of the LDAPInterface first, then sending an LDAP query if the cache did not contain the entry.
|
||||
"""
|
||||
if uid in self.cached_users:
|
||||
return self.cached_users.get(uid), None
|
||||
|
||||
entry, err = self.userQuery.ldap_search(self.connection, uid, self.required_user_attributes)
|
||||
entry, err = self.userQuery.ldap_search(
|
||||
self.connection, uid, self.required_user_attributes
|
||||
)
|
||||
if err:
|
||||
return None, err
|
||||
self.cached_users[uid] = entry
|
||||
@@ -466,19 +522,19 @@ class OpenshiftLDAPInterface(object):
|
||||
|
||||
def list_groups(self):
|
||||
group_qry = copy.deepcopy(self.groupQuery.qry)
|
||||
group_qry['attrlist'] = self.required_group_attributes
|
||||
group_qry["attrlist"] = self.required_group_attributes
|
||||
|
||||
groups, err = openshift_ldap_query_for_entries(
|
||||
connection=self.connection,
|
||||
qry=group_qry,
|
||||
unique_entry=False
|
||||
connection=self.connection, qry=group_qry, unique_entry=False
|
||||
)
|
||||
if err:
|
||||
return None, err
|
||||
|
||||
group_uids = []
|
||||
for entry in groups:
|
||||
uid = openshift_ldap_get_attribute_for_entry(entry, self.groupQuery.query_attribute)
|
||||
uid = openshift_ldap_get_attribute_for_entry(
|
||||
entry, self.groupQuery.query_attribute
|
||||
)
|
||||
if not uid:
|
||||
return None, "Unable to find LDAP group uid for entry %s" % entry
|
||||
self.cached_groups[uid] = entry
|
||||
@@ -487,7 +543,7 @@ class OpenshiftLDAPInterface(object):
|
||||
|
||||
def extract_members(self, uid):
|
||||
"""
|
||||
returns the LDAP member entries for a group specified with a ldapGroupUID
|
||||
returns the LDAP member entries for a group specified with a ldapGroupUID
|
||||
"""
|
||||
# Get group entry from LDAP
|
||||
group, err = self.get_group_entry(uid)
|
||||
@@ -514,39 +570,46 @@ class OpenshiftLDAPInterface(object):
|
||||
|
||||
|
||||
class OpenshiftLDAPRFC2307(object):
|
||||
|
||||
def __init__(self, config, ldap_connection):
|
||||
|
||||
self.config = config
|
||||
self.ldap_interface = self.create_ldap_interface(ldap_connection)
|
||||
|
||||
def create_ldap_interface(self, connection):
|
||||
segment = self.config.get("rfc2307")
|
||||
groups_base_qry = openshift_ldap_build_base_query(segment['groupsQuery'])
|
||||
users_base_qry = openshift_ldap_build_base_query(segment['usersQuery'])
|
||||
groups_base_qry = openshift_ldap_build_base_query(segment["groupsQuery"])
|
||||
users_base_qry = openshift_ldap_build_base_query(segment["usersQuery"])
|
||||
|
||||
groups_query = OpenshiftLDAPQueryOnAttribute(groups_base_qry, segment['groupUIDAttribute'])
|
||||
users_query = OpenshiftLDAPQueryOnAttribute(users_base_qry, segment['userUIDAttribute'])
|
||||
groups_query = OpenshiftLDAPQueryOnAttribute(
|
||||
groups_base_qry, segment["groupUIDAttribute"]
|
||||
)
|
||||
users_query = OpenshiftLDAPQueryOnAttribute(
|
||||
users_base_qry, segment["userUIDAttribute"]
|
||||
)
|
||||
|
||||
params = dict(
|
||||
connection=connection,
|
||||
groupQuery=groups_query,
|
||||
groupNameAttributes=segment['groupNameAttributes'],
|
||||
groupMembershipAttributes=segment['groupMembershipAttributes'],
|
||||
groupNameAttributes=segment["groupNameAttributes"],
|
||||
groupMembershipAttributes=segment["groupMembershipAttributes"],
|
||||
userQuery=users_query,
|
||||
userNameAttributes=segment['userNameAttributes'],
|
||||
config=segment
|
||||
userNameAttributes=segment["userNameAttributes"],
|
||||
config=segment,
|
||||
)
|
||||
return OpenshiftLDAPInterface(**params)
|
||||
|
||||
def get_username_for_entry(self, entry):
|
||||
username = openshift_ldap_get_attribute_for_entry(entry, self.ldap_interface.userNameAttributes)
|
||||
username = openshift_ldap_get_attribute_for_entry(
|
||||
entry, self.ldap_interface.userNameAttributes
|
||||
)
|
||||
if not username:
|
||||
return None, "The user entry (%s) does not map to a OpenShift User name with the given mapping" % entry
|
||||
return (
|
||||
None,
|
||||
"The user entry (%s) does not map to a OpenShift User name with the given mapping"
|
||||
% entry,
|
||||
)
|
||||
return username, None
|
||||
|
||||
def get_group_name_for_uid(self, uid):
|
||||
|
||||
# Get name from User defined mapping
|
||||
groupuid_name_mapping = self.config.get("groupUIDNameMapping")
|
||||
if groupuid_name_mapping and uid in groupuid_name_mapping:
|
||||
@@ -555,10 +618,13 @@ class OpenshiftLDAPRFC2307(object):
|
||||
group, err = self.ldap_interface.get_group_entry(uid)
|
||||
if err:
|
||||
return None, err
|
||||
group_name = openshift_ldap_get_attribute_for_entry(group, self.ldap_interface.groupNameAttributes)
|
||||
group_name = openshift_ldap_get_attribute_for_entry(
|
||||
group, self.ldap_interface.groupNameAttributes
|
||||
)
|
||||
if not group_name:
|
||||
error = "The group entry (%s) does not map to an OpenShift Group name with the given name attribute (%s)" % (
|
||||
group, self.ldap_interface.groupNameAttributes
|
||||
error = (
|
||||
"The group entry (%s) does not map to an OpenShift Group name with the given name attribute (%s)"
|
||||
% (group, self.ldap_interface.groupNameAttributes)
|
||||
)
|
||||
return None, error
|
||||
if isinstance(group_name, list):
|
||||
@@ -570,7 +636,11 @@ class OpenshiftLDAPRFC2307(object):
|
||||
def is_ldapgroup_exists(self, uid):
|
||||
group, err = self.ldap_interface.get_group_entry(uid)
|
||||
if err:
|
||||
if err == LDAP_SEARCH_OUT_OF_SCOPE_ERROR or err.startswith("Entry not found") or "non-existent entry" in err:
|
||||
if (
|
||||
err == LDAP_SEARCH_OUT_OF_SCOPE_ERROR
|
||||
or err.startswith("Entry not found")
|
||||
or "non-existent entry" in err
|
||||
):
|
||||
return False, None
|
||||
return False, err
|
||||
if group:
|
||||
@@ -585,7 +655,6 @@ class OpenshiftLDAPRFC2307(object):
|
||||
|
||||
|
||||
class OpenshiftLDAP_ADInterface(object):
|
||||
|
||||
def __init__(self, connection, user_query, group_member_attr, user_name_attr):
|
||||
self.connection = connection
|
||||
self.userQuery = user_query
|
||||
@@ -609,7 +678,9 @@ class OpenshiftLDAP_ADInterface(object):
|
||||
def populate_cache(self):
|
||||
if not self.cache_populated:
|
||||
self.cache_populated = True
|
||||
entries, err = self.userQuery.ldap_search(self.connection, self.required_user_attributes)
|
||||
entries, err = self.userQuery.ldap_search(
|
||||
self.connection, self.required_user_attributes
|
||||
)
|
||||
if err:
|
||||
return err
|
||||
|
||||
@@ -645,7 +716,9 @@ class OpenshiftLDAP_ADInterface(object):
|
||||
users_in_group = []
|
||||
for attr in self.groupMembershipAttributes:
|
||||
query_on_attribute = OpenshiftLDAPQueryOnAttribute(self.userQuery.qry, attr)
|
||||
entries, error = query_on_attribute.ldap_search(self.connection, uid, self.required_user_attributes, unique_entry=False)
|
||||
entries, error = query_on_attribute.ldap_search(
|
||||
self.connection, uid, self.required_user_attributes, unique_entry=False
|
||||
)
|
||||
if error and "not found" not in error:
|
||||
return None, error
|
||||
if not entries:
|
||||
@@ -660,15 +733,13 @@ class OpenshiftLDAP_ADInterface(object):
|
||||
|
||||
|
||||
class OpenshiftLDAPActiveDirectory(object):
|
||||
|
||||
def __init__(self, config, ldap_connection):
|
||||
|
||||
self.config = config
|
||||
self.ldap_interface = self.create_ldap_interface(ldap_connection)
|
||||
|
||||
def create_ldap_interface(self, connection):
|
||||
segment = self.config.get("activeDirectory")
|
||||
base_query = openshift_ldap_build_base_query(segment['usersQuery'])
|
||||
base_query = openshift_ldap_build_base_query(segment["usersQuery"])
|
||||
user_query = OpenshiftLDAPQuery(base_query)
|
||||
|
||||
return OpenshiftLDAP_ADInterface(
|
||||
@@ -679,9 +750,15 @@ class OpenshiftLDAPActiveDirectory(object):
|
||||
)
|
||||
|
||||
def get_username_for_entry(self, entry):
|
||||
username = openshift_ldap_get_attribute_for_entry(entry, self.ldap_interface.userNameAttributes)
|
||||
username = openshift_ldap_get_attribute_for_entry(
|
||||
entry, self.ldap_interface.userNameAttributes
|
||||
)
|
||||
if not username:
|
||||
return None, "The user entry (%s) does not map to a OpenShift User name with the given mapping" % entry
|
||||
return (
|
||||
None,
|
||||
"The user entry (%s) does not map to a OpenShift User name with the given mapping"
|
||||
% entry,
|
||||
)
|
||||
return username, None
|
||||
|
||||
def get_group_name_for_uid(self, uid):
|
||||
@@ -702,8 +779,15 @@ class OpenshiftLDAPActiveDirectory(object):
|
||||
|
||||
|
||||
class OpenshiftLDAP_AugmentedADInterface(OpenshiftLDAP_ADInterface):
|
||||
|
||||
def __init__(self, connection, user_query, group_member_attr, user_name_attr, group_qry, group_name_attr):
|
||||
def __init__(
|
||||
self,
|
||||
connection,
|
||||
user_query,
|
||||
group_member_attr,
|
||||
user_name_attr,
|
||||
group_qry,
|
||||
group_name_attr,
|
||||
):
|
||||
super(OpenshiftLDAP_AugmentedADInterface, self).__init__(
|
||||
connection, user_query, group_member_attr, user_name_attr
|
||||
)
|
||||
@@ -719,13 +803,15 @@ class OpenshiftLDAP_AugmentedADInterface(OpenshiftLDAP_ADInterface):
|
||||
|
||||
def get_group_entry(self, uid):
|
||||
"""
|
||||
get_group_entry returns an LDAP group entry for the given group UID by searching the internal cache
|
||||
of the LDAPInterface first, then sending an LDAP query if the cache did not contain the entry.
|
||||
get_group_entry returns an LDAP group entry for the given group UID by searching the internal cache
|
||||
of the LDAPInterface first, then sending an LDAP query if the cache did not contain the entry.
|
||||
"""
|
||||
if uid in self.cached_groups:
|
||||
return self.cached_groups.get(uid), None
|
||||
|
||||
group, err = self.groupQuery.ldap_search(self.connection, uid, self.required_group_attributes)
|
||||
group, err = self.groupQuery.ldap_search(
|
||||
self.connection, uid, self.required_group_attributes
|
||||
)
|
||||
if err:
|
||||
return None, err
|
||||
self.cached_groups[uid] = group
|
||||
@@ -750,19 +836,19 @@ class OpenshiftLDAP_AugmentedADInterface(OpenshiftLDAP_ADInterface):
|
||||
|
||||
|
||||
class OpenshiftLDAPAugmentedActiveDirectory(OpenshiftLDAPRFC2307):
|
||||
|
||||
def __init__(self, config, ldap_connection):
|
||||
|
||||
self.config = config
|
||||
self.ldap_interface = self.create_ldap_interface(ldap_connection)
|
||||
|
||||
def create_ldap_interface(self, connection):
|
||||
segment = self.config.get("augmentedActiveDirectory")
|
||||
user_base_query = openshift_ldap_build_base_query(segment['usersQuery'])
|
||||
groups_base_qry = openshift_ldap_build_base_query(segment['groupsQuery'])
|
||||
user_base_query = openshift_ldap_build_base_query(segment["usersQuery"])
|
||||
groups_base_qry = openshift_ldap_build_base_query(segment["groupsQuery"])
|
||||
|
||||
user_query = OpenshiftLDAPQuery(user_base_query)
|
||||
groups_query = OpenshiftLDAPQueryOnAttribute(groups_base_qry, segment['groupUIDAttribute'])
|
||||
groups_query = OpenshiftLDAPQueryOnAttribute(
|
||||
groups_base_qry, segment["groupUIDAttribute"]
|
||||
)
|
||||
|
||||
return OpenshiftLDAP_AugmentedADInterface(
|
||||
connection=connection,
|
||||
@@ -770,7 +856,7 @@ class OpenshiftLDAPAugmentedActiveDirectory(OpenshiftLDAPRFC2307):
|
||||
group_member_attr=segment["groupMembershipAttributes"],
|
||||
user_name_attr=segment["userNameAttributes"],
|
||||
group_qry=groups_query,
|
||||
group_name_attr=segment["groupNameAttributes"]
|
||||
group_name_attr=segment["groupNameAttributes"],
|
||||
)
|
||||
|
||||
def is_ldapgroup_exists(self, uid):
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
|
||||
from ansible_collections.community.okd.plugins.module_utils.openshift_common import AnsibleOpenshiftModule
|
||||
from ansible_collections.community.okd.plugins.module_utils.openshift_common import (
|
||||
AnsibleOpenshiftModule,
|
||||
)
|
||||
|
||||
try:
|
||||
from kubernetes.dynamic.exceptions import DynamicApiError
|
||||
@@ -124,7 +125,6 @@ class OpenShiftProcess(AnsibleOpenshiftModule):
|
||||
self.exit_json(**result)
|
||||
|
||||
def create_resources(self, definitions):
|
||||
|
||||
params = {"namespace": self.params.get("namespace_target")}
|
||||
|
||||
self.params["apply"] = False
|
||||
@@ -139,9 +139,7 @@ class OpenShiftProcess(AnsibleOpenshiftModule):
|
||||
continue
|
||||
kind = definition.get("kind")
|
||||
if kind and kind.endswith("List"):
|
||||
flattened_definitions.extend(
|
||||
self.flatten_list_kind(definition, params)
|
||||
)
|
||||
flattened_definitions.extend(self.flatten_list_kind(definition, params))
|
||||
else:
|
||||
flattened_definitions.append(self.merge_params(definition, params))
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
import traceback
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from ansible_collections.community.okd.plugins.module_utils.openshift_common import AnsibleOpenshiftModule
|
||||
from ansible_collections.community.okd.plugins.module_utils.openshift_common import (
|
||||
AnsibleOpenshiftModule,
|
||||
)
|
||||
|
||||
from ansible_collections.community.okd.plugins.module_utils.openshift_docker_image import (
|
||||
parse_docker_image_ref,
|
||||
@@ -15,6 +18,7 @@ from ansible_collections.community.okd.plugins.module_utils.openshift_docker_ima
|
||||
try:
|
||||
from requests import request
|
||||
from requests.auth import HTTPBasicAuth
|
||||
|
||||
HAS_REQUESTS_MODULE = True
|
||||
requests_import_exception = None
|
||||
except ImportError as e:
|
||||
@@ -32,11 +36,7 @@ class OpenShiftRegistry(AnsibleOpenshiftModule):
|
||||
kind = "ImageStream"
|
||||
api_version = "image.openshift.io/v1"
|
||||
|
||||
params = dict(
|
||||
kind=kind,
|
||||
api_version=api_version,
|
||||
namespace=namespace
|
||||
)
|
||||
params = dict(kind=kind, api_version=api_version, namespace=namespace)
|
||||
result = self.kubernetes_facts(**params)
|
||||
imagestream = []
|
||||
if len(result["resources"]) > 0:
|
||||
@@ -44,7 +44,6 @@ class OpenShiftRegistry(AnsibleOpenshiftModule):
|
||||
return imagestream
|
||||
|
||||
def find_registry_info(self):
|
||||
|
||||
def _determine_registry(image_stream):
|
||||
public, internal = None, None
|
||||
docker_repo = image_stream["status"].get("publicDockerImageRepository")
|
||||
@@ -72,39 +71,46 @@ class OpenShiftRegistry(AnsibleOpenshiftModule):
|
||||
self.fail_json(msg="The integrated registry has not been configured")
|
||||
return internal, public
|
||||
|
||||
self.fail_json(msg="No Image Streams could be located to retrieve registry info.")
|
||||
self.fail_json(
|
||||
msg="No Image Streams could be located to retrieve registry info."
|
||||
)
|
||||
|
||||
def execute_module(self):
|
||||
result = {}
|
||||
result["internal_hostname"], result["public_hostname"] = self.find_registry_info()
|
||||
(
|
||||
result["internal_hostname"],
|
||||
result["public_hostname"],
|
||||
) = self.find_registry_info()
|
||||
|
||||
if self.check:
|
||||
public_registry = result["public_hostname"]
|
||||
if not public_registry:
|
||||
result["check"] = dict(
|
||||
reached=False,
|
||||
msg="Registry does not have a public hostname."
|
||||
reached=False, msg="Registry does not have a public hostname."
|
||||
)
|
||||
else:
|
||||
headers = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
params = {
|
||||
'method': 'GET',
|
||||
'verify': False
|
||||
}
|
||||
headers = {"Content-Type": "application/json"}
|
||||
params = {"method": "GET", "verify": False}
|
||||
if self.client.configuration.api_key:
|
||||
headers.update(self.client.configuration.api_key)
|
||||
elif self.client.configuration.username and self.client.configuration.password:
|
||||
elif (
|
||||
self.client.configuration.username
|
||||
and self.client.configuration.password
|
||||
):
|
||||
if not HAS_REQUESTS_MODULE:
|
||||
result["check"] = dict(
|
||||
reached=False,
|
||||
msg="The requests python package is missing, try `pip install requests`",
|
||||
error=requests_import_exception
|
||||
error=requests_import_exception,
|
||||
)
|
||||
self.exit_json(**result)
|
||||
params.update(
|
||||
dict(auth=HTTPBasicAuth(self.client.configuration.username, self.client.configuration.password))
|
||||
dict(
|
||||
auth=HTTPBasicAuth(
|
||||
self.client.configuration.username,
|
||||
self.client.configuration.password,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# verify ssl
|
||||
@@ -112,23 +118,20 @@ class OpenShiftRegistry(AnsibleOpenshiftModule):
|
||||
if len(host.scheme) == 0:
|
||||
registry_url = "https://" + public_registry
|
||||
|
||||
if registry_url.startswith("https://") and self.client.configuration.ssl_ca_cert:
|
||||
params.update(
|
||||
dict(verify=self.client.configuration.ssl_ca_cert)
|
||||
)
|
||||
params.update(
|
||||
dict(headers=headers)
|
||||
)
|
||||
if (
|
||||
registry_url.startswith("https://")
|
||||
and self.client.configuration.ssl_ca_cert
|
||||
):
|
||||
params.update(dict(verify=self.client.configuration.ssl_ca_cert))
|
||||
params.update(dict(headers=headers))
|
||||
last_bad_status, last_bad_reason = None, None
|
||||
for path in ("/", "/healthz"):
|
||||
params.update(
|
||||
dict(url=registry_url + path)
|
||||
)
|
||||
params.update(dict(url=registry_url + path))
|
||||
response = request(**params)
|
||||
if response.status_code == 200:
|
||||
result["check"] = dict(
|
||||
reached=True,
|
||||
msg="The local client can contact the integrated registry."
|
||||
msg="The local client can contact the integrated registry.",
|
||||
)
|
||||
self.exit_json(**result)
|
||||
last_bad_reason = response.reason
|
||||
@@ -136,9 +139,8 @@ class OpenShiftRegistry(AnsibleOpenshiftModule):
|
||||
|
||||
result["check"] = dict(
|
||||
reached=False,
|
||||
msg="Unable to contact the integrated registry using local client. Status=%d, Reason=%s" % (
|
||||
last_bad_status, last_bad_reason
|
||||
)
|
||||
msg="Unable to contact the integrated registry using local client. Status=%d, Reason=%s"
|
||||
% (last_bad_status, last_bad_reason),
|
||||
)
|
||||
|
||||
self.exit_json(**result)
|
||||
|
||||
Reference in New Issue
Block a user