Update CI - Continue work from #195 (#202)

* 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:
Bikouo Aubin
2023-11-15 18:00:38 +01:00
committed by GitHub
parent cb796e1298
commit a63e5b7b36
76 changed files with 4364 additions and 3510 deletions

View File

@@ -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