Merge "App Fmwk: Add support for FluxCDKustomizeOperator"
This commit is contained in:
commit
bdd96b4ce5
|
@ -74,6 +74,9 @@ systemconfig.puppet_plugins =
|
|||
systemconfig.armada.manifest_ops =
|
||||
generic = sysinv.helm.manifest_generic:GenericArmadaManifestOperator
|
||||
|
||||
systemconfig.fluxcd.kustomize_ops =
|
||||
generic = sysinv.helm.kustomize_generic:GenericFluxCDKustomizeOperator
|
||||
|
||||
systemconfig.app_lifecycle =
|
||||
generic = sysinv.helm.lifecycle_generic:GenericAppLifecycleOperator
|
||||
|
||||
|
|
|
@ -167,7 +167,7 @@ class KubeAppController(rest.RestController):
|
|||
try:
|
||||
name, version, patches = app_helper._verify_metadata_file(
|
||||
app_path, app_name, app_version)
|
||||
mname, manifest = app_helper._find_manifest(app_path)
|
||||
mname, manifest = app_helper._find_manifest(app_path, name)
|
||||
app_helper._extract_helm_charts(app_path)
|
||||
LOG.info("Tar file of application %s verified." % name)
|
||||
return name, version, mname, manifest
|
||||
|
@ -669,31 +669,51 @@ class KubeAppHelper(object):
|
|||
raise exception.IncompatibleKubeVersion(
|
||||
name=app_name, version=app_version, kube_version=kube_version)
|
||||
|
||||
def _find_manifest(self, app_path):
|
||||
mdir = cutils.find_manifest_directory(app_path)
|
||||
def _find_manifest(self, app_path, app_name):
|
||||
""" Find the required application manifest elements
|
||||
|
||||
if mdir:
|
||||
return mdir
|
||||
Check for an Armada manifest or a FluxCD manifest directory
|
||||
"""
|
||||
try:
|
||||
# Check for the presence of a FluxCD manifest directory
|
||||
mfile = self._find_fluxcd_manifest(app_path, app_name)
|
||||
except exception.SysinvException as fluxcd_e:
|
||||
try:
|
||||
# Check for the presence of an Armada manifest
|
||||
mfile = self._find_armada_manifest(app_path)
|
||||
except exception.SysinvException as armada_e:
|
||||
raise exception.SysinvException(_(
|
||||
"Application-upload rejected: {} and {} ".format(
|
||||
fluxcd_e, armada_e)))
|
||||
return mfile
|
||||
|
||||
def _find_fluxcd_manifest(self, app_path, app_name):
|
||||
mfiles = cutils.find_fluxcd_manifests_directory(app_path, app_name)
|
||||
if mfiles:
|
||||
return mfiles[0]
|
||||
|
||||
raise exception.SysinvException(_(
|
||||
"FluxCD manifest structure is not present"))
|
||||
|
||||
def _find_armada_manifest(self, app_path):
|
||||
# It is expected that there is only one manifest file
|
||||
# per application and the file exists at top level of
|
||||
# the application path.
|
||||
mfiles = cutils.find_manifest_file(app_path)
|
||||
mfiles = cutils.find_armada_manifest_file(app_path)
|
||||
|
||||
if mfiles is None:
|
||||
raise exception.SysinvException(_(
|
||||
"manifest file is corrupted."))
|
||||
"Armada manifest file is corrupted."))
|
||||
|
||||
if mfiles:
|
||||
if len(mfiles) == 1:
|
||||
return mfiles[0]
|
||||
else:
|
||||
raise exception.SysinvException(_(
|
||||
"Application-upload rejected: tar file contains more "
|
||||
"than one manifest file."))
|
||||
"tar file contains more than one Armada manifest file."))
|
||||
|
||||
raise exception.SysinvException(_(
|
||||
"Application-upload rejected: manifest file/directory is missing."))
|
||||
"Armada manifest file/directory is missing"))
|
||||
|
||||
def _verify_metadata_file(self, app_path, app_name, app_version,
|
||||
upgrade_from_release=None):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (c) 2021 Wind River Systems, Inc.
|
||||
# Copyright (c) 2021-2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
@ -10,6 +10,7 @@ System Inventory Helm Utility.
|
|||
"""
|
||||
|
||||
import sys
|
||||
import urllib3
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
|
@ -26,13 +27,13 @@ CONF = cfg.CONF
|
|||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
def create_app_overrides_action(path, app_name=None, namespace=None):
|
||||
def create_fluxcd_app_overrides_action(path, app_name=None, namespace=None):
|
||||
dbapi = api.get_instance()
|
||||
|
||||
try:
|
||||
db_app = dbapi.kube_app_get(app_name)
|
||||
except exception.KubeAppNotFound:
|
||||
LOG.info("Application %s not found" % app_name)
|
||||
LOG.info("FluxCD Application %s not found" % app_name)
|
||||
return
|
||||
|
||||
helm_operator = helm.HelmOperator(dbapi=dbapi)
|
||||
|
@ -45,11 +46,14 @@ def create_app_overrides_action(path, app_name=None, namespace=None):
|
|||
if db_app.status == constants.APP_UPLOAD_SUCCESS:
|
||||
app_operator.activate_app_plugins(db_app)
|
||||
helm_operator.generate_helm_application_overrides(
|
||||
path, app_name, mode=None, cnamespace=namespace)
|
||||
path, app_name, mode=None, cnamespace=namespace,
|
||||
armada_format=False, chart_info=None, combined=False,
|
||||
is_fluxcd_app=True)
|
||||
app_operator.deactivate_app_plugins(db_app)
|
||||
else:
|
||||
helm_operator.generate_helm_application_overrides(
|
||||
path, app_name, mode=None, cnamespace=namespace)
|
||||
path, app_name, mode=None, cnamespace=namespace,
|
||||
is_fluxcd_app=True)
|
||||
|
||||
|
||||
def create_armada_app_overrides_action(path, app_name=None, namespace=None):
|
||||
|
@ -72,17 +76,18 @@ def create_armada_app_overrides_action(path, app_name=None, namespace=None):
|
|||
app_operator.activate_app_plugins(db_app)
|
||||
helm_operator.generate_helm_application_overrides(
|
||||
path, app_name, mode=None, cnamespace=namespace,
|
||||
armada_format=True, armada_chart_info=None, combined=False)
|
||||
armada_format=False, chart_info=None, combined=False,
|
||||
is_fluxcd_app=False)
|
||||
app_operator.deactivate_app_plugins(db_app)
|
||||
else:
|
||||
helm_operator.generate_helm_application_overrides(
|
||||
path, app_name, mode=None, cnamespace=namespace,
|
||||
armada_format=True, armada_chart_info=None, combined=False)
|
||||
is_fluxcd_app=False)
|
||||
|
||||
|
||||
def add_action_parsers(subparsers):
|
||||
parser = subparsers.add_parser('create-app-overrides')
|
||||
parser.set_defaults(func=create_app_overrides_action)
|
||||
parser = subparsers.add_parser('create-fluxcd-app-overrides')
|
||||
parser.set_defaults(func=create_fluxcd_app_overrides_action)
|
||||
parser.add_argument('path', nargs='?')
|
||||
parser.add_argument('app_name', nargs='?')
|
||||
parser.add_argument('namespace', nargs='?')
|
||||
|
@ -102,21 +107,23 @@ CONF.register_cli_opt(
|
|||
|
||||
|
||||
def main():
|
||||
urllib3.disable_warnings()
|
||||
|
||||
service.prepare_service(sys.argv)
|
||||
if CONF.action.name == 'create-app-overrides':
|
||||
if CONF.action.name == 'create-fluxcd-app-overrides':
|
||||
if not CONF.action.path:
|
||||
LOG.error("overrides path is required")
|
||||
LOG.error("A path is required to save overrides")
|
||||
elif not CONF.action.app_name:
|
||||
LOG.error("application name is required")
|
||||
LOG.error("FluxCD application name is required")
|
||||
else:
|
||||
CONF.action.func(CONF.action.path,
|
||||
CONF.action.app_name,
|
||||
CONF.action.namespace)
|
||||
elif CONF.action.name == 'create-armada-app-overrides':
|
||||
if not CONF.action.path:
|
||||
LOG.error("overrides path is required")
|
||||
LOG.error("A path is required to save overrides")
|
||||
elif not CONF.action.app_name:
|
||||
LOG.error("application name is required")
|
||||
LOG.error("Armada application name is required")
|
||||
else:
|
||||
CONF.action.func(CONF.action.path,
|
||||
CONF.action.app_name,
|
||||
|
|
|
@ -1650,12 +1650,21 @@ K8S_RBD_PROV_STOR_CLASS_NAME = 'general'
|
|||
# Working paths
|
||||
APP_INSTALL_ROOT_PATH = '/scratch'
|
||||
APP_INSTALL_PATH = APP_INSTALL_ROOT_PATH + '/apps'
|
||||
APP_SYNCED_ARMADA_DATA_PATH = os.path.join(tsc.PLATFORM_PATH, 'armada', tsc.SW_VERSION)
|
||||
APP_METADATA_FILE = 'metadata.yaml'
|
||||
APP_PENDING_REAPPLY_FLAG = os.path.join(
|
||||
tsc.HELM_OVERRIDES_PATH, ".app_reapply")
|
||||
|
||||
# Armada
|
||||
APP_SYNCED_ARMADA_DATA_PATH = os.path.join(tsc.PLATFORM_PATH, 'armada', tsc.SW_VERSION)
|
||||
|
||||
# FluxCD
|
||||
APP_FLUXCD_MANIFEST_DIR = 'fluxcd-manifests'
|
||||
APP_FLUXCD_DATA_PATH = os.path.join(tsc.PLATFORM_PATH, 'fluxcd', tsc.SW_VERSION)
|
||||
APP_ROOT_KUSTOMIZE_FILE = 'kustomization.yaml'
|
||||
APP_RELEASE_CLEANUP_FILE = 'helmrelease_cleanup.yaml'
|
||||
FLUXCD_CRD_HELM_REL_GROUP = 'helm.toolkit.fluxcd.io'
|
||||
FLUXCD_CRD_HELM_REL_VERSION = 'v2beta1'
|
||||
FLUXCD_CRD_HELM_REL_PLURAL = 'helmreleases'
|
||||
|
||||
# State constants
|
||||
APP_NOT_PRESENT = 'missing'
|
||||
|
|
|
@ -634,7 +634,7 @@ class KubeOperator(object):
|
|||
custom_resource_api = self._get_kubernetesclient_custom_objects()
|
||||
|
||||
try:
|
||||
cert = custom_resource_api.get_namespaced_custom_object(
|
||||
cr_obj = custom_resource_api.get_namespaced_custom_object(
|
||||
group,
|
||||
version,
|
||||
namespace,
|
||||
|
@ -647,14 +647,14 @@ class KubeOperator(object):
|
|||
LOG.error("Fail to access %s:%s. %s" % (namespace, name, e))
|
||||
raise
|
||||
else:
|
||||
return cert
|
||||
return cr_obj
|
||||
|
||||
def apply_custom_resource(self, group, version, namespace, plural, name, body):
|
||||
custom_resource_api = self._get_kubernetesclient_custom_objects()
|
||||
|
||||
# if resource already exists we apply just a patch
|
||||
cert = self.get_custom_resource(group, version, namespace, plural, name)
|
||||
if cert:
|
||||
cr_obj = self.get_custom_resource(group, version, namespace, plural, name)
|
||||
if cr_obj:
|
||||
custom_resource_api.patch_namespaced_custom_object(group,
|
||||
version,
|
||||
namespace,
|
||||
|
@ -677,11 +677,12 @@ class KubeOperator(object):
|
|||
plural, name, body)
|
||||
except ApiException as ex:
|
||||
if ex.reason == "Not Found":
|
||||
LOG.warn("Failed to delete custom object, Namespace %s: %s"
|
||||
% (namespace, str(ex.body).replace('\n', ' ')))
|
||||
LOG.warn("Failed to delete custom resource object, Namespace "
|
||||
"%s: %s" % (namespace,
|
||||
str(ex.body).replace('\n', ' ')))
|
||||
pass
|
||||
except Exception as e:
|
||||
LOG.error("Failed to delete custom object, Namespace %s: %s"
|
||||
LOG.error("Failed to delete custom resource object, Namespace %s: %s"
|
||||
% (namespace, e))
|
||||
raise
|
||||
|
||||
|
|
|
@ -2453,9 +2453,9 @@ def find_metadata_file(path, metadata_file, upgrade_from_release=None):
|
|||
return app_name, app_version, patches
|
||||
|
||||
|
||||
def find_manifest_file(path):
|
||||
""" Find all manifest files in a given directory. """
|
||||
def _is_manifest(yaml_file):
|
||||
def find_armada_manifest_file(path):
|
||||
""" Find all Armada manifest files in a given directory. """
|
||||
def _is_armada_manifest(yaml_file):
|
||||
with io.open(yaml_file, 'r', encoding='utf-8') as f:
|
||||
docs = yaml.load_all(f)
|
||||
for doc in docs:
|
||||
|
@ -2473,7 +2473,7 @@ def find_manifest_file(path):
|
|||
if file.endswith('.yaml'):
|
||||
yaml_file = os.path.join(path, file)
|
||||
try:
|
||||
mname, mfile = _is_manifest(yaml_file)
|
||||
mname, mfile = _is_armada_manifest(yaml_file)
|
||||
if mfile:
|
||||
mfiles.append((mname, mfile))
|
||||
except Exception as e:
|
||||
|
@ -2484,21 +2484,25 @@ def find_manifest_file(path):
|
|||
return mfiles
|
||||
|
||||
|
||||
def find_manifest_directory(path):
|
||||
"""For FluxCD apps we expect to have 1 manifest directory
|
||||
that has the name of constants.APP_FLUXCD_MANIFEST_DIR
|
||||
and we validate that and its structure"""
|
||||
def _is_manifest_dir(path):
|
||||
"""check if the directory has the desired FluxCD app structure"""
|
||||
mandatory_components = ("base", "kustomization.yaml")
|
||||
def find_fluxcd_manifests_directory(path, name):
|
||||
"""For FluxCD apps we expect to have one top-level manifest directory that
|
||||
contains the name of constants.APP_FLUXCD_MANIFEST_DIR. Validate that it
|
||||
is present and provide some basic validation of its structure.
|
||||
"""
|
||||
def _is_fluxcd_app_compliant(path):
|
||||
"""Check if the directory has the desired FluxCD app structure"""
|
||||
mandatory_components = ("base", constants.APP_ROOT_KUSTOMIZE_FILE)
|
||||
check_mandatory = all(comp in os.listdir(path)
|
||||
for comp in mandatory_components)
|
||||
return check_mandatory
|
||||
|
||||
mfiles = []
|
||||
manifest_dir_abs = os.path.join(path, constants.APP_FLUXCD_MANIFEST_DIR)
|
||||
if os.path.isdir(manifest_dir_abs) and _is_manifest_dir(manifest_dir_abs):
|
||||
return constants.APP_FLUXCD_MANIFEST_DIR, manifest_dir_abs
|
||||
return None
|
||||
if os.path.isdir(manifest_dir_abs) and \
|
||||
_is_fluxcd_app_compliant(manifest_dir_abs):
|
||||
mfiles.append((("{}-{}".format(name, constants.APP_FLUXCD_MANIFEST_DIR)),
|
||||
manifest_dir_abs))
|
||||
return mfiles
|
||||
|
||||
|
||||
def get_http_port(dbapi):
|
||||
|
@ -2638,32 +2642,38 @@ def is_aio_duplex_system(dbapi):
|
|||
|
||||
|
||||
def generate_synced_armada_dir(app_name, app_version):
|
||||
""" Armada application: Top level directory. """
|
||||
return os.path.join(constants.APP_SYNCED_ARMADA_DATA_PATH, app_name, app_version)
|
||||
|
||||
|
||||
def generate_synced_armada_manifest_fqpn(app_name, app_version, manifest_filename):
|
||||
""" Armada application: Armada manifest file. """
|
||||
return os.path.join(
|
||||
constants.APP_SYNCED_ARMADA_DATA_PATH, app_name, app_version,
|
||||
app_name + '-' + manifest_filename)
|
||||
|
||||
|
||||
def generate_synced_metadata_fqpn(app_name, app_version):
|
||||
""" Armada application: Application metadata file. """
|
||||
return os.path.join(
|
||||
constants.APP_SYNCED_ARMADA_DATA_PATH, app_name, app_version,
|
||||
'metadata.yaml')
|
||||
|
||||
|
||||
def generate_synced_fluxcd_dir(app_name, app_version):
|
||||
""" FluxCD application: Top level directory. """
|
||||
return os.path.join(constants.APP_FLUXCD_DATA_PATH, app_name, app_version)
|
||||
|
||||
|
||||
def generate_synced_fluxcd_manifest_fqpn(app_name, app_version, manifest):
|
||||
def generate_synced_fluxcd_manifests_fqpn(app_name, app_version):
|
||||
""" FluxCD application: Top level kustomize manifests directory. """
|
||||
return os.path.join(
|
||||
constants.APP_FLUXCD_DATA_PATH, app_name, app_version,
|
||||
app_name + '-' + manifest)
|
||||
app_name + '-' + constants.APP_FLUXCD_MANIFEST_DIR)
|
||||
|
||||
|
||||
def generate_synced_fluxcd_metadata_fqpn(app_name, app_version):
|
||||
""" FluxCD application: Application metadata file. """
|
||||
return os.path.join(
|
||||
constants.APP_FLUXCD_DATA_PATH, app_name, app_version,
|
||||
'metadata.yaml')
|
||||
|
|
|
@ -519,7 +519,7 @@ class AppOperator(object):
|
|||
if not cutils.verify_checksum(app.inst_path):
|
||||
_handle_extract_failure('checksum validation failed.')
|
||||
|
||||
mname, manifest = self._utils._find_manifest(app.inst_path)
|
||||
mname, manifest = self._utils._find_manifest(app.inst_path, app.name)
|
||||
# Save the official manifest file info. They will be persisted
|
||||
# in the next status update
|
||||
app.regenerate_manifest_filename(mname, os.path.basename(manifest))
|
||||
|
@ -567,7 +567,8 @@ class AppOperator(object):
|
|||
images_file = yaml.safe_load(f)
|
||||
|
||||
helmrepo_path = os.path.join(manifest, "base", "helmrepository.yaml")
|
||||
root_kustomization_path = os.path.join(manifest, "kustomization.yaml")
|
||||
root_kustomization_path = os.path.join(
|
||||
manifest, constants.APP_ROOT_KUSTOMIZE_FILE)
|
||||
for f in (helmrepo_path, root_kustomization_path):
|
||||
if not os.path.isfile(f):
|
||||
raise exception.SysinvException(_(
|
||||
|
@ -629,7 +630,7 @@ class AppOperator(object):
|
|||
not os.path.isfile(static_overrides_path):
|
||||
raise exception.SysinvException(_(
|
||||
"FluxCD app chart static overrides file doesn't exist "
|
||||
"%s" % static_overrides_path))
|
||||
"%s" % chart_name))
|
||||
|
||||
with io.open(static_overrides_path, 'r', encoding='utf-8') as f:
|
||||
static_overrides_file = yaml.safe_load(f) or {}
|
||||
|
@ -791,7 +792,7 @@ class AppOperator(object):
|
|||
LOG.info("Generating application overrides to discover required images.")
|
||||
self._helm.generate_helm_application_overrides(
|
||||
app.sync_overrides_dir, app.name, mode=None, cnamespace=None,
|
||||
armada_format=True, armada_chart_info=app.charts, combined=True,
|
||||
armada_format=True, chart_info=app.charts, combined=True,
|
||||
is_fluxcd_app=app.is_fluxcd_app)
|
||||
self._plugins.deactivate_plugins(app)
|
||||
|
||||
|
@ -1235,7 +1236,8 @@ class AppOperator(object):
|
|||
"""
|
||||
|
||||
helmrepo_path = os.path.join(manifest, "base", "helmrepository.yaml")
|
||||
root_kustomization_path = os.path.join(manifest, "kustomization.yaml")
|
||||
root_kustomization_path = os.path.join(
|
||||
manifest, constants.APP_ROOT_KUSTOMIZE_FILE)
|
||||
for f in (helmrepo_path, root_kustomization_path):
|
||||
if not os.path.isfile(f):
|
||||
raise exception.SysinvException(_(
|
||||
|
@ -1659,9 +1661,12 @@ class AppOperator(object):
|
|||
constants.APP_METADATA_APPLY_PROGRESS_ADJUST,
|
||||
constants.APP_METADATA_APPLY_PROGRESS_ADJUST_DEFAULT_VALUE)
|
||||
|
||||
# Build the list of expected chart releases. Re-read the
|
||||
# kustomization.yaml file as charts may have been enabled/disabled
|
||||
# via the plugins (helm or kustomize operator).
|
||||
charts = {
|
||||
c.metadata_name: {"namespace": c.namespace, "chart_label": c.chart_label}
|
||||
for c in app.charts
|
||||
for c in self._get_list_of_charts(app)
|
||||
}
|
||||
charts_count = len(charts)
|
||||
|
||||
|
@ -1691,8 +1696,10 @@ class AppOperator(object):
|
|||
for release_name, chart_obj in list(charts.items()):
|
||||
# Request the helm release info
|
||||
helm_rel = self._kube.get_custom_resource(
|
||||
"helm.toolkit.fluxcd.io", "v2beta1",
|
||||
chart_obj["namespace"], "helmreleases",
|
||||
constants.FLUXCD_CRD_HELM_REL_GROUP,
|
||||
constants.FLUXCD_CRD_HELM_REL_VERSION,
|
||||
chart_obj["namespace"],
|
||||
constants.FLUXCD_CRD_HELM_REL_PLURAL,
|
||||
release_name)
|
||||
|
||||
if not helm_rel:
|
||||
|
@ -2744,7 +2751,7 @@ class AppOperator(object):
|
|||
LOG.info("Generating application overrides...")
|
||||
self._helm.generate_helm_application_overrides(
|
||||
app.sync_overrides_dir, app.name, mode, cnamespace=None,
|
||||
armada_format=True, armada_chart_info=app.charts, combined=True,
|
||||
armada_format=True, chart_info=app.charts, combined=True,
|
||||
is_fluxcd_app=app.is_fluxcd_app)
|
||||
|
||||
overrides_str = None
|
||||
|
@ -3281,51 +3288,53 @@ class AppOperator(object):
|
|||
self.sync_armada_mfile_dir = cutils.generate_synced_armada_dir(
|
||||
self._kube_app.get('name'),
|
||||
self._kube_app.get('app_version'))
|
||||
self.sync_fluxcd_manifest_dir = cutils.generate_synced_fluxcd_dir(
|
||||
self._kube_app.get('name'),
|
||||
self._kube_app.get('app_version'))
|
||||
|
||||
# Files: DRBD synced between controllers
|
||||
self.sync_armada_mfile = cutils.generate_synced_armada_manifest_fqpn(
|
||||
self._kube_app.get('name'),
|
||||
self._kube_app.get('app_version'),
|
||||
self._kube_app.get('manifest_file'))
|
||||
self.sync_imgfile = generate_synced_images_fqpn(
|
||||
self._kube_app.get('name'),
|
||||
self._kube_app.get('app_version'))
|
||||
self.sync_metadata_file = cutils.generate_synced_metadata_fqpn(
|
||||
self.sync_fluxcd_manifest = cutils.generate_synced_fluxcd_manifests_fqpn(
|
||||
self._kube_app.get('name'),
|
||||
self._kube_app.get('app_version'))
|
||||
|
||||
# Files: FQPN formatted for the docker armada_service
|
||||
self.sync_armada_imgfile = generate_synced_images_fqpn(
|
||||
self._kube_app.get('name'),
|
||||
self._kube_app.get('app_version'))
|
||||
self.sync_fluxcd_imgfile = generate_synced_fluxcd_images_fqpn(
|
||||
self._kube_app.get('name'),
|
||||
self._kube_app.get('app_version'))
|
||||
self.sync_imgfile = self.sync_fluxcd_imgfile \
|
||||
if self.is_fluxcd_app else \
|
||||
self.sync_armada_imgfile
|
||||
|
||||
self.sync_armada_metadata_file = cutils.generate_synced_metadata_fqpn(
|
||||
self._kube_app.get('name'),
|
||||
self._kube_app.get('app_version'))
|
||||
self.sync_fluxcd_metadata_file = cutils.generate_synced_fluxcd_metadata_fqpn(
|
||||
self._kube_app.get('name'),
|
||||
self._kube_app.get('app_version'))
|
||||
self.sync_metadata_file = self.sync_fluxcd_metadata_file \
|
||||
if self.is_fluxcd_app else \
|
||||
self.sync_armada_metadata_file
|
||||
|
||||
# Files: FQPN formatted for the Armada pod
|
||||
self.armada_service_mfile = generate_armada_service_manifest_fqpn(
|
||||
self._kube_app.get('name'),
|
||||
self._kube_app.get('app_version'),
|
||||
self._kube_app.get('manifest_file'))
|
||||
|
||||
# FluxCD variables
|
||||
if self.is_fluxcd_app:
|
||||
self.sync_fluxcd_manifest_dir = cutils.generate_synced_fluxcd_dir(
|
||||
self._kube_app.get('name'),
|
||||
self._kube_app.get('app_version'))
|
||||
self.sync_fluxcd_manifest = cutils.generate_synced_fluxcd_manifest_fqpn(
|
||||
self._kube_app.get('name'),
|
||||
self._kube_app.get('app_version'),
|
||||
self._kube_app.get('manifest_file'))
|
||||
|
||||
# override the common variables
|
||||
self.sync_metadata_file = cutils.generate_synced_fluxcd_metadata_fqpn(
|
||||
self._kube_app.get('name'),
|
||||
self._kube_app.get('app_version'))
|
||||
self.sync_imgfile = generate_synced_fluxcd_images_fqpn(
|
||||
self._kube_app.get('name'),
|
||||
self._kube_app.get('app_version'))
|
||||
|
||||
self.patch_dependencies = []
|
||||
self.charts = []
|
||||
self.releases = []
|
||||
|
||||
@property
|
||||
def is_fluxcd_app(self):
|
||||
return self._kube_app.get('manifest_name') \
|
||||
== constants.APP_FLUXCD_MANIFEST_DIR
|
||||
return self._kube_app.get('manifest_name').endswith(
|
||||
constants.APP_FLUXCD_MANIFEST_DIR)
|
||||
|
||||
@property
|
||||
def system_app(self):
|
||||
|
@ -3390,10 +3399,9 @@ class AppOperator(object):
|
|||
self.inst_mfile = generate_install_manifest_fqpn(
|
||||
self.name, self.version, new_mfile)
|
||||
if self.is_fluxcd_app:
|
||||
self.sync_fluxcd_manifest = cutils.generate_synced_fluxcd_manifest_fqpn(
|
||||
self.sync_fluxcd_manifest = cutils.generate_synced_fluxcd_manifests_fqpn(
|
||||
self.name,
|
||||
self.version,
|
||||
new_mfile)
|
||||
self.version)
|
||||
else:
|
||||
self.armada_service_mfile = generate_armada_service_manifest_fqpn(
|
||||
self.name, self.version, new_mfile)
|
||||
|
@ -4448,6 +4456,11 @@ class FluxCDHelper(object):
|
|||
try:
|
||||
if operation == constants.APP_APPLY_OP:
|
||||
rc = self._apply(manifest_dir)
|
||||
if rc:
|
||||
rc = self._cleanup_disabled_helm_releases(manifest_dir)
|
||||
else:
|
||||
LOG.error("Applying %s failed. Skipping helm release "
|
||||
"cleanup...")
|
||||
elif operation == constants.APP_DELETE_OP:
|
||||
rc = self._delete(manifest_dir)
|
||||
elif operation == constants.APP_ROLLBACK_OP:
|
||||
|
@ -4559,3 +4572,38 @@ class FluxCDHelper(object):
|
|||
return conditions[0]['status'], conditions[0].get('message')
|
||||
else:
|
||||
return self.HELM_RELEASE_STATUS_UNKNOWN, None
|
||||
|
||||
def _cleanup_disabled_helm_releases(self, manifest_dir):
|
||||
helmrelease_cleanup_fqpn = os.path.join(
|
||||
manifest_dir, constants.APP_RELEASE_CLEANUP_FILE)
|
||||
|
||||
# See if we have any helm releases that we must make sure are cleaned up
|
||||
if not os.path.exists(helmrelease_cleanup_fqpn):
|
||||
return True
|
||||
|
||||
with io.open(helmrelease_cleanup_fqpn, 'r', encoding='utf-8') as f:
|
||||
helmrelease_doc = list(yaml.load_all(f,
|
||||
Loader=yaml.RoundTripLoader, preserve_quotes=True))
|
||||
|
||||
for release in helmrelease_doc[0]['releases']:
|
||||
try:
|
||||
if self._kube.get_custom_resource(
|
||||
constants.FLUXCD_CRD_HELM_REL_GROUP,
|
||||
constants.FLUXCD_CRD_HELM_REL_VERSION,
|
||||
release["namespace"],
|
||||
constants.FLUXCD_CRD_HELM_REL_PLURAL,
|
||||
release['name']):
|
||||
|
||||
self._kube.delete_custom_resource(
|
||||
constants.FLUXCD_CRD_HELM_REL_GROUP,
|
||||
constants.FLUXCD_CRD_HELM_REL_VERSION,
|
||||
release["namespace"],
|
||||
constants.FLUXCD_CRD_HELM_REL_PLURAL,
|
||||
release['name'])
|
||||
except Exception as e:
|
||||
LOG.error("Attemting to cleanup HelmRelease {}/{} "
|
||||
"failed".format(release["namespace"],
|
||||
release['name']))
|
||||
LOG.exception(e)
|
||||
return False
|
||||
return True
|
||||
|
|
|
@ -6409,7 +6409,7 @@ class ConductorManager(service.PeriodicService):
|
|||
self._kube_app_helper._verify_metadata_file(
|
||||
app_path, app_name, None)
|
||||
manifest_name, manifest_file = \
|
||||
self._kube_app_helper._find_manifest(app_path)
|
||||
self._kube_app_helper._find_manifest(app_path, app_name)
|
||||
self._kube_app_helper._extract_helm_charts(app_path)
|
||||
except exception.SysinvException as e:
|
||||
LOG.error("Extracting tarfile for %s failed: %s." % (
|
||||
|
@ -13784,7 +13784,7 @@ class ConductorManager(service.PeriodicService):
|
|||
app.charts = self._app._get_list_of_charts(app)
|
||||
self._helm.generate_helm_application_overrides(
|
||||
app.sync_overrides_dir, app.name, app.mode, cnamespace=None,
|
||||
armada_format=True, armada_chart_info=app.charts, combined=True,
|
||||
armada_format=True, chart_info=app.charts, combined=True,
|
||||
is_fluxcd_app=app.is_fluxcd_app)
|
||||
(helm_files, armada_files) = self._app._get_overrides_files(app, None)
|
||||
for f in helm_files + armada_files:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2018 Wind River Systems, Inc.
|
||||
# Copyright (c) 2018-2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
@ -418,6 +418,21 @@ class BaseHelm(object):
|
|||
"""
|
||||
pass
|
||||
|
||||
def execute_kustomize_updates(self, operator):
|
||||
"""
|
||||
Update the elements of FluxCD kustomize manifests.
|
||||
|
||||
This allows a helm chart plugin to use the FluxCDKustomizeOperator to
|
||||
make dynamic structural changes to the application manifest based on the
|
||||
current conditions in the platform
|
||||
|
||||
Changes currenty include updates to the top level kustomize manifest to
|
||||
disable helm releases.
|
||||
|
||||
:param operator: an instance of the FluxCDKustomizeOperator
|
||||
"""
|
||||
pass
|
||||
|
||||
def _is_enabled(self, app_name, chart_name, namespace):
|
||||
"""
|
||||
Check if the chart is enable at an application level
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2018-2021 Wind River Systems, Inc.
|
||||
# Copyright (c) 2018-2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
@ -42,6 +42,12 @@ HELM_PLUGIN_PREFIX_LENGTH = 4
|
|||
# optional suffix, as in PLUGINNAME_###.
|
||||
ARMADA_PLUGIN_SUFFIX_LENGTH = 4
|
||||
|
||||
# Number of optional characters appended to FluxCD kustomize operator name, to
|
||||
# allow overriding with a newer version of the FluxCD kustomize operator. The
|
||||
# convention here is for the FluxCD kustomize operator plugins to allow an
|
||||
# optional suffix, as in PLUGINNAME_###.
|
||||
FLUXCD_PLUGIN_SUFFIX_LENGTH = 4
|
||||
|
||||
# Number of optional characters appended to AppLifecycle operator name,
|
||||
# to allow overriding with a newer version of the AppLifecycle operator.
|
||||
# The convention here is for the AppLifecycle operator plugins to allow an
|
||||
|
@ -78,6 +84,7 @@ class HelmOperator(object):
|
|||
# Define the stevedore namespaces that will need to be managed for plugins
|
||||
STEVEDORE_APPS = 'systemconfig.helm_applications'
|
||||
STEVEDORE_ARMADA = 'systemconfig.armada.manifest_ops'
|
||||
STEVEDORE_FLUXCD = 'systemconfig.fluxcd.kustomize_ops'
|
||||
STEVEDORE_LIFECYCLE = 'systemconfig.app_lifecycle'
|
||||
|
||||
def __init__(self, dbapi=None):
|
||||
|
@ -97,6 +104,7 @@ class HelmOperator(object):
|
|||
self.helm_system_applications = {}
|
||||
self.chart_operators = {}
|
||||
self.armada_manifest_operators = {}
|
||||
self.fluxcd_kustomize_operators = {}
|
||||
self.app_lifecycle_operators = {}
|
||||
|
||||
# Need to purge the stevedore plugin cache so that when we discover the
|
||||
|
@ -110,6 +118,9 @@ class HelmOperator(object):
|
|||
# dict containing Armada manifest operators per app
|
||||
self.armada_manifest_operators = self._load_armada_manifest_operators()
|
||||
|
||||
# dict containing FluxCD kustomize operators per app
|
||||
self.fluxcd_kustomize_operators = self._load_fluxcd_kustomize_operators()
|
||||
|
||||
# dict containing app lifecycle operators per app
|
||||
self.app_lifecycle_operators = self._load_app_lifecycle_operators()
|
||||
|
||||
|
@ -168,6 +179,32 @@ class HelmOperator(object):
|
|||
LOG.info("Couldn't find endpoint distribution located at %s for "
|
||||
"%s" % (install_location, armada_distribution))
|
||||
|
||||
for fluxcd_ep in extension.ExtensionManager.ENTRY_POINT_CACHE[self.STEVEDORE_FLUXCD]:
|
||||
fluxcd_distribution = None
|
||||
|
||||
try:
|
||||
fluxcd_distribution = utils.get_distribution_from_entry_point(fluxcd_ep)
|
||||
(project_name, project_location) = \
|
||||
utils.get_project_name_and_location_from_distribution(fluxcd_distribution)
|
||||
|
||||
if project_location == install_location:
|
||||
extension.ExtensionManager.ENTRY_POINT_CACHE[self.STEVEDORE_FLUXCD].remove(fluxcd_ep)
|
||||
break
|
||||
except exception.SysinvException:
|
||||
# Temporary suppress errors on Debian until Stevedore is reworked.
|
||||
# See https://storyboard.openstack.org/#!/story/2009101
|
||||
if utils.is_debian():
|
||||
LOG.info("Didn't find distribution for {}. Deleting from cache".format(fluxcd_ep))
|
||||
try:
|
||||
extension.ExtensionManager.ENTRY_POINT_CACHE[self.STEVEDORE_FLUXCD].remove(fluxcd_ep)
|
||||
except Exception as e:
|
||||
LOG.info("Tried removing fluxcd_ep {}, error: {}".format(fluxcd_ep, e))
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
LOG.info("Couldn't find endpoint distribution located at %s for "
|
||||
"%s" % (install_location, fluxcd_distribution))
|
||||
|
||||
for app_ep in extension.ExtensionManager.ENTRY_POINT_CACHE[self.STEVEDORE_APPS]:
|
||||
try:
|
||||
app_distribution = utils.get_distribution_from_entry_point(app_ep)
|
||||
|
@ -227,6 +264,12 @@ class HelmOperator(object):
|
|||
except KeyError:
|
||||
LOG.info("No entry points for %s found." % self.STEVEDORE_ARMADA)
|
||||
|
||||
try:
|
||||
del extension.ExtensionManager.ENTRY_POINT_CACHE[self.STEVEDORE_FLUXCD]
|
||||
LOG.debug("Deleted entry points for %s." % self.STEVEDORE_FLUXCD)
|
||||
except KeyError:
|
||||
LOG.info("No entry points for %s found." % self.STEVEDORE_FLUXCD)
|
||||
|
||||
try:
|
||||
del extension.ExtensionManager.ENTRY_POINT_CACHE[self.STEVEDORE_LIFECYCLE]
|
||||
LOG.debug("Deleted entry points for %s." % self.STEVEDORE_LIFECYCLE)
|
||||
|
@ -304,6 +347,44 @@ class HelmOperator(object):
|
|||
|
||||
return operators_dict
|
||||
|
||||
def _load_fluxcd_kustomize_operators(self):
|
||||
"""Build a dictionary of FluxCD kustomize operators"""
|
||||
|
||||
operators_dict = {}
|
||||
dist_info_dict = {}
|
||||
|
||||
fluxcd_kustomize_operators = extension.ExtensionManager(
|
||||
namespace=self.STEVEDORE_FLUXCD,
|
||||
invoke_on_load=True, invoke_args=())
|
||||
|
||||
sorted_fluxcd_kustomize_operators = sorted(
|
||||
fluxcd_kustomize_operators.extensions, key=lambda x: x.name)
|
||||
|
||||
for op in sorted_fluxcd_kustomize_operators:
|
||||
if (op.name[-(FLUXCD_PLUGIN_SUFFIX_LENGTH - 1):].isdigit() and
|
||||
op.name[-FLUXCD_PLUGIN_SUFFIX_LENGTH:-3] == '_'):
|
||||
op_name = op.name[0:-FLUXCD_PLUGIN_SUFFIX_LENGTH]
|
||||
else:
|
||||
op_name = op.name
|
||||
operators_dict[op_name] = op.obj
|
||||
|
||||
distribution = utils.get_distribution_from_entry_point(op.entry_point)
|
||||
(project_name, project_location) = \
|
||||
utils.get_project_name_and_location_from_distribution(distribution)
|
||||
|
||||
# Extract distribution information for logging
|
||||
dist_info_dict[op_name] = {
|
||||
'name': project_name,
|
||||
'location': project_location,
|
||||
}
|
||||
|
||||
# Provide some log feedback on plugins being used
|
||||
for (app_name, info) in iteritems(dist_info_dict):
|
||||
LOG.info("Plugins for %-20s: loaded from %-20s - %s." % (app_name,
|
||||
info['name'], info['location']))
|
||||
|
||||
return operators_dict
|
||||
|
||||
def get_armada_manifest_operator(self, app_name):
|
||||
"""Return a manifest operator based on app name"""
|
||||
|
||||
|
@ -314,6 +395,16 @@ class HelmOperator(object):
|
|||
manifest_op = self.armada_manifest_operators['generic']
|
||||
return manifest_op
|
||||
|
||||
def get_fluxcd_kustomize_operator(self, app_name):
|
||||
"""Return a kustomize operator based on app name"""
|
||||
|
||||
plugin_name = utils.find_app_plugin_name(app_name)
|
||||
if plugin_name in self.fluxcd_kustomize_operators:
|
||||
kustomize_op = self.fluxcd_kustomize_operators[plugin_name]
|
||||
else:
|
||||
kustomize_op = self.fluxcd_kustomize_operators['generic']
|
||||
return kustomize_op
|
||||
|
||||
def _load_helm_applications(self):
|
||||
"""Build a dictionary of supported helm applications"""
|
||||
|
||||
|
@ -725,7 +816,7 @@ class HelmOperator(object):
|
|||
mode=None,
|
||||
cnamespace=None,
|
||||
armada_format=False,
|
||||
armada_chart_info=None,
|
||||
chart_info=None,
|
||||
combined=False,
|
||||
is_fluxcd_app=False):
|
||||
"""Create the system overrides files for a supported application
|
||||
|
@ -741,28 +832,59 @@ class HelmOperator(object):
|
|||
:param cnamespace: (optional) namespace
|
||||
:param armada_format: (optional) whether to emit in armada format
|
||||
instead of helm format (with extra header)
|
||||
:param armada_chart_info: (optional) supporting chart information
|
||||
:param chart_info: (optional) supporting chart information
|
||||
extracted from the armada manifest which is used to influence
|
||||
overrides
|
||||
:param combined: (optional) whether to apply user overrides on top of
|
||||
system overrides
|
||||
:param is_fluxcd_app: whether the app is fluxcd or not
|
||||
"""
|
||||
if is_fluxcd_app:
|
||||
self._generate_helm_application_overrides_fluxcd(
|
||||
path, app_name, mode, cnamespace,
|
||||
chart_info, combined)
|
||||
else:
|
||||
self._generate_helm_application_overrides_armada(
|
||||
path, app_name, mode, cnamespace, armada_format,
|
||||
chart_info, combined)
|
||||
|
||||
@helm_context
|
||||
def _generate_helm_application_overrides_armada(self, path, app_name,
|
||||
mode=None,
|
||||
cnamespace=None,
|
||||
armada_format=False,
|
||||
chart_info=None,
|
||||
combined=False):
|
||||
"""Create the system overrides files for a supported application
|
||||
|
||||
This method will generate system helm chart overrides yaml files for a
|
||||
set of supported charts that comprise an application. If the namespace
|
||||
is provided only the overrides files for that specified namespace will
|
||||
be written.
|
||||
|
||||
:param app_name: name of the bundle of charts required to support an
|
||||
application
|
||||
:param mode: mode to control how to apply application manifest
|
||||
:param cnamespace: (optional) namespace
|
||||
:param armada_format: (optional) whether to emit in armada format
|
||||
instead of helm format (with extra header)
|
||||
:param chart_info: (optional) supporting chart information
|
||||
extracted from the armada manifest which is used to influence
|
||||
overrides
|
||||
:param combined: (optional) whether to apply user overrides on top of
|
||||
system overrides
|
||||
"""
|
||||
|
||||
app, plugin_name = self._find_kube_app_and_app_plugin_name(app_name)
|
||||
|
||||
if is_fluxcd_app:
|
||||
armada_format = False
|
||||
# Get a manifest operator to provide a single point of
|
||||
# manipulation for the chart, chart group and manifest schemas
|
||||
manifest_op = self.get_armada_manifest_operator(app.name)
|
||||
|
||||
else:
|
||||
# Get a manifest operator to provide a single point of
|
||||
# manipulation for the chart, chart group and manifest schemas
|
||||
manifest_op = self.get_armada_manifest_operator(app.name)
|
||||
|
||||
# Load the manifest into the operator
|
||||
armada_manifest = utils.generate_synced_armada_manifest_fqpn(
|
||||
app.name, app.app_version, app.manifest_file)
|
||||
manifest_op.load(armada_manifest)
|
||||
# Load the manifest into the operator
|
||||
armada_manifest = utils.generate_synced_armada_manifest_fqpn(
|
||||
app.name, app.app_version, app.manifest_file)
|
||||
manifest_op.load(armada_manifest)
|
||||
|
||||
if plugin_name in self.helm_system_applications:
|
||||
app_overrides = self._get_helm_application_overrides(plugin_name,
|
||||
|
@ -802,34 +924,31 @@ class HelmOperator(object):
|
|||
|
||||
# If armada formatting is wanted, we need to change the
|
||||
# structure of the yaml file somewhat
|
||||
if armada_format:
|
||||
for key in overrides:
|
||||
metadata_name, repo_name, chart_tarfile = \
|
||||
self._get_chart_info_from_armada_chart(chart_name, key,
|
||||
armada_chart_info)
|
||||
new_overrides = self._add_armada_override_header(
|
||||
chart_name, metadata_name, repo_name, chart_tarfile,
|
||||
key, overrides[key])
|
||||
overrides[key] = new_overrides
|
||||
for key in overrides:
|
||||
metadata_name, repo_name, chart_tarfile = \
|
||||
self._get_chart_info_from_armada_chart(chart_name, key,
|
||||
chart_info)
|
||||
new_overrides = self._add_armada_override_header(
|
||||
chart_name, metadata_name, repo_name, chart_tarfile,
|
||||
key, overrides[key])
|
||||
overrides[key] = new_overrides
|
||||
self._write_chart_overrides(path, chart_name, cnamespace, overrides)
|
||||
|
||||
if not is_fluxcd_app:
|
||||
# Update manifest docs based on the plugin directives. If the
|
||||
# application does not provide a manifest operator, the
|
||||
# GenericArmadaManifestOperator is used and chart specific
|
||||
# operations can be skipped.
|
||||
if manifest_op.APP:
|
||||
if chart_name in self.chart_operators:
|
||||
self.chart_operators[chart_name].execute_manifest_updates(
|
||||
manifest_op)
|
||||
|
||||
# Update manifest docs based on the plugin directives. If the
|
||||
# application does not provide a manifest operator, the
|
||||
# GenericArmadaManifestOperator is used and chart specific
|
||||
# operations can be skipped.
|
||||
if manifest_op.APP:
|
||||
if chart_name in self.chart_operators:
|
||||
self.chart_operators[chart_name].execute_manifest_updates(
|
||||
manifest_op)
|
||||
if not is_fluxcd_app:
|
||||
# Update the manifest based on platform conditions
|
||||
manifest_op.platform_mode_manifest_updates(self.dbapi, mode)
|
||||
# Update the manifest based on platform conditions
|
||||
manifest_op.platform_mode_manifest_updates(self.dbapi, mode)
|
||||
|
||||
else:
|
||||
# Generic applications
|
||||
for chart in armada_chart_info:
|
||||
for chart in chart_info:
|
||||
try:
|
||||
db_chart = self.dbapi.helm_override_get(
|
||||
app.id, chart.name, chart.namespace)
|
||||
|
@ -855,24 +974,142 @@ class HelmOperator(object):
|
|||
user_overrides = yaml.load(yaml.dump(
|
||||
{chart.namespace: yaml.load(db_user_overrides)}))
|
||||
|
||||
if armada_format:
|
||||
metadata_name, repo_name, chart_tarfile =\
|
||||
self._get_chart_info_from_armada_chart(chart.name, chart.namespace,
|
||||
armada_chart_info)
|
||||
new_overrides = self._add_armada_override_header(
|
||||
chart.name, metadata_name, repo_name, chart_tarfile,
|
||||
chart.namespace, user_overrides[chart.namespace])
|
||||
user_overrides[chart.namespace] = new_overrides
|
||||
metadata_name, repo_name, chart_tarfile =\
|
||||
self._get_chart_info_from_armada_chart(chart.name, chart.namespace,
|
||||
chart_info)
|
||||
new_overrides = self._add_armada_override_header(
|
||||
chart.name, metadata_name, repo_name, chart_tarfile,
|
||||
chart.namespace, user_overrides[chart.namespace])
|
||||
user_overrides[chart.namespace] = new_overrides
|
||||
|
||||
self._write_chart_overrides(path, chart.name,
|
||||
cnamespace, user_overrides)
|
||||
|
||||
if not is_fluxcd_app:
|
||||
# Write the manifest doc overrides, a summmary file for easy --value
|
||||
# generation on the apply, and a unified manifest for deletion.
|
||||
manifest_op.save_overrides()
|
||||
manifest_op.save_summary(path=path)
|
||||
manifest_op.save_delete_manifest()
|
||||
# Write the manifest doc overrides, a summmary file for easy --value
|
||||
# generation on the apply, and a unified manifest for deletion.
|
||||
manifest_op.save_overrides()
|
||||
manifest_op.save_summary(path=path)
|
||||
manifest_op.save_delete_manifest()
|
||||
|
||||
@helm_context
|
||||
def _generate_helm_application_overrides_fluxcd(self, path, app_name,
|
||||
mode=None,
|
||||
cnamespace=None,
|
||||
chart_info=None,
|
||||
combined=False):
|
||||
"""Create the system overrides files for a supported application
|
||||
|
||||
This method will generate system helm chart overrides yaml files for a
|
||||
set of supported charts that comprise an application. If the namespace
|
||||
is provided only the overrides files for that specified namespace will
|
||||
be written.
|
||||
|
||||
:param app_name: name of the bundle of charts required to support an
|
||||
application
|
||||
:param mode: mode to control how to apply application manifest
|
||||
:param cnamespace: (optional) namespace
|
||||
:param chart_info: (optional) supporting chart information
|
||||
extracted from the fluxcd manifests which is used to influence
|
||||
overrides
|
||||
:param combined: (optional) whether to apply user overrides on top of
|
||||
system overrides
|
||||
"""
|
||||
|
||||
app, plugin_name = self._find_kube_app_and_app_plugin_name(app_name)
|
||||
|
||||
# Get a kustomize operator to provide a single point of
|
||||
# manipulation for the chart resources
|
||||
kustomize_op = self.get_fluxcd_kustomize_operator(app.name)
|
||||
|
||||
# Load the FluxCD manifests into the operator
|
||||
fluxcd_manifests_dir = utils.generate_synced_fluxcd_manifests_fqpn(
|
||||
app.name, app.app_version)
|
||||
kustomize_op.load(fluxcd_manifests_dir)
|
||||
|
||||
if plugin_name in self.helm_system_applications:
|
||||
app_overrides = self._get_helm_application_overrides(plugin_name,
|
||||
cnamespace)
|
||||
for (chart_name, overrides) in iteritems(app_overrides):
|
||||
if combined:
|
||||
# The overrides at this point are the system overrides. For
|
||||
# charts with multiple namespaces, the overrides would
|
||||
# contain multiple keys, one for each namespace.
|
||||
#
|
||||
# Retrieve the user overrides of each namespace from the
|
||||
# database and merge this list of user overrides, if they
|
||||
# exist, with the system overrides. Both system and user
|
||||
# override contents are then merged based on the namespace,
|
||||
# prepended with required header and written to
|
||||
# corresponding files (<namespace>-<chart>.yaml).
|
||||
file_overrides = []
|
||||
for chart_namespace in overrides.keys():
|
||||
try:
|
||||
db_chart = self.dbapi.helm_override_get(
|
||||
app.id, chart_name, chart_namespace)
|
||||
db_user_overrides = db_chart.user_overrides
|
||||
if db_user_overrides:
|
||||
file_overrides.append(yaml.dump(
|
||||
{chart_namespace: yaml.load(db_user_overrides)}))
|
||||
except exception.HelmOverrideNotFound:
|
||||
pass
|
||||
|
||||
if file_overrides:
|
||||
# Use dump() instead of safe_dump() as the latter is
|
||||
# not agreeable with password regex in some overrides
|
||||
system_overrides = yaml.dump(overrides)
|
||||
file_overrides.insert(0, system_overrides)
|
||||
combined_overrides = self.merge_overrides(
|
||||
file_overrides=file_overrides)
|
||||
overrides = yaml.load(combined_overrides)
|
||||
|
||||
self._write_chart_overrides(path, chart_name, cnamespace, overrides)
|
||||
|
||||
# Update manifest docs based on the plugin directives. If the
|
||||
# application does not provide a manifest operator, the
|
||||
# GenericFluxCDKustomizeOperator is used and chart specific
|
||||
# operations can be skipped.
|
||||
if kustomize_op.APP:
|
||||
if chart_name in self.chart_operators:
|
||||
self.chart_operators[chart_name].execute_kustomize_updates(
|
||||
kustomize_op)
|
||||
|
||||
# Update the kustomization manifests based on platform conditions
|
||||
kustomize_op.platform_mode_kustomize_updates(self.dbapi, mode)
|
||||
|
||||
else:
|
||||
# Generic applications
|
||||
for chart in chart_info:
|
||||
try:
|
||||
db_chart = self.dbapi.helm_override_get(
|
||||
app.id, chart.name, chart.namespace)
|
||||
except exception.HelmOverrideNotFound:
|
||||
# This routine is to create helm overrides entries
|
||||
# in database during application-upload so that user
|
||||
# can list the supported helm chart overrides of the
|
||||
# application via helm-override-list
|
||||
try:
|
||||
values = {
|
||||
'name': chart.name,
|
||||
'namespace': chart.namespace,
|
||||
'app_id': app.id,
|
||||
}
|
||||
db_chart = self.dbapi.helm_override_create(values=values)
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
return
|
||||
|
||||
user_overrides = {chart.namespace: {}}
|
||||
db_user_overrides = db_chart.user_overrides
|
||||
if db_user_overrides:
|
||||
user_overrides = yaml.load(yaml.dump(
|
||||
{chart.namespace: yaml.load(db_user_overrides)}))
|
||||
|
||||
self._write_chart_overrides(path, chart.name,
|
||||
cnamespace, user_overrides)
|
||||
|
||||
# Write the kustomization doc overrides and a unified manifest for deletion.
|
||||
kustomize_op.save_kustomization_updates()
|
||||
kustomize_op.save_release_cleanup_data()
|
||||
|
||||
def _find_kube_app_and_app_plugin_name(self, app_name):
|
||||
return utils.find_kube_app(self.dbapi, app_name), \
|
||||
|
|
|
@ -0,0 +1,285 @@
|
|||
#
|
||||
# Copyright (c) 2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
|
||||
""" System inventory FluxCD Kustomize manifest operator."""
|
||||
|
||||
import abc
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import ruamel.yaml as yaml
|
||||
import shutil
|
||||
import six
|
||||
import tempfile
|
||||
|
||||
from copy import deepcopy
|
||||
from oslo_log import log as logging
|
||||
from sysinv.common import constants
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class FluxCDKustomizeOperator(object):
|
||||
|
||||
def __init__(self, manifest_fqpn=None):
|
||||
|
||||
self.app_manifest_path = None # Path to the app manifests
|
||||
self.original_kustomization_fqpn = None # Original kustomization.yaml
|
||||
self.kustomization_fqpn = None # Updated kustomization.yaml
|
||||
self.release_cleanup_fqpn = None # Helm release cleanup data
|
||||
|
||||
self.kustomization_content = [] # Original app manifest content
|
||||
self.chart_to_resource_map = {} # Dict used to disable charts
|
||||
|
||||
self.kustomization_resources = [] # Kustomize resource list
|
||||
self.kustomization_namespace = None # Kustomize global namespace
|
||||
|
||||
self.chart_cleanup = [] # List of disabled charts
|
||||
|
||||
if manifest_fqpn:
|
||||
self.load(manifest_fqpn)
|
||||
|
||||
def __str__(self):
|
||||
return json.dumps({
|
||||
constants.APP_ROOT_KUSTOMIZE_FILE: self.kustomization_content,
|
||||
'chart_to_resource_map': self.chart_to_resource_map,
|
||||
'chart_cleanup': self.chart_cleanup,
|
||||
}, indent=2)
|
||||
|
||||
def load(self, manifests_dir_fqpn):
|
||||
""" Load the application kustomization manifests for processing
|
||||
|
||||
:param manifest_fqpn: fully qualified path name of the application
|
||||
manifests directory
|
||||
"""
|
||||
# Make sure that the manifests directory exists
|
||||
if not os.path.exists(manifests_dir_fqpn):
|
||||
LOG.error("Kustomize manifest directory %s does not exist" %
|
||||
manifests_dir_fqpn)
|
||||
return
|
||||
|
||||
# Save the location of the application manifests
|
||||
self.app_manifest_path = manifests_dir_fqpn
|
||||
|
||||
# Make sure that the kustomization.yaml file exists
|
||||
self.kustomization_fqpn = os.path.join(
|
||||
manifests_dir_fqpn, constants.APP_ROOT_KUSTOMIZE_FILE)
|
||||
if not os.path.exists(self.kustomization_fqpn):
|
||||
LOG.error("Kustomize manifest %s does not exist" %
|
||||
self.kustomization_fqpn)
|
||||
return
|
||||
|
||||
# Save the original kustomization.yaml for
|
||||
self.original_kustomization_fqpn = "%s-orig%s" % os.path.splitext(
|
||||
self.kustomization_fqpn)
|
||||
|
||||
if not os.path.exists(self.original_kustomization_fqpn):
|
||||
shutil.copy(self.kustomization_fqpn,
|
||||
self.original_kustomization_fqpn)
|
||||
|
||||
# Save the helm release cleanup data file name
|
||||
self.release_cleanup_fqpn = os.path.join(
|
||||
manifests_dir_fqpn, constants.APP_RELEASE_CLEANUP_FILE)
|
||||
|
||||
# Reset the view of charts to cleanup as platform conditions may have
|
||||
# changed
|
||||
self.chart_cleanup = []
|
||||
|
||||
# Read the original kustomization.yaml content
|
||||
with io.open(self.original_kustomization_fqpn, 'r', encoding='utf-8') as f:
|
||||
# The RoundTripLoader removes the superfluous quotes by default,
|
||||
# Set preserve_quotes=True to preserve all the quotes.
|
||||
self.kustomization_content = list(yaml.load_all(
|
||||
f, Loader=yaml.RoundTripLoader, preserve_quotes=True))
|
||||
|
||||
# Expect the top level kustomization.yaml to only have one doc
|
||||
if len(self.kustomization_content) > 1:
|
||||
LOG.error("Malformed Kustomize manifest %s contains more than one yaml "
|
||||
"doc." % self.kustomization_fqpn)
|
||||
return
|
||||
|
||||
# Grab the app resource
|
||||
self.kustomization_resources = self.kustomization_content[0]['resources']
|
||||
|
||||
# Grab the global namespace
|
||||
self.kustomization_namespace = deepcopy(
|
||||
self.kustomization_content[0]['namespace'])
|
||||
|
||||
# For these resources, find the HelmRelease info and build a resource
|
||||
# map
|
||||
for resource in self.kustomization_resources:
|
||||
# expect a helrelease.yaml flle to be present in a helm resource
|
||||
# directory
|
||||
|
||||
# is the resource a directory?
|
||||
resource_fqpn = os.path.join(manifests_dir_fqpn, resource)
|
||||
if not os.path.isdir(resource_fqpn):
|
||||
LOG.debug("%s is not a directory and cannot contain HelmRelease "
|
||||
"info. skipping" % resource_fqpn)
|
||||
continue
|
||||
|
||||
# is a helm release present?
|
||||
helm_release_fqpn = os.path.join(resource_fqpn, "helmrelease.yaml")
|
||||
if os.path.isfile(helm_release_fqpn):
|
||||
|
||||
with io.open(helm_release_fqpn, 'r', encoding='utf-8') as f:
|
||||
helm_release_doc = list(yaml.load_all(f,
|
||||
Loader=yaml.RoundTripLoader, preserve_quotes=True))
|
||||
|
||||
if len(helm_release_doc) > 1:
|
||||
LOG.error("Malformed HelmRelease: %s contains more than one "
|
||||
"yaml doc." % helm_release_fqpn)
|
||||
continue
|
||||
|
||||
# get the HelmRelease name
|
||||
try:
|
||||
metadata_name = helm_release_doc[0]['metadata']['name']
|
||||
except Exception:
|
||||
LOG.error("Malformed HelmRelease: Unable to retreive the "
|
||||
"metadata name from %s" % helm_release_fqpn)
|
||||
continue
|
||||
|
||||
# get the chart name
|
||||
try:
|
||||
chart_name = \
|
||||
helm_release_doc[0]['spec']['chart']['spec']['chart']
|
||||
except Exception:
|
||||
LOG.error("Malformed HelmRelease: Unable to retreive the "
|
||||
"chart name from %s" % helm_release_fqpn)
|
||||
continue
|
||||
|
||||
# Save pertinent data for disabling chart resources and cleaning
|
||||
# up existing helm releases after being disabled
|
||||
self.chart_to_resource_map.update({
|
||||
chart_name: {'resource': resource,
|
||||
'metadata_name': metadata_name}})
|
||||
|
||||
else:
|
||||
LOG.debug("Expecting to find a HelmRelease file at {}, skipping "
|
||||
"resource {}.".format(helm_release_fqpn,
|
||||
resource_fqpn))
|
||||
|
||||
LOG.debug("chart_to_resource_map: {}".format(self.chart_to_resource_map))
|
||||
|
||||
def _delete_kustomization_file(self):
|
||||
""" Remove any previously written top level kustomization file
|
||||
"""
|
||||
if self.kustomization_fqpn and os.path.exists(self.kustomization_fqpn):
|
||||
os.remove(self.kustomization_fqpn)
|
||||
|
||||
def _delete_release_cleanup_file(self):
|
||||
""" Remove any previously written helm release cleanup information
|
||||
"""
|
||||
if self.release_cleanup_fqpn and os.path.exists(self.release_cleanup_fqpn):
|
||||
os.remove(self.release_cleanup_fqpn)
|
||||
|
||||
def _write_file(self, path, filename, pathfilename, data):
|
||||
""" Write a yaml file
|
||||
|
||||
:param path: path to write the file
|
||||
:param filename: name of the file
|
||||
:param pathfilename: FQPN of the file
|
||||
:param data: file data
|
||||
"""
|
||||
try:
|
||||
fd, tmppath = tempfile.mkstemp(dir=path, prefix=filename,
|
||||
text=True)
|
||||
|
||||
with open(tmppath, 'w') as f:
|
||||
yaml.dump(data, f, Dumper=yaml.RoundTripDumper,
|
||||
default_flow_style=False)
|
||||
os.close(fd)
|
||||
os.rename(tmppath, pathfilename)
|
||||
# Change the permission to be readable to non-root
|
||||
# users
|
||||
os.chmod(pathfilename, 0o644)
|
||||
except Exception:
|
||||
if os.path.exists(tmppath):
|
||||
os.remove(tmppath)
|
||||
LOG.exception("Failed to write meta overrides %s" % pathfilename)
|
||||
raise
|
||||
|
||||
def save_kustomization_updates(self):
|
||||
""" Save an updated top level kustomization.yaml"""
|
||||
|
||||
if self.kustomization_fqpn and os.path.exists(self.kustomization_fqpn):
|
||||
|
||||
# remove existing kustomization file
|
||||
self._delete_kustomization_file()
|
||||
|
||||
# Save the updated view of the resource to enable
|
||||
self.kustomization_content[0]['resources'] = self.kustomization_resources
|
||||
|
||||
with open(self.kustomization_fqpn, 'w') as f:
|
||||
try:
|
||||
yaml.dump_all(self.kustomization_content, f, Dumper=yaml.RoundTripDumper,
|
||||
explicit_start=True,
|
||||
default_flow_style=False)
|
||||
LOG.debug("Updated kustomization file %s generated" %
|
||||
self.kustomization_fqpn)
|
||||
except Exception as e:
|
||||
LOG.error("Failed to generate updated kustomization file %s: "
|
||||
"%s" % (self.kustomization_fqpn, e))
|
||||
else:
|
||||
LOG.error("Kustomization file %s does not exist" % self.kustomization_fqpn)
|
||||
|
||||
def save_release_cleanup_data(self):
|
||||
""" Save yaml to cleanup HelmReleases that are no longer managed."""
|
||||
|
||||
# remove existing helm release file
|
||||
self._delete_release_cleanup_file()
|
||||
|
||||
if self.chart_cleanup:
|
||||
cleanup_dict = {'releases': self.chart_cleanup}
|
||||
self._write_file(self.app_manifest_path,
|
||||
constants.APP_RELEASE_CLEANUP_FILE,
|
||||
self.release_cleanup_fqpn,
|
||||
cleanup_dict)
|
||||
else:
|
||||
LOG.info("%s is not needed. All charts are enabled." % self.release_cleanup_fqpn)
|
||||
|
||||
def helm_release_resource_delete(self, chart):
|
||||
""" Delete a helm release resource
|
||||
|
||||
This method will remove a chart's resource from the top level
|
||||
kustomization file which will prevent it from being created during
|
||||
application applies.
|
||||
|
||||
The chart will also be added to a list of charts that will have their
|
||||
existing helm releases cleaned up
|
||||
|
||||
:param chart: chart name to remove from the resource list
|
||||
"""
|
||||
|
||||
removed_resource = self.chart_to_resource_map.pop(chart, None)
|
||||
if removed_resource:
|
||||
|
||||
# Remove the resource from the known resource list
|
||||
self.kustomization_resources.remove(removed_resource['resource'])
|
||||
|
||||
# Save the info needed to clean up any existing chart release
|
||||
self.chart_cleanup.append({
|
||||
'namespace': self.kustomization_namespace,
|
||||
'name': removed_resource['metadata_name']
|
||||
})
|
||||
else:
|
||||
LOG.error("%s is an unknown chart resource to %s" % (
|
||||
chart, self.original_kustomization_fqpn))
|
||||
|
||||
@abc.abstractmethod
|
||||
def platform_mode_kustomize_updates(self, dbapi, mode):
|
||||
""" Update the top-level kustomization resource list
|
||||
|
||||
Make changes to the top-level kustomization resource list based on the
|
||||
platform mode
|
||||
|
||||
:param dbapi: DB api object
|
||||
:param mode: mode to control when to update the resource list
|
||||
"""
|
||||
pass
|
|
@ -0,0 +1,24 @@
|
|||
#
|
||||
# Copyright (c) 2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
|
||||
""" System inventory Generic FluxCD Kustomize operator."""
|
||||
|
||||
from sysinv.helm import kustomize_base as base
|
||||
|
||||
|
||||
class GenericFluxCDKustomizeOperator(base.FluxCDKustomizeOperator):
|
||||
|
||||
APP = None
|
||||
|
||||
def platform_mode_kustomize_updates(self, dbapi, mode):
|
||||
""" Update the application kustomization manifests based on the platform
|
||||
|
||||
:param dbapi: DB api object
|
||||
:param mode: mode to control how to apply the application manifest
|
||||
"""
|
||||
pass
|
|
@ -1,6 +1,6 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright (c) 2019-2021 Wind River Systems, Inc.
|
||||
# Copyright (c) 2019-2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
@ -213,7 +213,7 @@ class ArmadaManifestOperator(object):
|
|||
was updated into an overrides file. The files are written to the same
|
||||
directory as the application manifest.
|
||||
"""
|
||||
if os.path.exists(self.manifest_path):
|
||||
if self.manifest_path and os.path.exists(self.manifest_path):
|
||||
|
||||
# cleanup any existing meta override files
|
||||
self._cleanup_meta_files(self.manifest_path)
|
||||
|
@ -242,7 +242,7 @@ class ArmadaManifestOperator(object):
|
|||
clear view of the conditional changes that were enforced by the system
|
||||
in the plugins
|
||||
"""
|
||||
if os.path.exists(self.manifest_path):
|
||||
if self.manifest_path and os.path.exists(self.manifest_path):
|
||||
|
||||
# cleanup existing deletion manifest
|
||||
self._cleanup_deletion_manifest()
|
||||
|
|
Loading…
Reference in New Issue