Issue test certificate via lifecyle

Issue a test certificate using the application lifecycle in order to
assert cert-manager readiness. This allows dependent applications to
promptly rely on cert-manager once it is applied.

This is a re-implementation of the original code, which was removed with
the 21-k8s-app-upgrade.sh script, in order to allow apps to be updated
directly via the Application Framework using the new dependency feature:
https://review.opendev.org/c/starlingx/update/+/953526

Tox sysinv import was fixed for the application plugins as part of this
change.

Test plan:
PASS: build-pkgs && build-image
PASS: upload/apply/update/remove/delete cert-manager
PASS: Platform fresh install
PASS: Apply ptp-notification-app
      Platform upgrade from stx 10

Depends-on: https://review.opendev.org/c/starlingx/config/+/956724

Closes-bug: 2119681

Change-Id: Ic37e97ee47c08eabcb1afde5c2a820bf68196326
Signed-off-by: Igor Soares <Igor.PiresSoares@windriver.com>
This commit is contained in:
Igor Soares
2025-07-31 15:03:33 -03:00
parent be83921100
commit 8fb39439e9
2 changed files with 128 additions and 2 deletions

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2022 Wind River Systems, Inc.
# Copyright (c) 2022,2025 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@@ -12,18 +12,63 @@
# pylint: disable=no-member
# pylint: disable=no-name-in-module
import os
import time
from oslo_log import log as logging
from sysinv.common import constants
from sysinv.common import exception
from sysinv.common import kubernetes
from sysinv.helm import lifecycle_base as base
from sysinv.helm import lifecycle_utils as lifecycle_utils
from sysinv.helm.lifecycle_constants import LifecycleConstants
LOG = logging.getLogger(__name__)
NAMESPACE = "cert-manager"
CERT_MANAGER_GROUP = 'cert-manager.io'
CERT_MANAGER_VERSION = 'v1'
CERT_NAME = "stx-test-cm"
CERT_SECRET_NAME = "stx-test-cm"
ISSUER_NAME = "system-local-ca"
ISSUER_PLURAL = "issuers"
PLURAL_NAME_CERT = 'certificates'
TEST_CERT_RETRIES = 60
ISSUER = {
'apiVersion': f'{CERT_MANAGER_GROUP}/{CERT_MANAGER_VERSION}',
'kind': 'Issuer',
'metadata': {
'creationTimestamp': None,
'name': ISSUER_NAME,
'namespace': NAMESPACE
},
'spec': {
'ca': {
'secretName': ISSUER_NAME
}
},
'status': {}
}
CERT = {
'apiVersion': 'cert-manager.io/v1',
'kind': 'Certificate',
'metadata': {
'creationTimestamp': None,
'name': CERT_NAME,
'namespace': NAMESPACE
},
'spec': {
'commonName': CERT_NAME,
'issuerRef': {
'kind': 'Issuer',
'name': ISSUER_NAME,
'namespace': NAMESPACE
},
'secretName': CERT_SECRET_NAME,
}
}
class CertManagerAppLifecycleOperator(base.AppLifecycleOperator):
def app_lifecycle_actions(self, context, conductor_obj, app_op, app, hook_info):
""" Perform lifecycle actions for an operation
@@ -61,6 +106,13 @@ class CertManagerAppLifecycleOperator(base.AppLifecycleOperator):
hook_info.relative_timing == LifecycleConstants.APP_LIFECYCLE_TIMING_POST:
return lifecycle_utils.delete_local_registry_secrets(app_op, app, hook_info)
# FluxCD request
elif hook_info.lifecycle_type == LifecycleConstants.APP_LIFECYCLE_TYPE_FLUXCD_REQUEST:
if hook_info.operation == constants.APP_APPLY_OP and \
hook_info.relative_timing == LifecycleConstants.APP_LIFECYCLE_TIMING_STATUS \
and not os.path.isfile(constants.ANSIBLE_BOOTSTRAP_FLAG):
return self.issue_test_cert()
# Use the default behaviour for other hooks
super(CertManagerAppLifecycleOperator, self).app_lifecycle_actions(
context, conductor_obj, app_op, app, hook_info)
@@ -75,3 +127,77 @@ class CertManagerAppLifecycleOperator(base.AppLifecycleOperator):
if os.path.isfile(constants.ANSIBLE_BOOTSTRAP_FLAG):
raise exception.LifecycleSemanticCheckException(
"Auto-apply disabled during bootstrap.")
def issue_test_cert(self):
""" Issue a test certificate to ensure cert-manager is functional """
LOG.info("Issue test certificate to assert cert-manager readiness.")
kubernetes_operator = kubernetes.KubeOperator()
# Delete any pre-existing test certificate
try:
kubernetes_operator.delete_custom_resource(CERT_MANAGER_GROUP,
CERT_MANAGER_VERSION,
NAMESPACE,
PLURAL_NAME_CERT,
CERT_NAME)
except Exception as e:
LOG.warning(f"Unable to remove existing test certificate: {e}")
apply_failed = True
secret_failed = True
for _ in range(1, TEST_CERT_RETRIES + 1):
if apply_failed:
LOG.info("Applying Issuer...")
try:
kubernetes_operator.apply_custom_resource(CERT_MANAGER_GROUP,
CERT_MANAGER_VERSION,
NAMESPACE,
ISSUER_PLURAL,
ISSUER_NAME,
ISSUER)
except Exception as e:
LOG.warning(f"Error applying certificate issuer: {e}. Retrying.")
time.sleep(3)
continue
LOG.info("Applying test certificate CRD...")
try:
kubernetes_operator.apply_custom_resource(CERT_MANAGER_GROUP,
CERT_MANAGER_VERSION,
NAMESPACE,
PLURAL_NAME_CERT,
CERT_NAME,
CERT)
except Exception as e:
LOG.warning(f"Error applying certificate CRD: {e}. Retrying.")
time.sleep(3)
continue
apply_failed = False
LOG.info("Waiting cert-manager to issue the certificate...")
time.sleep(3)
try:
result = kubernetes_operator.kube_get_secret(CERT_SECRET_NAME, NAMESPACE)
if result:
LOG.info("cert-manager is ready to issue certificates.")
secret_failed = False
break
except Exception as e:
LOG.warning(f"Unable to retrieve secret: {e}")
# Cleanup
try:
kubernetes_operator.delete_custom_resource(CERT_MANAGER_GROUP,
CERT_MANAGER_VERSION,
NAMESPACE,
PLURAL_NAME_CERT,
CERT_NAME)
except Exception as e:
LOG.warning(f"Unable to remove existing test certificate: {e}")
if secret_failed:
raise exception.SysinvException("Cert-manager is not ready after the allotted time. "
"Check the pod logs.")

View File

@@ -39,7 +39,7 @@ setenv = VIRTUAL_ENV={envdir}
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
-e{[tox]stxdir}/config/sysinv/sysinv/sysinv
{[tox]stxdir}/config/sysinv/sysinv/sysinv
-e{[tox]stxdir}/config/tsconfig/tsconfig
-e{[tox]stxdir}/fault/fm-api/source
-e{[tox]stxdir}/fault/python-fmclient/fmclient