mirror of
https://github.com/openshift/community.okd.git
synced 2026-05-07 21:52:37 +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
|
||||
|
||||
Reference in New Issue
Block a user