From 97463eebc986f1558c37d5ff5a84ec188f9e92d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrich=20Kr=C3=A4mer?= Date: Mon, 1 Jul 2024 11:45:03 +0000 Subject: [PATCH 01/36] TIR - remove dynamic pod data and surface helm status If helm is used for deployment, the components deployed by helm are no longer listed as toplevel elements in the TIR. Instead the helm release is represented by its status so that users will see tables under: Deployment Resource: -deploymentMean Deployment Resource: -deploymentStatus The DeploymentStatus has rows surfacing the releaseName, releaseRevision, namespace, deployStatus, deployDescription, lastDeployed and resources. Here an example from the resources: "Deployment: backend-helm-monorepo-chart-component-a, backend- helm-monorepo-chart-component-b, Service: backend-helm-monorepo- chart" Non-Helm deployments are surfaced as before. Further change notes: - To avoid CPS issues in Jenkins all types were using their own file and the Immutable annotation was avoided. - Type HelmStatusData captures helm status with high fidelity whereas HelmStatusSimpleData captures shallower information in particular omitting the details of resources except their names and kinds. This approach simplifies the json Helm status json parsing code while keeping the consuming code relatively lean. In the deployment artifact the simplified helmStatus is kept under key 'helmStatus' in the deploymentMean information. Which is also written to `ods-deployments.json` by DeploymentDescriptor. - PodData fields capturing dynamic data where removed, except for the podName which has a prefix useful for correlating dynamic data with pods. At the moment this is still needed for both tailor and helm deployments. The code which finds this named pods did require retry logic even for helm installs using --atomic. --- .../AbstractDeploymentStrategy.groovy | 3 + .../component/HelmDeploymentStrategy.groovy | 100 ++++--- .../phases/DeployOdsComponent.groovy | 27 +- .../usecase/LeVADocumentUseCase.groovy | 257 ++++++++++++------ .../util/DeploymentDescriptor.groovy | 13 + src/org/ods/services/OpenShiftService.groovy | 49 +++- .../util/HelmStatusContainerStatusData.groovy | 32 +++ src/org/ods/util/HelmStatusData.groovy | 181 ++++++++++++ src/org/ods/util/HelmStatusInfoData.groovy | 48 ++++ src/org/ods/util/HelmStatusResource.groovy | 25 ++ .../ods/util/HelmStatusResourceData.groovy | 39 +++ src/org/ods/util/HelmStatusSimpleData.groovy | 70 +++++ src/org/ods/util/PodData.groovy | 15 - 13 files changed, 712 insertions(+), 147 deletions(-) create mode 100644 src/org/ods/util/HelmStatusContainerStatusData.groovy create mode 100644 src/org/ods/util/HelmStatusData.groovy create mode 100644 src/org/ods/util/HelmStatusInfoData.groovy create mode 100644 src/org/ods/util/HelmStatusResource.groovy create mode 100644 src/org/ods/util/HelmStatusResourceData.groovy create mode 100644 src/org/ods/util/HelmStatusSimpleData.groovy diff --git a/src/org/ods/component/AbstractDeploymentStrategy.groovy b/src/org/ods/component/AbstractDeploymentStrategy.groovy index 359541c9a..32213c70b 100644 --- a/src/org/ods/component/AbstractDeploymentStrategy.groovy +++ b/src/org/ods/component/AbstractDeploymentStrategy.groovy @@ -14,6 +14,9 @@ abstract class AbstractDeploymentStrategy implements IDeploymentStrategy { @Override abstract Map> deploy() + // Fetches original kubernetes revisions of deployment resources. + // + // returns a two level map with keys resourceKind -> resourceName -> revision (int) protected Map> fetchOriginalVersions(Map> deploymentResources) { def originalVersions = [:] deploymentResources.each { resourceKind, resourceNames -> diff --git a/src/org/ods/component/HelmDeploymentStrategy.groovy b/src/org/ods/component/HelmDeploymentStrategy.groovy index 0bd9360c9..c706c24ee 100644 --- a/src/org/ods/component/HelmDeploymentStrategy.groovy +++ b/src/org/ods/component/HelmDeploymentStrategy.groovy @@ -5,6 +5,7 @@ import groovy.transform.TypeChecked import groovy.transform.TypeCheckingMode import org.ods.services.JenkinsService import org.ods.services.OpenShiftService +import org.ods.util.HelmStatusSimpleData import org.ods.util.ILogger import org.ods.util.PipelineSteps import org.ods.util.PodData @@ -94,10 +95,16 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { logger.info "Rolling out ${context.componentId} with HELM, selector: ${options.selector}" helmUpgrade(context.targetProject) + HelmStatusSimpleData helmStatus = openShift.helmStatus(context.targetProject, options.helmReleaseName) + def helmStatusMap = helmStatus.toMap() + def deploymentResources = getDeploymentResources(helmStatus) - def deploymentResources = openShift.getResourcesForComponent( - context.targetProject, DEPLOYMENT_KINDS, options.selector - ) + logger.info("${this.class.name} -- HELM STATUS") + logger.info( + JsonOutput.prettyPrint( + JsonOutput.toJson(helmStatusMap))) + + // not sure if we need both HELM STATUS and DEPLOYMENT RESOURCES" logger.info("${this.class.name} -- DEPLOYMENT RESOURCES") logger.info( JsonOutput.prettyPrint( @@ -107,11 +114,12 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { // // we assume that Helm does "Deployment" that should work for most // // cases since they don't have triggers. // metadataSvc.updateMetadata(false, deploymentResources) - def rolloutData = getRolloutData(deploymentResources) ?: [:] + def rolloutData = getRolloutData(helmStatus) logger.info(JsonOutput.prettyPrint(JsonOutput.toJson(rolloutData))) return rolloutData } + @SuppressWarnings(['UnnecessaryCast']) // otherwise IDE marked up helmUpgrade call private void helmUpgrade(String targetProject) { steps.dir(options.chartDir) { jenkins.maybeWithPrivateKeyCredentials(options.helmPrivateKeyCredentialsId) { String pkeyFile -> @@ -124,7 +132,7 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { options.helmValues['componentId'] = context.componentId // we persist the original ones set from outside - here we just add ours - Map mergedHelmValues = [:] + Map mergedHelmValues = [:] as Map mergedHelmValues << options.helmValues // we add the global ones - this allows usage in subcharts @@ -140,7 +148,7 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { mergedHelmValues['global.imageTag'] = options.imageTag // deal with dynamic value files - which are env dependent - def mergedHelmValuesFiles = [] + def mergedHelmValuesFiles = [] as List mergedHelmValuesFiles.addAll(options.helmValuesFiles) options.helmEnvBasedValuesFiles.each { envValueFile -> @@ -161,6 +169,10 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { } } + private Map> getDeploymentResources(HelmStatusSimpleData helmStatus) { + helmStatus.getResourcesByKind(DEPLOYMENT_KINDS) + } + // rollout returns a map like this: // [ // 'DeploymentConfig/foo': [[podName: 'foo-a', ...], [podName: 'foo-b', ...]], @@ -168,39 +180,55 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { // ] @TypeChecked(TypeCheckingMode.SKIP) private Map> getRolloutData( - Map> deploymentResources) { - + HelmStatusSimpleData helmStatus + ) { def rolloutData = [:] - deploymentResources.each { resourceKind, resourceNames -> - resourceNames.each { resourceName -> - def podData = [] - for (def i = 0; i < options.deployTimeoutRetries; i++) { - podData = openShift.checkForPodData(context.targetProject, options.selector, resourceName) - if (!podData.isEmpty()) { - break - } - steps.echo("Could not find 'running' pod(s) with label '${options.selector}' - waiting") - steps.sleep(12) + helmStatus.resources.each { resource -> + if (! (resource.kind in DEPLOYMENT_KINDS)) { + return // continues with next + } + context.addDeploymentToArtifactURIs("${resource.name}-deploymentMean", + [ + 'type': 'helm', + 'selector': options.selector, + 'chartDir': options.chartDir, + 'helmReleaseName': options.helmReleaseName, + 'helmEnvBasedValuesFiles': options.helmEnvBasedValuesFiles, + 'helmValuesFiles': options.helmValuesFiles, + 'helmValues': options.helmValues, + 'helmDefaultFlags': options.helmDefaultFlags, + 'helmAdditionalFlags': options.helmAdditionalFlags, + 'helmStatus': helmStatus.toMap(), + ] + ) + def podDataContext = [ + "targetProject=${context.targetProject}", + "selector=${options.selector}", + "name=${resource.name}", + ] + def msgPodsNotFound = "Could not find 'running' pod(s) for '${podDataContext.join(', ')}'" + List podData = null + for (def i = 0; i < options.deployTimeoutRetries; i++) { + podData = openShift.checkForPodData(context.targetProject, options.selector, resource.name) + if (podData) { + break } - context.addDeploymentToArtifactURIs("${resourceName}-deploymentMean", - [ - 'type': 'helm', - 'selector': options.selector, - 'chartDir': options.chartDir, - 'helmReleaseName': options.helmReleaseName, - 'helmEnvBasedValuesFiles': options.helmEnvBasedValuesFiles, - 'helmValuesFiles': options.helmValuesFiles, - 'helmValues': options.helmValues, - 'helmDefaultFlags': options.helmDefaultFlags, - 'helmAdditionalFlags': options.helmAdditionalFlags - ]) - rolloutData["${resourceKind}/${resourceName}"] = podData - // TODO: Once the orchestration pipeline can deal with multiple replicas, - // update this to store multiple pod artifacts. - // TODO: Potential conflict if resourceName is duplicated between - // Deployment and DeploymentConfig resource. - context.addDeploymentToArtifactURIs(resourceName, podData[0]?.toMap()) + steps.echo("${msgPodsNotFound} - waiting") + steps.sleep(12) + } + if (!podData) { + throw new RuntimeException(msgPodsNotFound) } + + logger.debug("Helm podData for ${podDataContext.join(', ')}: " + + "${JsonOutput.prettyPrint(JsonOutput.toJson(podData))}") + + rolloutData["${resource.kind}/${resource.name}"] = podData + // TODO: Once the orchestration pipeline can deal with multiple replicas, + // update this to store multiple pod artifacts. + // TODO: Potential conflict if resourceName is duplicated between + // Deployment and DeploymentConfig resource. + context.addDeploymentToArtifactURIs(resource.name, podData[0]?.toMap()) } rolloutData } diff --git a/src/org/ods/orchestration/phases/DeployOdsComponent.groovy b/src/org/ods/orchestration/phases/DeployOdsComponent.groovy index 260128866..4c697bb26 100644 --- a/src/org/ods/orchestration/phases/DeployOdsComponent.groovy +++ b/src/org/ods/orchestration/phases/DeployOdsComponent.groovy @@ -1,5 +1,6 @@ package org.ods.orchestration.phases +import groovy.json.JsonOutput import groovy.transform.TypeChecked import groovy.transform.TypeCheckingMode import org.ods.util.IPipelineSteps @@ -11,6 +12,7 @@ import org.ods.services.GitService import org.ods.orchestration.util.DeploymentDescriptor import org.ods.orchestration.util.MROPipelineUtil import org.ods.orchestration.util.Project +import org.ods.util.PodData // Deploy ODS comnponent (code or service) to 'qa' or 'prod'. @TypeChecked @@ -58,21 +60,29 @@ class DeployOdsComponent { applyTemplates(openShiftDir, deploymentMean) def retries = project.environmentConfig?.openshiftRolloutTimeoutRetries ?: 10 - def podData = null + def podDataContext = [ + "targetProject=${project.targetProject}", + "selector=${deploymentMean.selector}", + "name=${deploymentName}", + ] + def msgPodsNotFound = "Could not find 'running' pod(s) for '${podDataContext.join(', ')}'" + List podData = null for (def i = 0; i < retries; i++) { podData = os.checkForPodData(project.targetProject, deploymentMean.selector, deploymentName) if (podData) { break } - steps.echo("Could not find 'running' pod(s) with label '${deploymentMean.selector}' - waiting") + steps.echo("${msgPodsNotFound} - waiting") steps.sleep(12) } if (!podData) { - throw new RuntimeException("Could not find 'running' pod(s) with label " + - "'${deploymentMean.selector}'") + throw new RuntimeException(msgPodsNotFound) } + logger.info("Helm podData for '${podDataContext.join(', ')}': " + + "${JsonOutput.prettyPrint(JsonOutput.toJson(podData))}") + // TODO: Once the orchestration pipeline can deal with multiple replicas, // update this to deal with multiple pods. def pod = podData[0].toMap() @@ -227,6 +237,15 @@ class DeployOdsComponent { deploymentMean.helmDefaultFlags, deploymentMean.helmAdditionalFlags, true) + + def helmStatus = os. helmStatus(project.targetProject, deploymentMean.helmReleaseName) + def helmStatusMap = helmStatus.toMap() + deploymentMean.helmStatus = helmStatusMap + logger.info("${this.class.name} -- HELM STATUS") + logger.info( + JsonOutput.prettyPrint( + JsonOutput.toJson(helmStatusMap))) + } } jenkins.maybeWithPrivateKeyCredentials(secretName) { String pkeyFile -> diff --git a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy index ec02672c0..f143889e0 100644 --- a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy +++ b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy @@ -79,16 +79,16 @@ class LeVADocumentUseCase extends DocGenUseCase { ] static Map DOCUMENT_TYPE_FILESTORAGE_EXCEPTIONS = [ - 'SCRR-MD' : [storage: 'pdf', content: 'pdf' ] + 'SCRR-MD': [storage: 'pdf', content: 'pdf'] ] static Map INTERNAL_TO_EXT_COMPONENT_TYPES = [ - (MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_SAAS_SERVICE as String) : 'SAAS Component', - (MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_TEST as String) : 'Automated tests', - (MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_SERVICE as String) : '3rd Party Service Component', - (MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_CODE as String) : 'ODS Software Component', - (MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_INFRA as String) : 'Infrastructure as Code Component', - (MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_LIB as String) : 'ODS library component' + (MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_SAAS_SERVICE as String): 'SAAS Component', + (MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_TEST as String) : 'Automated tests', + (MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_SERVICE as String) : '3rd Party Service Component', + (MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_CODE as String) : 'ODS Software Component', + (MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_INFRA as String) : 'Infrastructure as Code Component', + (MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_LIB as String) : 'ODS library component' ] public static String DEVELOPER_PREVIEW_WATERMARK = 'Developer Preview' @@ -138,7 +138,7 @@ class LeVADocumentUseCase extends DocGenUseCase { def requirements = this.project.getSystemRequirements() def reqsWithNoGampTopic = getReqsWithNoGampTopic(requirements) def reqsGroupedByGampTopic = getReqsGroupedByGampTopic(requirements) - reqsGroupedByGampTopic << ['uncategorized': reqsWithNoGampTopic ] + reqsGroupedByGampTopic << ['uncategorized': reqsWithNoGampTopic] def requirementsForDocument = reqsGroupedByGampTopic.collectEntries { gampTopic, reqs -> def updatedReqs = reqs.collect { req -> @@ -146,18 +146,18 @@ class LeVADocumentUseCase extends DocGenUseCase { def epic = epics.isEmpty() ? null : epics.first() return [ - key : req.key, - applicability : 'Mandatory', - ursName : req.name, - ursDescription : this.convertImages(req.description ?: ''), - csName : req.configSpec.name ?: 'N/A', - csDescription : this.convertImages(req.configSpec.description ?: ''), - fsName : req.funcSpec.name ?: 'N/A', - fsDescription : this.convertImages(req.funcSpec.description ?: ''), - epic : epic?.key, - epicName : epic?.epicName, - epicTitle : epic?.title, - epicDescription : this.convertImages(epic?.description), + key : req.key, + applicability : 'Mandatory', + ursName : req.name, + ursDescription : this.convertImages(req.description ?: ''), + csName : req.configSpec.name ?: 'N/A', + csDescription : this.convertImages(req.configSpec.description ?: ''), + fsName : req.funcSpec.name ?: 'N/A', + fsDescription : this.convertImages(req.funcSpec.description ?: ''), + epic : epic?.key, + epicName : epic?.epicName, + epicTitle : epic?.title, + epicDescription: this.convertImages(epic?.description), ] } @@ -193,17 +193,18 @@ class LeVADocumentUseCase extends DocGenUseCase { protected Map sortByEpicAndRequirementKeys(List updatedReqs) { def sortedUpdatedReqs = SortUtil.sortIssuesByKey(updatedReqs) def reqsGroupByEpic = sortedUpdatedReqs.findAll { - it.epic != null }.groupBy { it.epic }.sort() + it.epic != null + }.groupBy { it.epic }.sort() def reqsGroupByEpicUpdated = reqsGroupByEpic.values().indexed(1).collect { index, epicStories -> def aStory = epicStories.first() [ - epicName : aStory.epicName, - epicTitle : aStory.epicTitle, - epicDescription : this.convertImages(aStory.epicDescription ?: ''), - key : aStory.epic, - epicIndex : index, - stories : epicStories, + epicName : aStory.epicName, + epicTitle : aStory.epicTitle, + epicDescription: this.convertImages(aStory.epicDescription ?: ''), + key : aStory.epic, + epicIndex : index, + stories : epicStories, ] } def output = [ @@ -216,14 +217,14 @@ class LeVADocumentUseCase extends DocGenUseCase { @NonCPS private def computeKeysInDocForCSD(def data) { - return data.collect { it.subMap(['key', 'epics']).values() } + return data.collect { it.subMap(['key', 'epics']).values() } .flatten().unique() } @NonCPS private def computeKeysInDocForDTP(def data, def tests) { return data.collect { 'Technology-' + it.id } + tests - .collect { [it.testKey, it.systemRequirement.split(', '), it.softwareDesignSpec.split(', ')] } + .collect { [it.testKey, it.systemRequirement.split(', '), it.softwareDesignSpec.split(', ')] } .flatten() } @@ -286,7 +287,7 @@ class LeVADocumentUseCase extends DocGenUseCase { description += testIssue.name } - def riskLevels = testIssue.getResolvedRisks(). collect { + def riskLevels = testIssue.getResolvedRisks().collect { def value = obtainEnum("SeverityOfImpact", it.severityOfImpact) return value ? value.text : "None" } @@ -312,19 +313,19 @@ class LeVADocumentUseCase extends DocGenUseCase { def data_ = [ metadata: this.getDocumentMetadata(this.DOCUMENT_TYPE_NAMES[documentType], repo), data : [ - repo : repo, - sections : sections, - tests : tests, - numAdditionalTests: junit.getNumberOfTestCases(unitTestData.testResults) - testIssues.count { !it.isUnexecuted }, - testFiles : SortUtil.sortIssuesByProperties(unitTestData.testReportFiles.collect { file -> + repo : repo, + sections : sections, + tests : tests, + numAdditionalTests : junit.getNumberOfTestCases(unitTestData.testResults) - testIssues.count { !it.isUnexecuted }, + testFiles : SortUtil.sortIssuesByProperties(unitTestData.testReportFiles.collect { file -> [name: file.name, path: file.path, text: XmlUtil.serialize(file.text)] } ?: [], ["name"]), - discrepancies : discrepancies.discrepancies, - conclusion : [ + discrepancies : discrepancies.discrepancies, + conclusion : [ summary : discrepancies.conclusion.summary, statement: discrepancies.conclusion.statement ], - documentHistory: docHistory?.getDocGenFormat() ?: [], + documentHistory : docHistory?.getDocGenFormat() ?: [], documentHistoryLatestVersionId: docHistory?.latestVersionId ?: 1, ] ] @@ -398,7 +399,7 @@ class LeVADocumentUseCase extends DocGenUseCase { //Discrepancy ID -> BUG Issue ID discrepancyID : bug.key, //Test Case No. -> JIRA (Test Case Key) - testcaseID : bug.tests. collect { it.key }.join(", "), + testcaseID : bug.tests.collect { it.key }.join(", "), //- Level of Test Case = Unit / Integration / Acceptance / Installation level : "Integration", //Description of Failure or Discrepancy -> Bug Issue Summary @@ -421,7 +422,7 @@ class LeVADocumentUseCase extends DocGenUseCase { //Discrepancy ID -> BUG Issue ID discrepancyID : bug.key, //Test Case No. -> JIRA (Test Case Key) - testcaseID : bug.tests. collect { it.key }.join(", "), + testcaseID : bug.tests.collect { it.key }.join(", "), //- Level of Test Case = Unit / Integration / Acceptance / Installation level : "Acceptance", //Description of Failure or Discrepancy -> Bug Issue Summary @@ -455,8 +456,8 @@ class LeVADocumentUseCase extends DocGenUseCase { def data_ = [ metadata: this.getDocumentMetadata(this.DOCUMENT_TYPE_NAMES[documentType]), data : [ - sections : sections, - documentHistory: docHistory?.getDocGenFormat() ?: [], + sections : sections, + documentHistory : docHistory?.getDocGenFormat() ?: [], documentHistoryLatestVersionId: docHistory?.latestVersionId ?: 1, ] ] @@ -497,14 +498,14 @@ class LeVADocumentUseCase extends DocGenUseCase { def data_ = [ metadata: this.getDocumentMetadata(this.DOCUMENT_TYPE_NAMES[documentType]), data : [ - sections : sections, - numAdditionalAcceptanceTests : junit.getNumberOfTestCases(acceptanceTestData.testResults) - acceptanceTestIssues.count { !it.isUnexecuted }, - numAdditionalIntegrationTests: junit.getNumberOfTestCases(integrationTestData.testResults) - integrationTestIssues.count { !it.isUnexecuted }, - conclusion : [ + sections : sections, + numAdditionalAcceptanceTests : junit.getNumberOfTestCases(acceptanceTestData.testResults) - acceptanceTestIssues.count { !it.isUnexecuted }, + numAdditionalIntegrationTests : junit.getNumberOfTestCases(integrationTestData.testResults) - integrationTestIssues.count { !it.isUnexecuted }, + conclusion : [ summary : discrepancies.conclusion.summary, statement: discrepancies.conclusion.statement ], - documentHistory: docHistory?.getDocGenFormat() ?: [], + documentHistory : docHistory?.getDocGenFormat() ?: [], documentHistoryLatestVersionId: docHistory?.latestVersionId ?: 1, ] ] @@ -554,7 +555,7 @@ class LeVADocumentUseCase extends DocGenUseCase { @NonCPS private def computeKeysInDocForRA(def data) { return data - .collect { it.subMap(['key', 'requirements', 'techSpecs', 'mitigations', 'tests']).values() } + .collect { it.subMap(['key', 'requirements', 'techSpecs', 'mitigations', 'tests']).values() } .flatten() } @@ -569,7 +570,7 @@ class LeVADocumentUseCase extends DocGenUseCase { } def risks = this.project.getRisks() - .findAll { it != null } + .findAll { it != null } .collect { r -> def mitigationsText = this.replaceDashToNonBreakableUnicode(r.mitigations ? r.mitigations.join(", ") : "None") def testsText = this.replaceDashToNonBreakableUnicode(r.tests ? r.tests.join(", ") : "None") @@ -588,11 +589,11 @@ class LeVADocumentUseCase extends DocGenUseCase { requirement: requirement, gxpRelevance: gxpRelevance ? gxpRelevance."short" : "None", probabilityOfOccurrence: probabilityOfOccurrence ? probabilityOfOccurrence."short" : "None", - severityOfImpact: severityOfImpact ? severityOfImpact."short" : "None", - probabilityOfDetection: probabilityOfDetection ? probabilityOfDetection."short" : "None", - riskPriority: riskPriority ? riskPriority."short" : "None", - riskPriorityNumber: (r.riskPriorityNumber != null) ? r.riskPriorityNumber : "N/A", - riskComment: r.riskComment ? r.riskComment : "N/A", + severityOfImpact : severityOfImpact ? severityOfImpact."short" : "None", + probabilityOfDetection : probabilityOfDetection ? probabilityOfDetection."short" : "None", + riskPriority : riskPriority ? riskPriority."short" : "None", + riskPriorityNumber : (r.riskPriorityNumber != null) ? r.riskPriorityNumber : "N/A", + riskComment : r.riskComment ? r.riskComment : "N/A", ] } @@ -663,7 +664,7 @@ class LeVADocumentUseCase extends DocGenUseCase { @NonCPS private def computeKeysInDocForIPV(def data) { return data - .collect { it.subMap(['key', 'components', 'techSpecs']).values() } + .collect { it.subMap(['key', 'components', 'techSpecs']).values() } .flatten() } @@ -699,9 +700,9 @@ class LeVADocumentUseCase extends DocGenUseCase { def data_ = [ metadata: this.getDocumentMetadata(DOCUMENT_TYPE_NAMES[documentType]), data : [ - repositories : installedRepos.collect { [id: it.id, type: it.type, doInstall: it.doInstall, data: [git: [url: it.data.git == null ? null : it.data.git.url]]] }, - sections : sections, - tests : SortUtil.sortIssuesByKey(installationTestIssues.collect { testIssue -> + repositories : installedRepos.collect { [id: it.id, type: it.type, doInstall: it.doInstall, data: [git: [url: it.data.git == null ? null : it.data.git.url]]] }, + sections : sections, + tests : SortUtil.sortIssuesByKey(installationTestIssues.collect { testIssue -> [ key : testIssue.key, summary : testIssue.name, @@ -723,7 +724,7 @@ class LeVADocumentUseCase extends DocGenUseCase { @NonCPS private def computeKeysInDocForIVR(def data) { return data - .collect { it.subMap(['key', 'components', 'techSpecs']).values() } + .collect { it.subMap(['key', 'components', 'techSpecs']).values() } .flatten() } @@ -753,7 +754,7 @@ class LeVADocumentUseCase extends DocGenUseCase { } } - def keysInDoc = this.computeKeysInDocForIVR(installationTestIssues) + def keysInDoc = this.computeKeysInDocForIVR(installationTestIssues) def docHistory = this.getAndStoreDocumentHistory(documentType, keysInDoc) def installedRepos = this.project.repositories.findAll { it -> @@ -763,9 +764,9 @@ class LeVADocumentUseCase extends DocGenUseCase { def data_ = [ metadata: this.getDocumentMetadata(this.DOCUMENT_TYPE_NAMES[documentType]), data : [ - repositories : installedRepos.collect { [id: it.id, type: it.type, doInstall: it.doInstall, data: [git: [url: it.data.git == null ? null : it.data.git.url]]] }, - sections : sections, - tests : SortUtil.sortIssuesByKey(installationTestIssues.collect { testIssue -> + repositories : installedRepos.collect { [id: it.id, type: it.type, doInstall: it.doInstall, data: [git: [url: it.data.git == null ? null : it.data.git.url]]] }, + sections : sections, + tests : SortUtil.sortIssuesByKey(installationTestIssues.collect { testIssue -> [ key : testIssue.key, description: this.convertImages(testIssue.description ?: ''), @@ -775,12 +776,12 @@ class LeVADocumentUseCase extends DocGenUseCase { techSpec : testIssue.techSpecs.join(", ") ?: "N/A" ] }), - numAdditionalTests: junit.getNumberOfTestCases(installationTestData.testResults) - installationTestIssues.count { !it.isUnexecuted }, - testFiles : SortUtil.sortIssuesByProperties(installationTestData.testReportFiles.collect { file -> + numAdditionalTests : junit.getNumberOfTestCases(installationTestData.testResults) - installationTestIssues.count { !it.isUnexecuted }, + testFiles : SortUtil.sortIssuesByProperties(installationTestData.testReportFiles.collect { file -> [name: file.name, path: file.path, text: file.text] } ?: [], ["name"]), - discrepancies : discrepancies.discrepancies, - conclusion : [ + discrepancies : discrepancies.discrepancies, + conclusion : [ summary : discrepancies.conclusion.summary, statement: discrepancies.conclusion.statement ], @@ -818,7 +819,7 @@ class LeVADocumentUseCase extends DocGenUseCase { def matchedHandler = { result -> result.each { testIssue, testCase -> testIssue.isSuccess = !(testCase.error || testCase.failure || testCase.skipped - || !testIssue.getResolvedBugs(). findAll { bug -> bug.status?.toLowerCase() != "done" }.isEmpty() + || !testIssue.getResolvedBugs().findAll { bug -> bug.status?.toLowerCase() != "done" }.isEmpty() || testIssue.isUnexecuted) testIssue.comment = testIssue.isUnexecuted ? "This Test Case has not been executed" : "" testIssue.timestamp = testIssue.isUnexecuted ? "N/A" : testCase.timestamp @@ -846,37 +847,37 @@ class LeVADocumentUseCase extends DocGenUseCase { def data_ = [ metadata: this.getDocumentMetadata(DOCUMENT_TYPE_NAMES[documentType]), data : [ - sections : sections, - integrationTests : SortUtil.sortIssuesByKey(integrationTestIssues.collect { testIssue -> + sections : sections, + integrationTests : SortUtil.sortIssuesByKey(integrationTestIssues.collect { testIssue -> [ key : testIssue.key, description : this.convertImages(getTestDescription(testIssue)), requirements: testIssue.requirements ? testIssue.requirements.join(", ") : "N/A", isSuccess : testIssue.isSuccess, - bugs : testIssue.bugs ? testIssue.bugs.join(", ") : (testIssue.comment ? "": "N/A"), + bugs : testIssue.bugs ? testIssue.bugs.join(", ") : (testIssue.comment ? "" : "N/A"), steps : sortTestSteps(testIssue.steps), timestamp : testIssue.timestamp ? testIssue.timestamp.replaceAll("T", " ") : "N/A", comment : testIssue.comment, actualResult: testIssue.actualResult ] }), - acceptanceTests : SortUtil.sortIssuesByKey(acceptanceTestIssues.collect { testIssue -> + acceptanceTests : SortUtil.sortIssuesByKey(acceptanceTestIssues.collect { testIssue -> [ key : testIssue.key, description : this.convertImages(getTestDescription(testIssue)), requirements: testIssue.requirements ? testIssue.requirements.join(", ") : "N/A", isSuccess : testIssue.isSuccess, - bugs : testIssue.bugs ? testIssue.bugs.join(", ") : (testIssue.comment ? "": "N/A"), + bugs : testIssue.bugs ? testIssue.bugs.join(", ") : (testIssue.comment ? "" : "N/A"), steps : sortTestSteps(testIssue.steps), timestamp : testIssue.timestamp ? testIssue.timestamp.replaceAll("T", " ") : "N/A", comment : testIssue.comment, actualResult: testIssue.actualResult ] }), - integrationTestFiles: SortUtil.sortIssuesByProperties(integrationTestData.testReportFiles.collect { file -> + integrationTestFiles : SortUtil.sortIssuesByProperties(integrationTestData.testReportFiles.collect { file -> [name: file.name, path: file.path, text: file.text] } ?: [], ["name"]), - acceptanceTestFiles : SortUtil.sortIssuesByProperties(acceptanceTestData.testReportFiles.collect { file -> + acceptanceTestFiles : SortUtil.sortIssuesByProperties(acceptanceTestData.testReportFiles.collect { file -> [name: file.name, path: file.path, text: file.text] } ?: [], ["name"]), documentHistory: docHistory?.getDocGenFormat() ?: [], @@ -904,8 +905,8 @@ class LeVADocumentUseCase extends DocGenUseCase { def data_ = [ metadata: this.getDocumentMetadata(DOCUMENT_TYPE_NAMES[documentType]), data : [ - sections : sections, - integrationTests: SortUtil.sortIssuesByKey(integrationTestIssues.collect { testIssue -> + sections : sections, + integrationTests : SortUtil.sortIssuesByKey(integrationTestIssues.collect { testIssue -> [ key : testIssue.key, description : this.convertImages(testIssue.description ?: testIssue.name), @@ -914,7 +915,7 @@ class LeVADocumentUseCase extends DocGenUseCase { steps : sortTestSteps(testIssue.steps) ] }), - acceptanceTests : SortUtil.sortIssuesByKey(acceptanceTestIssues.collect { testIssue -> + acceptanceTests : SortUtil.sortIssuesByKey(acceptanceTestIssues.collect { testIssue -> [ key : testIssue.key, description : this.convertImages(testIssue.description ?: testIssue.name), @@ -986,17 +987,17 @@ class LeVADocumentUseCase extends DocGenUseCase { // Get the components that we consider modules in SSDS (the ones you have to code) def modules = componentsMetadata - .findAll { it.odsRepoType.toLowerCase() == MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_CODE.toLowerCase() } - .collect { component -> + .findAll { it.odsRepoType.toLowerCase() == MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_CODE.toLowerCase() } + .collect { component -> // We will set-up a double loop in the template. For moustache limitations we need to have lists component.requirements = component.requirements.findAll { it != null }.collect { r -> - [key: r.key, name: r.name, + [key : r.key, name: r.name, reqDescription: this.convertImages(r.description), gampTopic: r.gampTopic ?: "uncategorized"] }.groupBy { it.gampTopic.toLowerCase() } .collect { k, v -> [gampTopic: k, requirementsofTopic: v] } return component - } + } if (!sections."sec10") sections."sec10" = [:] sections."sec10".modules = modules @@ -1096,9 +1097,9 @@ class LeVADocumentUseCase extends DocGenUseCase { deployNote : deploynoteData, openShiftData: [ builds : repo.data.openshift.builds ?: '', - deployments: repo.data.openshift.deployments ?: '' + deployments: assembleDeployments(repo.data.openshift.deployments ?: [:]), ], - testResults: [ + testResults : [ installation: installationTestData?.testResults ], data: [ @@ -1113,7 +1114,7 @@ class LeVADocumentUseCase extends DocGenUseCase { def codeReviewReport if (this.project.isAssembleMode && !this.jiraUseCase.jira && repo.type?.toLowerCase() == MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_CODE.toLowerCase()) { - def currentRepoAsList = [ repo ] + def currentRepoAsList = [repo] codeReviewReport = obtainCodeReviewReport(currentRepoAsList) } @@ -1130,6 +1131,92 @@ class LeVADocumentUseCase extends DocGenUseCase { return this.createDocument(documentType, repo, data_, [:], modifier, getDocumentTemplateName(documentType, repo), watermarkText) } + /** + * Helm releases become top level elements, tailor deployments are left alone. + */ + @SuppressWarnings('CyclomaticComplexity') + Map assembleDeployments(Map> deployments) { + // collect helm releases + Map> deploymentsMeansHelm = deployments.findAll { + it.key.endsWith('-deploymentMean') && it.value.type == "helm" + } + Map> deploymentsHelmByRelease = [:] + deploymentsMeansHelm.each { String deploymentName, Map deploymentMeanHelm -> + deploymentsHelmByRelease << [(deploymentMeanHelm.helmReleaseName): deploymentMeanHelm] + } + Set componentsCoveredByHelm = [] + deploymentsHelmByRelease.each { String release, Map deploymentMeanHelm -> + List> resources = deploymentMeanHelm?.helmStatus?.resources ?: [] + resources.each { + if (!it?.kind) { + logger.debug("skipping resource - no kind defined: ${it}") + return + } + if (!it?.name) { + logger.debug("skipping resource - no name defined: ${it}") + return + } + if (!os.isDeploymentKind(it.kind)) { + return + } + componentsCoveredByHelm << it.name + } + } + + Set helmReleasesCovered = [] + Map deploymentsForTir = [:] + deployments.each { String deploymentName, Map deployment -> + if (deploymentName.endsWith('-deploymentMean')) { + if (deployment.type == "helm") { + String releaseName = deployment?.helmReleaseName + if (!releaseName) { + logger.warn("No helmReleaseName name in ${deploymentName}: skipping") + return + } + if (releaseName in helmReleasesCovered) { + return + } + def withoutHelmStatus = deployment.findAll { k, v -> k != 'helmStatus' } + deploymentsForTir.put("${releaseName}-deploymentMean".toString(), withoutHelmStatus) + def helmStatus = assembleHelmStatus(deployment?.helmStatus ?: [:]) + deploymentsForTir.put("${releaseName}-deploymentStatus".toString(), helmStatus) + helmReleasesCovered << (releaseName) + } else { + deploymentsForTir.put(deploymentName, deployment) + } + } else { + if (deploymentName in componentsCoveredByHelm) { + return + } else { + deploymentsForTir.put(deploymentName, deployment.findAll { k, v -> k != 'podName' }) + } + } + } + logger.debug("createTIR - assembled deployments data:${prettyPrint(toJson(deploymentsForTir))}") + deploymentsForTir + } + + @SuppressWarnings('UnnecessaryCast') + Map assembleHelmStatus(Map helmStatus) { + def resources = helmStatus?.resources ?: [] as List > + def properResources = resources.findAll { + it?.kind && it?.name + } + def byKind = properResources.groupBy { it.kind } + String formattedResourcesByKind = byKind.collect { kind, resourcesOfKind -> + "${kind}: " + resourcesOfKind.collect { it.name }.join(', ') + }.sort().join(', ') + + def assembledHelmStatus = helmStatus.collectEntries { k, v -> + if (k == 'resources') { + [(k): formattedResourcesByKind ] + } else { + [(k): v] + } + } + return assembledHelmStatus + } + String createOverallTIR(Map repo = null, Map data = null) { def documentTypeName = DOCUMENT_TYPE_NAMES[DocumentType.OVERALL_TIR as String] def metadata = this.getDocumentMetadata(documentTypeName) diff --git a/src/org/ods/orchestration/util/DeploymentDescriptor.groovy b/src/org/ods/orchestration/util/DeploymentDescriptor.groovy index 76608a5ba..32170204d 100644 --- a/src/org/ods/orchestration/util/DeploymentDescriptor.groovy +++ b/src/org/ods/orchestration/util/DeploymentDescriptor.groovy @@ -17,6 +17,19 @@ class DeploymentDescriptor { this.createdByBuild = createdByBuild ?: '' } + /** + * This function takes a map of deployments and returns a stripped down version of it. + * + * deploymentMean information is moved from a key of the form '${resource-name}-deploymentMean' into the Map for + * key '${resource-name}' under key 'deploymentMean'. + * + * For each deployment, it processes its containers and modifies the image name based on certain conditions. + * If the first part of the image name is in the EXCLUDE_NAMESPACES_FROM_IMPORT list, it keeps the full image name. + * Otherwise, it only keeps the last part of the image name. + * + * @param deployments The original deployments map whose values are Maps themselves + * @return A new stripped down deployments map. + */ static Map stripDeployments(Map deployments) { def strippedDownDeployments = [:] def deploymentMeanPostfix = 'deploymentMean' diff --git a/src/org/ods/services/OpenShiftService.groovy b/src/org/ods/services/OpenShiftService.groovy index cf0cf7f39..2c1430640 100644 --- a/src/org/ods/services/OpenShiftService.groovy +++ b/src/org/ods/services/OpenShiftService.groovy @@ -5,6 +5,8 @@ import groovy.json.JsonOutput import groovy.json.JsonSlurperClassic import groovy.transform.TypeChecked import groovy.transform.TypeCheckingMode +import org.ods.util.HelmStatusData +import org.ods.util.HelmStatusSimpleData import org.ods.util.ILogger import org.ods.util.IPipelineSteps import org.ods.util.PodData @@ -155,6 +157,33 @@ class OpenShiftService { } } + HelmStatusSimpleData helmStatus( + String project, + String release + ) { + def helmStatusData = retrieveHelmStatus(project, release) + HelmStatusSimpleData.from(helmStatusData) + } + + HelmStatusData retrieveHelmStatus( + String project, + String release + ) { + try { + def helmStdout = steps.sh( + script: "helm -n ${project} status ${release} --show-resources -o json", + label: "Gather Helm status for release ${release} in ${project}", + returnStdout: true + ).toString().trim() + def object = new JsonSlurperClassic().parseText(helmStdout) + def helmStatusData = HelmStatusData.fromJsonObject(object) + helmStatusData + } catch (Exception e) { + throw new RuntimeException("Helm status Failed (${e.message})!" + + "Helm could not gather status of ${release} in ${project}") + } + } + @SuppressWarnings(['LineLength', 'ParameterCount']) void tailorApply(String project, Map target, String paramFile, List params, List preserve, String tailorPrivateKeyFile, boolean verify) { def verifyFlag = verify ? '--verify' : '' @@ -429,7 +458,12 @@ class OpenShiftService { ) } - // Returns data about the pods (replicas) of the deployment. + boolean isDeploymentKind(String kind) { + boolean b = kind in [DEPLOYMENTCONFIG_KIND, DEPLOYMENT_KIND] + b + } + + // Returns data about the pods (replicas) of the deployment. // If not all pods are running until the retries are exhausted, // an exception is thrown. List getPodDataForDeployment(String project, String kind, String podManagerName, int retries) { @@ -449,7 +483,7 @@ class OpenShiftService { throw new RuntimeException("Could not find 'running' pod(s) with label '${label}'") } - // getResourcesForComponent returns a map in which each kind is mapped to a list of resources. + // getResourcesForComponent returns a map in which each kind is mapped to a list of resources names. Map> getResourcesForComponent(String project, List kinds, String selector) { def items = steps.sh( script: """oc -n ${project} get ${kinds.join(',')} \ @@ -1256,7 +1290,7 @@ class OpenShiftService { ) } - @SuppressWarnings(['CyclomaticComplexity', 'AbcMetric']) + @SuppressWarnings(['CyclomaticComplexity', 'AbcMetric', 'LineLength']) @TypeChecked(TypeCheckingMode.SKIP) private List extractPodData(Map podJson) { List pods = [] @@ -1270,16 +1304,17 @@ class OpenShiftService { if (podOCData.metadata?.generateName) { pod.deploymentId = podOCData.metadata.generateName - ~/-$/ // Trim dash suffix } - pod.podNode = podOCData.spec?.nodeName ?: 'N/A' - pod.podIp = podOCData.status?.podIP ?: 'N/A' pod.podStatus = podOCData.status?.phase ?: 'N/A' - pod.podStartupTimeStamp = podOCData.status?.startTime ?: 'N/A' pod.containers = [:] // We need to get the image SHA from the containerStatuses, and not // from the pod spec because the pod spec image field is optional // and may not contain an image SHA, but e.g. a tag, depending on // the pod manager (e.g. ReplicationController, ReplicaSet). See - // https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#container-v1-core. + // Comment above from version 1.19 no longer available online + // Cluster currently run on 1.27 + // docs for 1.27: https://v1-27.docs.kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/ + // latest: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#Container + // example of an imageID: "image-registry.openshift-image-registry.svc:5000/guardians-test/core-standalone@sha256:6a2290e522133866cafc4864c30ea6ba591de4238a865936e3e4f953d60c173a" podOCData.spec?.containers?.each { container -> podOCData.status?.containerStatuses?.each { containerStatus -> if (containerStatus.name == container.name) { diff --git a/src/org/ods/util/HelmStatusContainerStatusData.groovy b/src/org/ods/util/HelmStatusContainerStatusData.groovy new file mode 100644 index 000000000..e3cb46c75 --- /dev/null +++ b/src/org/ods/util/HelmStatusContainerStatusData.groovy @@ -0,0 +1,32 @@ +package org.ods.util + +import groovy.transform.TypeChecked + +@TypeChecked +class HelmStatusContainerStatusData { + + String name + String image + String imageID + + @SuppressWarnings(['Instanceof']) + HelmStatusContainerStatusData(Map map) { + def missingKeys = [] + def badTypes = [] + + def stringTypes = [ "name", "image", "imageID"] + for (att in stringTypes) { + if (!map.containsKey(att)) { + missingKeys << att + } else if (!(map[att] instanceof String)) { + badTypes << "${att}: expected String, found ${map[att].getClass()}" + } + } + HelmStatusData.handleMissingKeysOrBadTypes(missingKeys, badTypes) + + this.name = map['name'] as String + this.image = map['image'] as String + this.imageID = map['imageID'] as String + } + +} diff --git a/src/org/ods/util/HelmStatusData.groovy b/src/org/ods/util/HelmStatusData.groovy new file mode 100644 index 000000000..f1127f46a --- /dev/null +++ b/src/org/ods/util/HelmStatusData.groovy @@ -0,0 +1,181 @@ +package org.ods.util + +import com.cloudbees.groovy.cps.NonCPS +import groovy.transform.PackageScope +import groovy.transform.TypeChecked + +// Relevant data returned by helm status --show-resources -o json +@TypeChecked +class HelmStatusData { + + // release name + String name + String namespace + + HelmStatusInfoData info + + // version is an integer but we map it to a string. + String version + + @SuppressWarnings(['IfStatementBraces']) + @NonCPS + static HelmStatusData fromJsonObject(Object object) { + try { + def jsonObject = ensureMap(object, "") + + Map status = [:] + + // Constructors of classes receiving the data catches missing keys or unexpected types and + // report them in summary as IllegalArgumentException. + if (jsonObject.name) status.name = jsonObject.name + if (jsonObject.version) status.version = jsonObject.version + if (jsonObject.namespace) status.namespace = jsonObject.namespace + + def infoObject = ensureMap(jsonObject.info, "info") + Map info = [:] + + if (infoObject.status) info.status = infoObject.status + if (infoObject.description) info.description = infoObject.description + if (infoObject['last_deployed']) info.lastDeployed = infoObject['last_deployed'] + + def resourcesObject = ensureMap(infoObject.resources, "info.resources") + // All resources are in an json object which organize resources by keys + // Examples are "v1/Cluster", "v1/ConfigMap" "v1/Deployment", "v1/Pod(related)" + // For these keys the map contains list of resources. + Map> resourcesMap = [:] + for (entry in resourcesObject.entrySet()) { + def key = entry.key as String + def resourceList = ensureList(entry.value, "info.resources.${key}") + def resources = [] + resourceList.eachWithIndex { resourceJsonObject, i -> + def resourceData = fromJsonObjectHelmStatusResource( + resourceJsonObject, + "info.resources.${key}.[${i}]") + if (resourceData != null) { + resources << resourceData + } + } + resourcesMap.put(key, resources) + } + info.resources = resourcesMap + status.info = new HelmStatusInfoData(info) + new HelmStatusData(status) + } catch (Exception e) { + throw new IllegalArgumentException( + "Unexpected helm status information in JSON at 'info': ${e.getMessage()}") + } + } + + @SuppressWarnings(['Instanceof']) + @NonCPS + private static Map ensureMap(Object obj, String context) { + if (obj == null) { + return [:] + } + if (!(obj instanceof Map)) { + def msg = context ? + "${context}: expected JSON object, found ${obj.getClass()}" : + "Expected JSON object, found ${obj.getClass()}" + + throw new IllegalArgumentException(msg) + } + obj as Map + } + + @SuppressWarnings(['Instanceof']) + @NonCPS + private static List ensureList(Object obj, String context) { + if (obj == null) { + return [] + } + if (!(obj instanceof List)) { + throw new IllegalArgumentException( + "${context}: expected JSON array, found ${obj.getClass()}") + } + obj as List + } + + @SuppressWarnings(['IfStatementBraces', 'Instanceof']) + @NonCPS + private static HelmStatusResourceData fromJsonObjectHelmStatusResource( + resourceJsonObject, String context) { + def resourceObject = ensureMap(resourceJsonObject, context) + Map resource = [:] + if (resourceObject.apiVersion) resource.apiVersion = resourceObject.apiVersion + if (resourceObject.kind) resource.kind = resourceObject.kind + Map metadataObject = ensureMap( + resourceObject.metadata, "${context}.metadata") + if (metadataObject.name) resource.metadataName = metadataObject.name + if (resource.kind == "PodList") { + return null + } + if (resource.kind == "Pod") { + def statusObject = ensureMap(resourceObject.status, + "${context}.status") + List containerStatuses = [] + def containerStatusesJsonArray = ensureList(statusObject.containerStatuses, + "${context}.status.containerStatuses") + containerStatusesJsonArray.eachWithIndex { cs, int csi -> + def cso = ensureMap(cs, "${context}.status.containerStatuses[${csi}]") + Map containerStatus = [:] + if (cso.name) containerStatus.name = cso.name + if (cso.image) containerStatus.image = cso.image + if (cso.imageID) containerStatus.imageID = cso.imageID + containerStatuses << new HelmStatusContainerStatusData(cso) + } + resource.containerStatuses = containerStatuses + } + try { + new HelmStatusResourceData(resource) + } catch (Exception e) { + throw new IllegalArgumentException( + "Unexpected helm status JSON at '${context}': ${e.getMessage()}") + } + } + + @SuppressWarnings(['PublicMethodsBeforeNonPublicMethods']) + @NonCPS + static @PackageScope void handleMissingKeysOrBadTypes(List missingKeys, List badTypes) { + if (missingKeys || badTypes) { + def msgs = [] + if (missingKeys) { + msgs << "Missing keys: ${missingKeys.join(', ')}" + } + if (badTypes) { + msgs << "Bad types: ${badTypes.join(', ')}" + } + throw new IllegalArgumentException(msgs.join(".")) + } + } + + @SuppressWarnings(['Instanceof']) + HelmStatusData(Map map) { + def missingKeys = [] + def badTypes = [] + + def stringTypes = [ "name", "namespace"] + for (att in stringTypes) { + if (!map.containsKey(att)) { + missingKeys << att + } else if (!(map[att] instanceof String)) { + badTypes << "${att}: expected String, found ${map[att].getClass()}" + } + } + if (!map.containsKey('info')) { + missingKeys << 'info' + } else if (!(map['info'] instanceof HelmStatusInfoData)) { + badTypes << "info: expected HelmStatusInfoData, found ${map['info'].getClass()}" + } + + if (!map.containsKey('version')) { + missingKeys << 'version' + } + handleMissingKeysOrBadTypes(missingKeys, badTypes) + + this.name = map['name'] as String + this.namespace = map['namespace'] as String + this.info = map['info'] as HelmStatusInfoData + this.version = map['version'].toString() + } + +} diff --git a/src/org/ods/util/HelmStatusInfoData.groovy b/src/org/ods/util/HelmStatusInfoData.groovy new file mode 100644 index 000000000..e0525595e --- /dev/null +++ b/src/org/ods/util/HelmStatusInfoData.groovy @@ -0,0 +1,48 @@ +package org.ods.util + +import groovy.transform.TypeChecked + +//Relevant helm status info data we want to capture +@TypeChecked +class HelmStatusInfoData { + + // deployment status - see `helm status --help` for full list + // Example: "deployed" + String status + // description of the release (can be completion message or error message) + // Example: "Upgrade complete" + String description + // last-deployed field. + // Example: "2024-03-04T15:21:09.34520527Z" + String lastDeployed + + Map> resources + + @SuppressWarnings(['Instanceof']) + HelmStatusInfoData(Map map) { + def missingKeys = [] + def badTypes = [] + + def stringTypes = [ "status", "description", "lastDeployed"] + for (att in stringTypes) { + if (!map.containsKey(att)) { + missingKeys << att + } else if (!(map[att] instanceof String)) { + badTypes << "${att}: expected String, found ${map[att].getClass()}" + } + } + if (!map.containsKey('resources')) { + missingKeys << 'resources' + } else if (!(map['resources'] instanceof Map)) { + badTypes << "resources: expected Map, found ${map['resources'].getClass()}" + } + + HelmStatusData.handleMissingKeysOrBadTypes(missingKeys, badTypes) + + this.status = map['status'] as String + this.description = map['description'] as String + this.lastDeployed = map['lastDeployed'] as String + this.resources = map['resources'] as Map + } + +} diff --git a/src/org/ods/util/HelmStatusResource.groovy b/src/org/ods/util/HelmStatusResource.groovy new file mode 100644 index 000000000..7069d22ff --- /dev/null +++ b/src/org/ods/util/HelmStatusResource.groovy @@ -0,0 +1,25 @@ +package org.ods.util + +import com.cloudbees.groovy.cps.NonCPS +import groovy.transform.TypeChecked + +@TypeChecked +class HelmStatusResource { + + String kind + String name + + @NonCPS + Map toMap() { + [ + kind: kind, + name: name, + ] + } + + @NonCPS + String toString() { + toMap().toMapString() + } + +} diff --git a/src/org/ods/util/HelmStatusResourceData.groovy b/src/org/ods/util/HelmStatusResourceData.groovy new file mode 100644 index 000000000..d5a8c7492 --- /dev/null +++ b/src/org/ods/util/HelmStatusResourceData.groovy @@ -0,0 +1,39 @@ +package org.ods.util + +import groovy.transform.TypeChecked + +//Relevant helm status resource data we want to capture. This is inside the info data. +@TypeChecked +class HelmStatusResourceData { + + String apiVersion + String kind + String metadataName + // for kind "Pod" containerStatusData may be present + List containerStatuses = [] + + @SuppressWarnings(['Instanceof']) + HelmStatusResourceData(Map map) { + def missingKeys = [] + def badTypes = [] + + def stringTypes = [ "apiVersion", "kind", "metadataName"] + for (att in stringTypes) { + if (!map.containsKey(att)) { + missingKeys << att + } else if (!(map[att] instanceof String)) { + badTypes << "${att}: expected String, found ${map[att].getClass()}" + } + } + + HelmStatusData.handleMissingKeysOrBadTypes(missingKeys, badTypes) + + this.apiVersion = map['apiVersion'] as String + this.kind = map['kind'] as String + this.metadataName = map['metadataName'] as String + if (map.containsKey('containerStatuses')) { + this.containerStatuses = (map['containerStatuses'] as List) + } + } + +} diff --git a/src/org/ods/util/HelmStatusSimpleData.groovy b/src/org/ods/util/HelmStatusSimpleData.groovy new file mode 100644 index 000000000..12a70a660 --- /dev/null +++ b/src/org/ods/util/HelmStatusSimpleData.groovy @@ -0,0 +1,70 @@ +package org.ods.util + +import com.cloudbees.groovy.cps.NonCPS +import groovy.transform.TypeChecked + +@TypeChecked +class HelmStatusSimpleData { + + String releaseName + String releaseRevision + String namespace + String deployStatus + String deployDescription + String lastDeployed + List resources + + static HelmStatusSimpleData fromJsonObject(Object jsonObject) { + from(HelmStatusData.fromJsonObject(jsonObject)) + } + + @SuppressWarnings(['NestedForLoop']) + static HelmStatusSimpleData from(HelmStatusData status) { + def simpleResources = [] + for (resourceList in status.info.resources.values()) { + for (hsr in resourceList) { + simpleResources << new HelmStatusResource(kind: hsr.kind, name: hsr.metadataName) + } + } + new HelmStatusSimpleData( + releaseName: status.name, + releaseRevision: status.version, + namespace: status.namespace, + deployStatus: status.info.status, + deployDescription: status.info.description, + lastDeployed: status.info.lastDeployed, + resources: simpleResources,) + } + + Map> getResourcesByKind(List kinds) { + def deploymentResources = resources.findAll { it.kind in kinds } + Map> resourcesByKind = [:] + deploymentResources.each { + if (!resourcesByKind.containsKey(it.kind)) { + resourcesByKind[it.kind] = [] + } + resourcesByKind[it.kind] << it.name + } + resourcesByKind + } + + @NonCPS + Map toMap() { + def result = [ + releaseName: releaseName, + releaseRevision: releaseRevision, + namespace: namespace, + deployStatus: deployStatus, + deployDescription: deployDescription, + lastDeployed: lastDeployed, + resources: resources.collect { [kind: it.kind, name: it.name] } + ] + result + } + + @NonCPS + String toString() { + toMap().toMapString() + } + +} diff --git a/src/org/ods/util/PodData.groovy b/src/org/ods/util/PodData.groovy index dd26b8903..87edd4c0a 100644 --- a/src/org/ods/util/PodData.groovy +++ b/src/org/ods/util/PodData.groovy @@ -24,22 +24,10 @@ class PodData { // Example: foo-3 String deploymentId - // podNode is the node name on which of the pod, equal to .spec.nodeName. - // Example: ip-172-32-53-123.eu-west-1.compute.internal - String podNode - - // podIp is the IP of the pod, equal to .status.podIP. - // Example: 10.132.16.73 - String podIp - // podStatus is the status phase of the pod, equal to .status.phase // Example: Running String podStatus - // podStartupTimeStamp is the start time of the pod, equal to .status.startTime. - // Example: 2020-11-02T10:57:35Z - String podStartupTimeStamp - // containers is a map of container names to their image. // Example: [bar: '172.30.21.193:5000/foo/bar@sha256:a828...4389'] Map containers @@ -51,10 +39,7 @@ class PodData { podNamespace: podNamespace, podMetaDataCreationTimestamp: podMetaDataCreationTimestamp, deploymentId: deploymentId, - podNode: podNode, - podIp: podIp, podStatus: podStatus, - podStartupTimeStamp: podStartupTimeStamp, containers: containers, ] } From 449dd9e75ec8e41c730eef3010ca29ddda41d699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrich=20Kr=C3=A4mer?= Date: Mon, 1 Jul 2024 13:10:17 +0000 Subject: [PATCH 02/36] Tests were previously not pushed. --- .../HelmDeploymentStrategySpec.groovy | 90 +- .../usecase/LeVADocumentUseCaseSpec.groovy | 86 +- .../ods/services/OpenShiftServiceSpec.groovy | 66 +- test/groovy/util/HelmStatusSpec.groovy | 45 + ...StageRolloutOpenShiftDeploymentSpec.groovy | 36 +- test/resources/deployments-data.json | 112 ++ test/resources/helmstatus.json | 1422 +++++++++++++++++ 7 files changed, 1782 insertions(+), 75 deletions(-) create mode 100644 test/groovy/util/HelmStatusSpec.groovy create mode 100644 test/resources/deployments-data.json create mode 100644 test/resources/helmstatus.json diff --git a/test/groovy/org/ods/component/HelmDeploymentStrategySpec.groovy b/test/groovy/org/ods/component/HelmDeploymentStrategySpec.groovy index 81edfd3b5..f4f7e73d3 100644 --- a/test/groovy/org/ods/component/HelmDeploymentStrategySpec.groovy +++ b/test/groovy/org/ods/component/HelmDeploymentStrategySpec.groovy @@ -1,11 +1,14 @@ package org.ods.component +import groovy.json.JsonSlurperClassic import org.ods.services.JenkinsService import org.ods.services.OpenShiftService import org.ods.services.ServiceRegistry +import org.ods.util.HelmStatusSimpleData import org.ods.util.Logger import org.ods.util.PodData import spock.lang.Shared +import util.FixtureHelper import vars.test_helper.PipelineSpockTestBase class HelmDeploymentStrategySpec extends PipelineSpockTestBase { @@ -21,45 +24,75 @@ class HelmDeploymentStrategySpec extends PipelineSpockTestBase { buildUrl: 'https://jenkins.example.com/job/foo-cd/job/foo-cd-bar-master/11/console', buildTime: '2020-03-23 12:27:08 +0100', odsSharedLibVersion: '2.x', - projectId: 'foo', - componentId: 'bar', - cdProject: 'foo-cd', + projectId: 'guardians', + componentId: 'core', + cdProject: 'guardians-cd', artifactUriStore: [builds: [bar: [:]]] ] def "rollout: check deploymentMean"() { given: + def expectedDeploymentMean = [ + type : "helm", + selector : "app=guardians-core", + chartDir : "chart", + helmReleaseName : "core", + helmEnvBasedValuesFiles: [], + helmValuesFiles : ["values.yaml"], + helmValues : [:], + helmDefaultFlags : ["--install", "--atomic"], + helmAdditionalFlags : [], + helmStatus : [ + releaseName : "standalone-app", + releaseRevision : "43", + namespace : "guardians-test", + deployStatus : "deployed", + deployDescription: "Upgrade complete", + lastDeployed : "2024-03-04T15:21:09.34520527Z", + resources : [ + [kind: "ConfigMap", name: "core-appconfig-configmap"], + [kind: "Deployment", name: "core"], + [kind: "Deployment", name: "standalone-gateway"], + [kind: "Service", name: "core"], + [kind: "Service", name: "standalone-gateway"], + [kind: "Cluster", name: "edb-cluster"], + [kind: "Secret", name: "core-rsa-key-secret"], + [kind: "Secret", name: "core-security-exandradev-secret"], + [kind: "Secret", name: "core-security-unify-secret"] + ] + ] + ] def expectedDeploymentMeans = [ - "builds": [:], - "deployments": [ - "bar-deploymentMean": [ - "type": "helm", - "selector": "app=foo-bar", - "chartDir": "chart", - "helmReleaseName": "bar", - "helmEnvBasedValuesFiles": [], - "helmValuesFiles": ["values.yaml"], - "helmValues": [:], - "helmDefaultFlags": ["--install", "--atomic"], - "helmAdditionalFlags": [] + builds : [:], + deployments: [ + "core-deploymentMean" : expectedDeploymentMean, + "core" : [ + podName : null, + podNamespace : null, + podMetaDataCreationTimestamp: null, + deploymentId : "core-124", + podStatus : null, + containers : null + ], + "standalone-gateway-deploymentMean": expectedDeploymentMean, + "standalone-gateway" : [ + podName : null, + podNamespace : null, + podMetaDataCreationTimestamp: null, + deploymentId : "core-124", + podStatus : null, + containers : null, ], - "bar":[ - "podName": null, - "podNamespace": null, - "podMetaDataCreationTimestamp": null, - "deploymentId": "bar-124", - "podNode": null, - "podIp": null, - "podStatus": null, - "podStartupTimeStamp": null, - "containers": null, - ] ] ] + def config = [:] - def ctxData = contextData + [environment: 'dev', targetProject: 'foo-dev', openshiftRolloutTimeoutRetries: 5, chartDir: 'chart'] + def helmStatusFile = new FixtureHelper().getResource("helmstatus.json") + def helmStatus = HelmStatusSimpleData.fromJsonObject(new JsonSlurperClassic().parseText(helmStatusFile.text)) + + def ctxData = contextData + [environment: 'test', targetProject: 'guardians-test', openshiftRolloutTimeoutRetries: 5, chartDir: 'chart'] IContext context = new Context(null, ctxData, logger) OpenShiftService openShiftService = Mock(OpenShiftService.class) openShiftService.checkForPodData(*_) >> [new PodData([deploymentId: "${contextData.componentId}-124"])] @@ -72,8 +105,7 @@ class HelmDeploymentStrategySpec extends PipelineSpockTestBase { HelmDeploymentStrategy strategy = Spy(HelmDeploymentStrategy, constructorArgs: [null, context, config, openShiftService, jenkinsService, logger]) when: - def deploymentResources = [Deployment: ['bar']] - def rolloutData = strategy.getRolloutData(deploymentResources) + strategy.getRolloutData(helmStatus) def actualDeploymentMeans = context.getBuildArtifactURIs() diff --git a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy index 799c04509..b643ed9b2 100644 --- a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy +++ b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy @@ -1,6 +1,7 @@ package org.ods.orchestration.usecase import groovy.json.JsonSlurper +import groovy.json.JsonSlurperClassic import groovy.util.logging.Log import groovy.util.logging.Slf4j import org.apache.commons.io.FileUtils @@ -8,6 +9,7 @@ import org.junit.Rule import org.junit.rules.TemporaryFolder import org.ods.util.ILogger import org.ods.services.ServiceRegistry +import org.ods.util.PodData import spock.lang.Unroll import org.ods.services.JenkinsService @@ -890,6 +892,7 @@ class LeVADocumentUseCaseSpec extends SpecHelper { 1 * usecase.updateJiraDocumentationTrackingIssue(documentType, uri, "${docHistory.getVersion()}") } + def "create IVR"() { given: jiraUseCase = Spy(new JiraUseCase(project, steps, util, Mock(JiraService), logger)) @@ -960,10 +963,10 @@ class LeVADocumentUseCaseSpec extends SpecHelper { def version = (odsRepoType == MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_CODE) ? 'WIP' : '1.0' def expectedSpecifications = systemDesignSpec - ? ["key":"NET-128", - "req_key":"NET-125", - "description":systemDesignSpec] - : null + ? ["key":"NET-128", + "req_key":"NET-125", + "description":systemDesignSpec] + : null def expectedComponents = ["key":"Technology-demo-app-catalogue", "nameOfSoftware":"demo-app-catalogue", "componentType":componentTypeLong, @@ -1073,7 +1076,7 @@ class LeVADocumentUseCaseSpec extends SpecHelper { "risks": ["NET-126"], "tests": ["NET-127"] }''', - ''' + ''' { "key": "NET-128", "id": "128", @@ -1087,7 +1090,7 @@ class LeVADocumentUseCaseSpec extends SpecHelper { "risks": ["NET-126"], "tests": ["NET-127"] }''', - ''' + ''' { "key": "NET-128", "id": "128", @@ -1278,6 +1281,43 @@ class LeVADocumentUseCaseSpec extends SpecHelper { 1 * usecase.createDocument(documentType, repo, _, [:], _, documentTemplate, watermarkText) } + def "assemble deploymentInfo for TIR"() { + given: + def file = new FixtureHelper().getResource("deployments-data.json") + os.isDeploymentKind(*_) >> true + + when: + def deploymentsData = new JsonSlurperClassic().parseText(file.text) + def assembledData = usecase.assembleDeployments(deploymentsData.deployments) + + then: + + assembledData["backend-helm-monorepo-deploymentMean"] == [ + chartDir : "chart", + repoId : "backend-helm-monorepo", + helmAdditionalFlags: [], + helmEnvBasedValuesFiles :[], + helmValues :[ + registry :"image-registry.openshift-image-registry.svc:5000", + componentId :"backend-helm-monorepo" + ], + helmDefaultFlags :["--install", "--atomic"], + helmReleaseName :"backend-helm-monorepo", + selector :"app.kubernetes.io/instance=backend-helm-monorepo", + helmValuesFiles :["values.yaml"], + type :"helm", ] + + assembledData["backend-helm-monorepo-deploymentStatus"] == [ + releaseRevision :"2", + releaseName :"backend-helm-monorepo", + namespace: "kraemerh-dev", + deployDescription :"Upgrade complete", + resources : "Deployment: backend-helm-monorepo-chart-component-a, backend-helm-monorepo-chart-component-b, Service: backend-helm-monorepo-chart", + deployStatus :"deployed", + lastDeployed :"2024-06-26T12:59:51.270713404Z" + ] + } + def "create overall DTR"() { given: // Argument Constraints @@ -1657,25 +1697,25 @@ class LeVADocumentUseCaseSpec extends SpecHelper { def "order steps"() { given: def testIssue = [ key: "JIRA-1" , - steps: [ - [ - orderId: 2, - data: "N/A" - ], - [ - orderId: 1, - data: "N/A" - ] - ]] + steps: [ + [ + orderId: 2, + data: "N/A" + ], + [ + orderId: 1, + data: "N/A" + ] + ]] - when: - LeVADocumentUseCase leVADocumentUseCase = new LeVADocumentUseCase(null, null, null, - null, null, null, null, null, null, null, - null, null, null, null) - def ordered = leVADocumentUseCase.sortTestSteps(testIssue.steps) + when: + LeVADocumentUseCase leVADocumentUseCase = new LeVADocumentUseCase(null, null, null, + null, null, null, null, null, null, null, + null, null, null, null) + def ordered = leVADocumentUseCase.sortTestSteps(testIssue.steps) - then: - ordered.get(0).orderId == 1 + then: + ordered.get(0).orderId == 1 } def "referenced documents version"() { diff --git a/test/groovy/org/ods/services/OpenShiftServiceSpec.groovy b/test/groovy/org/ods/services/OpenShiftServiceSpec.groovy index efbabf285..8a96b37cf 100644 --- a/test/groovy/org/ods/services/OpenShiftServiceSpec.groovy +++ b/test/groovy/org/ods/services/OpenShiftServiceSpec.groovy @@ -2,6 +2,7 @@ package org.ods.services import groovy.json.JsonSlurperClassic +import org.ods.util.HelmStatusSimpleData import org.ods.util.ILogger import org.ods.util.IPipelineSteps import org.ods.util.Logger @@ -189,6 +190,10 @@ class OpenShiftServiceSpec extends SpecHelper { then: results.size() == expected.size() for (int i = 0; i < results.size(); i++) { + // Note: podIp, podNode and podStartupTimeStamp are no longer in type PodData. + // Each result is only a subset of the corresponding expected data. + // While it can surpise that the == assertion below would not catch this, + // it actually comes in handy so we can leave the original data in place. results[i].toMap() == expected[i] } } @@ -200,25 +205,17 @@ class OpenShiftServiceSpec extends SpecHelper { def file = new FixtureHelper().getResource("pods.json") List expected = [ [ - podName : 'example-be-token-6fcb4d85d6-7jr2r', podNamespace : 'proj-dev', podMetaDataCreationTimestamp: '2023-07-24T11:58:29Z', deploymentId : 'example-be-token-6fcb4d85d6', - podNode : 'ip-10-32-10-30.eu-west-1.compute.internal', - podIp : '192.0.2.172', podStatus : 'Running', - podStartupTimeStamp : '2023-07-24T11:58:29Z', containers : ['be-token': 'image-registry.openshift-image-registry.svc:5000/proj-dev/example-be-token@sha256:cc5e57f98ee789429384e8df2832a89fbf1092b724aa8f3faff2708e227cb39e'] ], [ - podName : 'example-be-token-6fcb4d85d6-ndp8x', podNamespace : 'proj-dev', podMetaDataCreationTimestamp: '2023-07-24T11:58:29Z', deploymentId : 'example-be-token-6fcb4d85d6', - podNode : 'ip-10-32-9-69.eu-west-1.compute.internal', - podIp : '192.0.2.171', podStatus : 'Running', - podStartupTimeStamp : '2023-07-24T11:58:29Z', containers : ['be-token': 'image-registry.openshift-image-registry.svc:5000/proj-dev/example-be-token@sha256:cc5e57f98ee789429384e8df2832a89fbf1092b724aa8f3faff2708e227cb39e'] ] ] @@ -250,16 +247,61 @@ class OpenShiftServiceSpec extends SpecHelper { podNamespace: 'foo-dev', podMetaDataCreationTimestamp: '2020-05-18T10:43:56Z', deploymentId: 'bar-164', - podNode: 'ip-172-31-61-82.eu-central-1.compute.internal', - podIp: '10.128.17.92', podStatus: 'Running', - podStartupTimeStamp: '2020-05-18T10:43:56Z', containers: [ bar: '172.30.21.196:5000/foo-dev/bar@sha256:07ba1778e7003335e6f6e0f809ce7025e5a8914dc5767f2faedd495918bee58a' ] ] } + def "helm status data extraction"() { + given: + def steps = Spy(util.PipelineSteps) + def service = new OpenShiftService(steps, new Logger(steps, false)) + def helmJsonText = new FixtureHelper().getResource("helmstatus.json").text + + when: + def helmStatusData = service.retrieveHelmStatus('guardians-test', 'standalone-app') +// OpenShiftService.DEPLOYMENT_KIND, OpenShiftService.DEPLOYMENTCONFIG_KIND,]) + then: + 1 * steps.sh( + script: 'helm -n guardians-test status standalone-app --show-resources -o json', + label: 'Gather Helm status for release standalone-app in guardians-test', + returnStdout: true, + ) >> helmJsonText + helmStatusData.name == 'standalone-app' + helmStatusData.namespace == 'guardians-test' + } + + def "helm status data extraction bad content"() { + given: + def steps = Spy(util.PipelineSteps) + def service = new OpenShiftService(steps, new Logger(steps, false)) + def helmJsonText = """ +{ + "name": "standalone-app", + "info": { + "first_deployed": "2022-12-19T09:44:32.164490076Z", + "last_deployed": "2024-03-04T15:21:09.34520527Z", + "deleted": "", + "description": "Upgrade complete", + "status": "deployed", + "resources" : {} + } +} + """ + when: + def helmStatusData = service.helmStatus('guardians-test', 'standalone-app') +// OpenShiftService.DEPLOYMENT_KIND, OpenShiftService.DEPLOYMENTCONFIG_KIND,]) + then: + 1 * steps.sh( + script: 'helm -n guardians-test status standalone-app --show-resources -o json', + label: 'Gather Helm status for release standalone-app in guardians-test', + returnStdout: true, + ) + thrown RuntimeException + } + def "helm upgrade"() { given: def steps = Spy(util.PipelineSteps) @@ -752,7 +794,7 @@ class OpenShiftServiceSpec extends SpecHelper { when: def result = service.getConsoleUrl(steps) - then: + then: result == routeUrl } diff --git a/test/groovy/util/HelmStatusSpec.groovy b/test/groovy/util/HelmStatusSpec.groovy new file mode 100644 index 000000000..de63030a9 --- /dev/null +++ b/test/groovy/util/HelmStatusSpec.groovy @@ -0,0 +1,45 @@ +package org.ods.util + +import groovy.json.JsonSlurperClassic +import org.ods.services.OpenShiftService +import util.FixtureHelper +import util.SpecHelper + +class HelmStatusSpec extends SpecHelper { + def "helm status parsing"() { + given: + def file = new FixtureHelper().getResource("helmstatus.json") + + when: + def helmParsedStatus = HelmStatusData.fromJsonObject(new JsonSlurperClassic().parseText(file.text)) + def helmStatus = HelmStatusSimpleData.from(helmParsedStatus) + def simpleStatusMap = helmStatus.toMap() + def simpleStatusNoResources = simpleStatusMap.findAll { k,v -> k != "resources"} + def deploymentResources = helmStatus.getResourcesByKind([ + OpenShiftService.DEPLOYMENT_KIND, OpenShiftService.DEPLOYMENTCONFIG_KIND,]) + then: + simpleStatusNoResources == [ + releaseName: 'standalone-app', + releaseRevision: '43', + namespace: 'guardians-test', + deployStatus: 'deployed', + deployDescription: 'Upgrade complete', + lastDeployed: '2024-03-04T15:21:09.34520527Z' + ] + simpleStatusMap.resources == [ + [kind: 'ConfigMap', name:'core-appconfig-configmap'], + [kind: 'Deployment', name:'core'], + [kind: 'Deployment', name:'standalone-gateway'], + [kind: 'Service', name:'core'], + [kind: 'Service', name:'standalone-gateway'], + [kind: 'Cluster', name:'edb-cluster'], + [kind: 'Secret', name:'core-rsa-key-secret'], + [kind: 'Secret', name:'core-security-exandradev-secret'], + [kind: 'Secret', name:'core-security-unify-secret'] + ] + deploymentResources == [ + Deployment: [ 'core', 'standalone-gateway'] + ] + + } +} diff --git a/test/groovy/vars/OdsComponentStageRolloutOpenShiftDeploymentSpec.groovy b/test/groovy/vars/OdsComponentStageRolloutOpenShiftDeploymentSpec.groovy index 9bffdc955..e47edde1c 100644 --- a/test/groovy/vars/OdsComponentStageRolloutOpenShiftDeploymentSpec.groovy +++ b/test/groovy/vars/OdsComponentStageRolloutOpenShiftDeploymentSpec.groovy @@ -1,13 +1,18 @@ package vars + +import groovy.json.JsonSlurperClassic import org.codehaus.groovy.runtime.typehandling.GroovyCastException import org.ods.component.Context import org.ods.component.IContext import org.ods.services.OpenShiftService import org.ods.services.JenkinsService import org.ods.services.ServiceRegistry +import org.ods.util.HelmStatusData +import org.ods.util.HelmStatusSimpleData import org.ods.util.Logger import org.ods.util.PodData +import util.FixtureHelper import util.PipelineSteps import vars.test_helper.PipelineSpockTestBase import spock.lang.* @@ -149,15 +154,22 @@ class OdsComponentStageRolloutOpenShiftDeploymentSpec extends PipelineSpockTestB def "run successfully with Helm"() { given: - def c = config + [environment: 'dev',targetProject: 'foo-dev',openshiftRolloutTimeoutRetries: 5,chartDir: 'chart'] + def c = config + [ + projectId: 'guardians', + componentId: 'core', + environment: 'test', + targetProject: 'guardians-test', + openshiftRolloutTimeoutRetries: 5, + chartDir: 'chart'] + def helmJsonText = new FixtureHelper().getResource("helmstatus.json").text + IContext context = new Context(null, c, logger) OpenShiftService openShiftService = Mock(OpenShiftService.class) - openShiftService.getResourcesForComponent('foo-dev', ['Deployment', 'DeploymentConfig'], 'app=foo-bar') >> [Deployment: ['bar']] - openShiftService.getRevision(*_) >> 123 - openShiftService.rollout(*_) >> "${config.componentId}-124" - openShiftService.getPodDataForDeployment(*_) >> [new PodData([ deploymentId: "${config.componentId}-124" ])] - openShiftService.getImagesOfDeployment(*_) >> [[ repository: 'foo', name: 'bar' ]] - openShiftService.checkForPodData(*_) >> [new PodData([deploymentId: "${config.componentId}-124"])] + openShiftService.helmStatus('guardians-test', 'standalone-app') >> HelmStatusSimpleData.from(HelmStatusData.fromJsonObject(new JsonSlurperClassic().parseText(helmJsonText))) + // todo: verify that we did not want to ensure that build images are tagged here. + // - the org.ods.component.Context.artifactUriStore is not initialized with c when created above! + // - as a consequence the build artifacts are empty so no retagging happens here. + openShiftService.checkForPodData(*_) >> [new PodData([deploymentId: "${c.componentId}-124"])] ServiceRegistry.instance.add(OpenShiftService, openShiftService) JenkinsService jenkinsService = Stub(JenkinsService.class) jenkinsService.maybeWithPrivateKeyCredentials(*_) >> { args -> args[1]('/tmp/file') } @@ -192,19 +204,21 @@ class OdsComponentStageRolloutOpenShiftDeploymentSpec extends PipelineSpockTestB return metadata } } - def deploymentInfo = script.call(context) + def deploymentInfo = script.call(context, [ + helmReleaseName: "standalone-app", + ]) then: printCallStack() assertJobStatusSuccess() - deploymentInfo['Deployment/bar'][0].deploymentId == "bar-124" + deploymentInfo['Deployment/core'][0].deploymentId == "core-124" // test artifact URIS def buildArtifacts = context.getBuildArtifactURIs() buildArtifacts.size() > 0 - buildArtifacts.deployments['bar-deploymentMean']['type'] == 'helm' + buildArtifacts.deployments['core-deploymentMean']['type'] == 'helm' - 1 * openShiftService.helmUpgrade('foo-dev', 'bar', ['values.yaml'], ['registry':null, 'componentId':'bar', 'global.registry':null, 'global.componentId':'bar', 'imageNamespace':'foo-dev', 'imageTag':'cd3e9082', 'global.imageNamespace':'foo-dev', 'global.imageTag':'cd3e9082'], ['--install', '--atomic'], [], true) + 1 * openShiftService.helmUpgrade('guardians-test', 'standalone-app', ['values.yaml'], ['registry':null, 'componentId':'core', 'global.registry':null, 'global.componentId':'core', 'imageNamespace':'guardians-test', 'imageTag':'cd3e9082', 'global.imageNamespace':'guardians-test', 'global.imageTag':'cd3e9082'], ['--install', '--atomic'], [], true) } @Unroll diff --git a/test/resources/deployments-data.json b/test/resources/deployments-data.json new file mode 100644 index 000000000..9a9f26b68 --- /dev/null +++ b/test/resources/deployments-data.json @@ -0,0 +1,112 @@ +{ + "deployments": { + "backend-helm-monorepo-chart-component-a": { + "podName": "backend-helm-monorepo-chart-component-a-7d6659884-vhl2z", + "podNamespace": "kraemerh-test", + "podMetaDataCreationTimestamp": "2024-06-26T13:05:33Z", + "deploymentId": "backend-helm-monorepo-chart-component-a-7d6659884", + "podStatus": "Running", + "containers": { + "chart-component-a": "image-registry.openshift-image-registry.svc:5000/kraemerh-test/backend-helm-monorepo-component-a@sha256:5c6440e6179138842d75a9b4a0eb9dd283097839931119e79ee0da43656c8870" + } + }, + "backend-helm-monorepo-chart-component-a-deploymentMean": { + "type": "helm", + "selector": "app.kubernetes.io/instance=backend-helm-monorepo", + "chartDir": "chart", + "helmReleaseName": "backend-helm-monorepo", + "helmEnvBasedValuesFiles": [ + ], + "helmValuesFiles": [ + "values.yaml" + ], + "helmValues": { + "registry": "image-registry.openshift-image-registry.svc:5000", + "componentId": "backend-helm-monorepo" + }, + "helmDefaultFlags": [ + "--install", + "--atomic" + ], + "helmAdditionalFlags": [ + ], + "helmStatus": { + "releaseName": "backend-helm-monorepo", + "releaseRevision": "2", + "namespace": "kraemerh-dev", + "deployStatus": "deployed", + "deployDescription": "Upgrade complete", + "lastDeployed": "2024-06-26T12:59:51.270713404Z", + "resources": [ + { + "kind": "Deployment", + "name": "backend-helm-monorepo-chart-component-a" + }, + { + "kind": "Deployment", + "name": "backend-helm-monorepo-chart-component-b" + }, + { + "kind": "Service", + "name": "backend-helm-monorepo-chart" + } + ] + }, + "repoId": "backend-helm-monorepo" + }, + "backend-helm-monorepo-chart-component-b": { + "podName": "backend-helm-monorepo-chart-component-b-87c7f548d-6hhcz", + "podNamespace": "kraemerh-test", + "podMetaDataCreationTimestamp": "2024-06-26T13:05:33Z", + "deploymentId": "backend-helm-monorepo-chart-component-b-87c7f548d", + "podStatus": "Running", + "containers": { + "chart-component-b": "image-registry.openshift-image-registry.svc:5000/kraemerh-test/backend-helm-monorepo-component-b@sha256:5e9ed6ba8458a9501a9d973398ff27e6e50411d3745cec0dac761e07378185a2" + } + }, + "backend-helm-monorepo-chart-component-b-deploymentMean": { + "type": "helm", + "selector": "app.kubernetes.io/instance=backend-helm-monorepo", + "chartDir": "chart", + "helmReleaseName": "backend-helm-monorepo", + "helmEnvBasedValuesFiles": [ + ], + "helmValuesFiles": [ + "values.yaml" + ], + "helmValues": { + "registry": "image-registry.openshift-image-registry.svc:5000", + "componentId": "backend-helm-monorepo" + }, + "helmDefaultFlags": [ + "--install", + "--atomic" + ], + "helmAdditionalFlags": [ + ], + "helmStatus": { + "releaseName": "backend-helm-monorepo", + "releaseRevision": "2", + "namespace": "kraemerh-dev", + "deployStatus": "deployed", + "deployDescription": "Upgrade complete", + "lastDeployed": "2024-06-26T12:59:51.270713404Z", + "resources": [ + { + "kind": "Deployment", + "name": "backend-helm-monorepo-chart-component-a" + }, + { + "kind": "Deployment", + "name": "backend-helm-monorepo-chart-component-b" + }, + { + "kind": "Service", + "name": "backend-helm-monorepo-chart" + } + ] + }, + "repoId": "backend-helm-monorepo" + } + } +} diff --git a/test/resources/helmstatus.json b/test/resources/helmstatus.json new file mode 100644 index 000000000..e81088a83 --- /dev/null +++ b/test/resources/helmstatus.json @@ -0,0 +1,1422 @@ +{ + "name": "standalone-app", + "info": { + "first_deployed": "2022-12-19T09:44:32.164490076Z", + "last_deployed": "2024-03-04T15:21:09.34520527Z", + "deleted": "", + "description": "Upgrade complete", + "status": "deployed", + "resources": { + "v1/Cluster": [ + { + "apiVersion": "postgresql.k8s.enterprisedb.io/v1", + "kind": "Cluster", + "metadata": { + "annotations": { + "meta.helm.sh/release-name": "standalone-app", + "meta.helm.sh/release-namespace": "guardians-test" + }, + "creationTimestamp": "2023-07-04T13:18:28Z", + "generation": 3, + "labels": { + "app.kubernetes.io/instance": "standalone-app", + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "edb-cluster", + "app.kubernetes.io/version": "2f70aaae0e4facb06e6981c542b235e89fe156c3", + "helm.sh/chart": "edb-cluster-0.1.0_2f70aaae0e4facb06e6981c542b235e89fe156c3" + }, + "name": "edb-cluster", + "namespace": "guardians-test", + "resourceVersion": "2880969905", + "uid": "3ee355b9-0ebb-4997-9351-47b96def618f" + }, + "spec": { + "affinity": { + "podAntiAffinityType": "preferred" + }, + "bootstrap": { + "initdb": { + "database": "app", + "encoding": "UTF8", + "localeCType": "C", + "localeCollate": "C", + "owner": "app" + } + }, + "enableSuperuserAccess": true, + "failoverDelay": 0, + "imageName": "quay.io/enterprisedb/postgresql:15.3", + "instances": 1, + "logLevel": "info", + "maxSyncReplicas": 0, + "minSyncReplicas": 0, + "monitoring": { + "customQueriesConfigMap": [ + { + "key": "queries", + "name": "postgresql-operator-default-monitoring" + } + ], + "disableDefaultQueries": false, + "enablePodMonitor": false + }, + "postgresGID": 26, + "postgresUID": 26, + "postgresql": { + "parameters": { + "archive_mode": "on", + "archive_timeout": "5min", + "dynamic_shared_memory_type": "posix", + "log_destination": "csvlog", + "log_directory": "/controller/log", + "log_filename": "postgres", + "log_rotation_age": "0", + "log_rotation_size": "0", + "log_truncate_on_rotation": "false", + "logging_collector": "on", + "max_parallel_workers": "32", + "max_replication_slots": "32", + "max_worker_processes": "32", + "shared_memory_type": "mmap", + "shared_preload_libraries": "", + "ssl_max_protocol_version": "TLSv1.3", + "ssl_min_protocol_version": "TLSv1.3", + "wal_keep_size": "512MB", + "wal_receiver_timeout": "5s", + "wal_sender_timeout": "5s" + }, + "syncReplicaElectionConstraint": { + "enabled": false + } + }, + "primaryUpdateMethod": "restart", + "primaryUpdateStrategy": "unsupervised", + "replicationSlots": { + "highAvailability": { + "enabled": true, + "slotPrefix": "_cnp_" + }, + "updateInterval": 30 + }, + "resources": {}, + "smartShutdownTimeout": 180, + "startDelay": 30, + "stopDelay": 30, + "storage": { + "resizeInUseVolumes": true, + "size": "20Gi" + }, + "switchoverDelay": 40000000 + }, + "status": { + "certificates": { + "clientCASecret": "edb-cluster-ca", + "expirations": { + "edb-cluster-ca": "2024-08-29 14:02:22 +0000 UTC", + "edb-cluster-replication": "2024-08-29 14:02:22 +0000 UTC", + "edb-cluster-server": "2024-08-29 14:02:22 +0000 UTC" + }, + "replicationTLSSecret": "edb-cluster-replication", + "serverAltDNSNames": [ + "edb-cluster-rw", + "edb-cluster-rw.guardians-test", + "edb-cluster-rw.guardians-test.svc", + "edb-cluster-r", + "edb-cluster-r.guardians-test", + "edb-cluster-r.guardians-test.svc", + "edb-cluster-ro", + "edb-cluster-ro.guardians-test", + "edb-cluster-ro.guardians-test.svc" + ], + "serverCASecret": "edb-cluster-ca", + "serverTLSSecret": "edb-cluster-server" + }, + "cloudNativePostgresqlCommitHash": "949626034", + "cloudNativePostgresqlOperatorHash": "0737af0747dd2ac7040c7b21655bd8e5fa4e01028fe9d833891be071390e8785", + "conditions": [ + { + "lastTransitionTime": "2024-05-25T14:42:08Z", + "message": "Cluster is Ready", + "reason": "ClusterIsReady", + "status": "True", + "type": "Ready" + }, + { + "lastTransitionTime": "2023-07-04T13:19:38Z", + "message": "velero addon is disabled", + "reason": "Disabled", + "status": "False", + "type": "k8s.enterprisedb.io/velero" + }, + { + "lastTransitionTime": "2023-07-04T13:19:38Z", + "message": "external-backup-adapter addon is disabled", + "reason": "Disabled", + "status": "False", + "type": "k8s.enterprisedb.io/externalBackupAdapter" + }, + { + "lastTransitionTime": "2023-07-04T13:19:38Z", + "message": "external-backup-adapter-cluster addon is disabled", + "reason": "Disabled", + "status": "False", + "type": "k8s.enterprisedb.io/externalBackupAdapterCluster" + }, + { + "lastTransitionTime": "2023-07-04T13:19:40Z", + "message": "kasten addon is disabled", + "reason": "Disabled", + "status": "False", + "type": "k8s.enterprisedb.io/kasten" + }, + { + "lastTransitionTime": "2023-11-30T15:26:14Z", + "message": "Continuous archiving is working", + "reason": "ContinuousArchivingSuccess", + "status": "True", + "type": "ContinuousArchiving" + } + ], + "configMapResourceVersion": { + "metrics": { + "postgresql-operator-default-monitoring": "2880955105" + } + }, + "currentPrimary": "edb-cluster-1", + "currentPrimaryTimestamp": "2023-07-04T13:19:27.039619Z", + "healthyPVC": [ + "edb-cluster-1" + ], + "instanceNames": [ + "edb-cluster-1" + ], + "instances": 1, + "instancesReportedState": { + "edb-cluster-1": { + "isPrimary": true, + "timeLineID": 1 + } + }, + "instancesStatus": { + "healthy": [ + "edb-cluster-1" + ] + }, + "latestGeneratedNode": 1, + "licenseStatus": { + "licenseExpiration": "2999-12-31T00:00:00Z", + "licenseStatus": "Valid license (Boehringer Ingelheim (boehringer_ingelheim))", + "repositoryAccess": false, + "valid": true + }, + "managedRolesStatus": {}, + "phase": "Cluster in healthy state", + "poolerIntegrations": { + "pgBouncerIntegration": {} + }, + "pvcCount": 1, + "readService": "edb-cluster-r", + "readyInstances": 1, + "secretsResourceVersion": { + "applicationSecretVersion": "2880969810", + "clientCaSecretVersion": "2880969811", + "replicationSecretVersion": "2880969813", + "serverCaSecretVersion": "2880969811", + "serverSecretVersion": "2880969815", + "superuserSecretVersion": "2880969816" + }, + "targetPrimary": "edb-cluster-1", + "targetPrimaryTimestamp": "2023-07-04T13:18:29.516149Z", + "timelineID": 1, + "topology": { + "instances": { + "edb-cluster-1": {} + }, + "nodesUsed": 1, + "successfullyExtracted": true + }, + "writeService": "edb-cluster-rw" + } + } + ], + "v1/ConfigMap": [ + { + "apiVersion": "v1", + "data": { + "application.yaml": "REDACTED\n" + }, + "kind": "ConfigMap", + "metadata": { + "annotations": { + "meta.helm.sh/release-name": "standalone-app", + "meta.helm.sh/release-namespace": "guardians-test" + }, + "creationTimestamp": "2023-05-16T15:41:54Z", + "labels": { + "app.kubernetes.io/instance": "standalone-app", + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "core", + "app.kubernetes.io/version": "ea80478c0052a5d76c505d7e5f56556ac63ea982", + "helm.sh/chart": "core-0.1.0_ea80478c0052a5d76c505d7e5f56556ac63ea982" + }, + "name": "core-appconfig-configmap", + "namespace": "guardians-test", + "resourceVersion": "2880955101", + "uid": "612ad220-26de-44b6-bbf6-31ba57e456cb" + } + } + ], + "v1/Deployment": [ + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "annotations": { + "deployment.kubernetes.io/revision": "36", + "meta.helm.sh/release-name": "standalone-app", + "meta.helm.sh/release-namespace": "guardians-test" + }, + "creationTimestamp": "2022-12-19T09:44:33Z", + "generation": 42, + "labels": { + "app.kubernetes.io/instance": "standalone-app", + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "core", + "app.kubernetes.io/version": "ea80478c0052a5d76c505d7e5f56556ac63ea982", + "helm.sh/chart": "core-0.1.0_ea80478c0052a5d76c505d7e5f56556ac63ea982" + }, + "name": "core", + "namespace": "guardians-test", + "resourceVersion": "2865328801", + "uid": "30d8bb5a-06ff-4705-97d4-51f7737a9bfe" + }, + "spec": { + "progressDeadlineSeconds": 600, + "replicas": 1, + "revisionHistoryLimit": 10, + "selector": { + "matchLabels": { + "app.kubernetes.io/instance": "standalone-app", + "app.kubernetes.io/name": "core" + } + }, + "strategy": { + "type": "Recreate" + }, + "template": { + "metadata": { + "annotations": { + "checksum/appconfig-configmap": "cf3b985c902671383801409e8a965ad17ab4ec10e5b6237b7486bd5d16dcec67", + "checksum/rsa-key-secret": "57a5e7abec60d7d4084c36a3d59aca39df32371cff531337d060401b9655d3e5", + "checksum/security-exandradev-secret": "07f38b38833d3701cfcd128a3429ba4defe5272e99164fbba4352a40ed94f99a", + "checksum/security-unify-secret": "2705b192feffaf260f2d5d524d6dfdefe6348d6faa95662f1485fbfd63af2a95" + }, + "creationTimestamp": null, + "labels": { + "app.kubernetes.io/instance": "standalone-app", + "app.kubernetes.io/name": "core" + } + }, + "spec": { + "containers": [ + { + "env": [ + { + "name": "EXANDRADEV_CLIENT_ID", + "valueFrom": { + "secretKeyRef": { + "key": "clientId", + "name": "core-security-exandradev-secret" + } + } + }, + { + "name": "EXANDRADEV_CLIENT_SECRET", + "valueFrom": { + "secretKeyRef": { + "key": "clientSecret", + "name": "core-security-exandradev-secret" + } + } + }, + { + "name": "UNIFY_CLIENT_ID", + "valueFrom": { + "secretKeyRef": { + "key": "clientId", + "name": "core-security-unify-secret" + } + } + }, + { + "name": "UNIFY_CLIENT_SECRET", + "valueFrom": { + "secretKeyRef": { + "key": "clientSecret", + "name": "core-security-unify-secret" + } + } + }, + { + "name": "DB_USERNAME", + "valueFrom": { + "secretKeyRef": { + "key": "username", + "name": "edb-cluster-app" + } + } + }, + { + "name": "DB_PASSWORD", + "valueFrom": { + "secretKeyRef": { + "key": "password", + "name": "edb-cluster-app" + } + } + } + ], + "image": "image-registry.openshift-image-registry.svc:5000/guardians-test/core-standalone:ea80478c0052a5d76c505d7e5f56556ac63ea982", + "imagePullPolicy": "IfNotPresent", + "livenessProbe": { + "failureThreshold": 3, + "httpGet": { + "path": "/q/health/live", + "port": "http", + "scheme": "HTTP" + }, + "periodSeconds": 5, + "successThreshold": 1, + "timeoutSeconds": 1 + }, + "name": "core", + "ports": [ + { + "containerPort": 8081, + "name": "http", + "protocol": "TCP" + } + ], + "resources": { + "limits": { + "cpu": "1", + "memory": "512Mi" + }, + "requests": { + "cpu": "1", + "memory": "512Mi" + } + }, + "securityContext": {}, + "startupProbe": { + "failureThreshold": 20, + "httpGet": { + "path": "/q/health/started", + "port": "http", + "scheme": "HTTP" + }, + "periodSeconds": 3, + "successThreshold": 1, + "timeoutSeconds": 1 + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/deployments/core/rsa", + "name": "exandra-rsa-key-volume", + "readOnly": true + }, + { + "mountPath": "/deployments/config", + "name": "exandra-config-volume", + "readOnly": true + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "terminationGracePeriodSeconds": 30, + "volumes": [ + { + "name": "exandra-rsa-key-volume", + "secret": { + "defaultMode": 420, + "items": [ + { + "key": "rsaKey", + "path": "jwk.json" + } + ], + "secretName": "core-rsa-key-secret" + } + }, + { + "configMap": { + "defaultMode": 420, + "name": "core-appconfig-configmap" + }, + "name": "exandra-config-volume" + } + ] + } + } + }, + "status": { + "availableReplicas": 1, + "conditions": [ + { + "lastTransitionTime": "2023-05-16T15:53:18Z", + "lastUpdateTime": "2024-03-04T15:21:26Z", + "message": "ReplicaSet \"core-75c8f865f7\" has successfully progressed.", + "reason": "NewReplicaSetAvailable", + "status": "True", + "type": "Progressing" + }, + { + "lastTransitionTime": "2024-05-25T13:43:04Z", + "lastUpdateTime": "2024-05-25T13:43:04Z", + "message": "Deployment has minimum availability.", + "reason": "MinimumReplicasAvailable", + "status": "True", + "type": "Available" + } + ], + "observedGeneration": 42, + "readyReplicas": 1, + "replicas": 1, + "updatedReplicas": 1 + } + }, + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "annotations": { + "deployment.kubernetes.io/revision": "18", + "meta.helm.sh/release-name": "standalone-app", + "meta.helm.sh/release-namespace": "guardians-test" + }, + "creationTimestamp": "2023-05-08T09:40:33Z", + "generation": 18, + "labels": { + "app.kubernetes.io/instance": "standalone-app", + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "standalone-gateway", + "app.kubernetes.io/version": "7b5e50e13fd78502967881f4970484ae08b76dc4", + "helm.sh/chart": "standalone-gateway-0.1.0_7b5e50e13fd78502967881f4970484ae08b76d" + }, + "name": "standalone-gateway", + "namespace": "guardians-test", + "resourceVersion": "2865332166", + "uid": "e4d081ee-0e07-48f7-873a-50a167513b09" + }, + "spec": { + "progressDeadlineSeconds": 600, + "replicas": 1, + "revisionHistoryLimit": 10, + "selector": { + "matchLabels": { + "app.kubernetes.io/instance": "standalone-app", + "app.kubernetes.io/name": "standalone-gateway" + } + }, + "strategy": { + "type": "Recreate" + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "app.kubernetes.io/instance": "standalone-app", + "app.kubernetes.io/name": "standalone-gateway" + } + }, + "spec": { + "containers": [ + { + "image": "image-registry.openshift-image-registry.svc:5000/guardians-test/standalone-gateway:7b5e50e13fd78502967881f4970484ae08b76dc4", + "imagePullPolicy": "IfNotPresent", + "livenessProbe": { + "failureThreshold": 3, + "httpGet": { + "path": "/ready", + "port": 9901, + "scheme": "HTTP" + }, + "periodSeconds": 5, + "successThreshold": 1, + "timeoutSeconds": 1 + }, + "name": "standalone-gateway", + "ports": [ + { + "containerPort": 8000, + "name": "http", + "protocol": "TCP" + } + ], + "resources": { + "limits": { + "cpu": "1", + "memory": "512Mi" + }, + "requests": { + "cpu": "100m", + "memory": "256Mi" + } + }, + "securityContext": {}, + "startupProbe": { + "failureThreshold": 30, + "httpGet": { + "path": "/ready", + "port": 9901, + "scheme": "HTTP" + }, + "initialDelaySeconds": 1, + "periodSeconds": 1, + "successThreshold": 1, + "timeoutSeconds": 1 + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File" + } + ], + "dnsPolicy": "ClusterFirst", + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "terminationGracePeriodSeconds": 30 + } + } + }, + "status": { + "availableReplicas": 1, + "conditions": [ + { + "lastTransitionTime": "2023-05-08T09:40:33Z", + "lastUpdateTime": "2023-12-20T16:48:17Z", + "message": "ReplicaSet \"standalone-gateway-5466b58d7c\" has successfully progressed.", + "reason": "NewReplicaSetAvailable", + "status": "True", + "type": "Progressing" + }, + { + "lastTransitionTime": "2024-05-25T13:43:54Z", + "lastUpdateTime": "2024-05-25T13:43:54Z", + "message": "Deployment has minimum availability.", + "reason": "MinimumReplicasAvailable", + "status": "True", + "type": "Available" + } + ], + "observedGeneration": 18, + "readyReplicas": 1, + "replicas": 1, + "updatedReplicas": 1 + } + } + ], + "v1/Pod(related)": [ + { + "apiVersion": "v1", + "items": [ + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "annotations": { + "checksum/appconfig-configmap": "cf3b985c902671383801409e8a965ad17ab4ec10e5b6237b7486bd5d16dcec67", + "checksum/rsa-key-secret": "57a5e7abec60d7d4084c36a3d59aca39df32371cff531337d060401b9655d3e5", + "checksum/security-exandradev-secret": "07f38b38833d3701cfcd128a3429ba4defe5272e99164fbba4352a40ed94f99a", + "checksum/security-unify-secret": "2705b192feffaf260f2d5d524d6dfdefe6348d6faa95662f1485fbfd63af2a95", + "k8s.ovn.org/pod-networks": "{\"default\":{\"ip_addresses\":[\"10.251.18.50/24\"],\"mac_address\":\"0a:58:0a:fb:12:32\",\"gateway_ips\":[\"10.251.18.1\"],\"routes\":[{\"dest\":\"10.251.0.0/16\",\"nextHop\":\"10.251.18.1\"},{\"dest\":\"172.30.0.0/16\",\"nextHop\":\"10.251.18.1\"},{\"dest\":\"100.64.0.0/16\",\"nextHop\":\"10.251.18.1\"}],\"ip_address\":\"10.251.18.50/24\",\"gateway_ip\":\"10.251.18.1\"}}", + "k8s.v1.cni.cncf.io/network-status": "[{\n \"name\": \"ovn-kubernetes\",\n \"interface\": \"eth0\",\n \"ips\": [\n \"10.251.18.50\"\n ],\n \"mac\": \"0a:58:0a:fb:12:32\",\n \"default\": true,\n \"dns\": {}\n}]", + "openshift.io/scc": "restricted-v2", + "seccomp.security.alpha.kubernetes.io/pod": "runtime/default" + }, + "creationTimestamp": "2024-05-25T13:41:18Z", + "generateName": "core-75c8f865f7-", + "labels": { + "app.kubernetes.io/instance": "standalone-app", + "app.kubernetes.io/name": "core", + "pod-template-hash": "75c8f865f7" + }, + "name": "core-75c8f865f7-8tbcw", + "namespace": "guardians-test", + "ownerReferences": [ + { + "apiVersion": "apps/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "ReplicaSet", + "name": "core-75c8f865f7", + "uid": "f98ac8f8-8e79-4511-9124-7514fe409761" + } + ], + "resourceVersion": "2865328796", + "uid": "47adcd3a-b1f4-409a-9647-4e00e3c453cb" + }, + "spec": { + "containers": [ + { + "env": [ + { + "name": "EXANDRADEV_CLIENT_ID", + "valueFrom": { + "secretKeyRef": { + "key": "clientId", + "name": "core-security-exandradev-secret" + } + } + }, + { + "name": "EXANDRADEV_CLIENT_SECRET", + "valueFrom": { + "secretKeyRef": { + "key": "clientSecret", + "name": "core-security-exandradev-secret" + } + } + }, + { + "name": "UNIFY_CLIENT_ID", + "valueFrom": { + "secretKeyRef": { + "key": "clientId", + "name": "core-security-unify-secret" + } + } + }, + { + "name": "UNIFY_CLIENT_SECRET", + "valueFrom": { + "secretKeyRef": { + "key": "clientSecret", + "name": "core-security-unify-secret" + } + } + }, + { + "name": "DB_USERNAME", + "valueFrom": { + "secretKeyRef": { + "key": "username", + "name": "edb-cluster-app" + } + } + }, + { + "name": "DB_PASSWORD", + "valueFrom": { + "secretKeyRef": { + "key": "password", + "name": "edb-cluster-app" + } + } + } + ], + "image": "image-registry.openshift-image-registry.svc:5000/guardians-test/core-standalone:ea80478c0052a5d76c505d7e5f56556ac63ea982", + "imagePullPolicy": "IfNotPresent", + "livenessProbe": { + "failureThreshold": 3, + "httpGet": { + "path": "/q/health/live", + "port": "http", + "scheme": "HTTP" + }, + "periodSeconds": 5, + "successThreshold": 1, + "timeoutSeconds": 1 + }, + "name": "core", + "ports": [ + { + "containerPort": 8081, + "name": "http", + "protocol": "TCP" + } + ], + "resources": { + "limits": { + "cpu": "1", + "memory": "512Mi" + }, + "requests": { + "cpu": "1", + "memory": "512Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "runAsUser": 1001270000 + }, + "startupProbe": { + "failureThreshold": 20, + "httpGet": { + "path": "/q/health/started", + "port": "http", + "scheme": "HTTP" + }, + "periodSeconds": 3, + "successThreshold": 1, + "timeoutSeconds": 1 + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/deployments/core/rsa", + "name": "exandra-rsa-key-volume", + "readOnly": true + }, + { + "mountPath": "/deployments/config", + "name": "exandra-config-volume", + "readOnly": true + }, + { + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", + "name": "kube-api-access-wdbhx", + "readOnly": true + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "enableServiceLinks": true, + "imagePullSecrets": [ + { + "name": "default-dockercfg-kn5ds" + } + ], + "nodeName": "ip-10-8-33-221.ec2.internal", + "preemptionPolicy": "PreemptLowerPriority", + "priority": 0, + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": { + "fsGroup": 1001270000, + "seLinuxOptions": { + "level": "s0:c36,c5" + }, + "seccompProfile": { + "type": "RuntimeDefault" + } + }, + "serviceAccount": "default", + "serviceAccountName": "default", + "terminationGracePeriodSeconds": 30, + "tolerations": [ + { + "effect": "NoExecute", + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoExecute", + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoSchedule", + "key": "node.kubernetes.io/memory-pressure", + "operator": "Exists" + } + ], + "volumes": [ + { + "name": "exandra-rsa-key-volume", + "secret": { + "defaultMode": 420, + "items": [ + { + "key": "rsaKey", + "path": "jwk.json" + } + ], + "secretName": "core-rsa-key-secret" + } + }, + { + "configMap": { + "defaultMode": 420, + "name": "core-appconfig-configmap" + }, + "name": "exandra-config-volume" + }, + { + "name": "kube-api-access-wdbhx", + "projected": { + "defaultMode": 420, + "sources": [ + { + "serviceAccountToken": { + "expirationSeconds": 3607, + "path": "token" + } + }, + { + "configMap": { + "items": [ + { + "key": "ca.crt", + "path": "ca.crt" + } + ], + "name": "kube-root-ca.crt" + } + }, + { + "downwardAPI": { + "items": [ + { + "fieldRef": { + "apiVersion": "v1", + "fieldPath": "metadata.namespace" + }, + "path": "namespace" + } + ] + } + }, + { + "configMap": { + "items": [ + { + "key": "service-ca.crt", + "path": "service-ca.crt" + } + ], + "name": "openshift-service-ca.crt" + } + } + ] + } + } + ] + }, + "status": { + "conditions": [ + { + "lastProbeTime": null, + "lastTransitionTime": "2024-05-25T13:41:18Z", + "status": "True", + "type": "Initialized" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2024-05-25T13:43:03Z", + "status": "True", + "type": "Ready" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2024-05-25T13:43:03Z", + "status": "True", + "type": "ContainersReady" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2024-05-25T13:41:18Z", + "status": "True", + "type": "PodScheduled" + } + ], + "containerStatuses": [ + { + "containerID": "cri-o://475026e4b427c773eb5355446e4e9da989d91d7864b0e4f623c3f942885a11a2", + "image": "image-registry.openshift-image-registry.svc:5000/guardians-test/core-standalone:ea80478c0052a5d76c505d7e5f56556ac63ea982", + "imageID": "image-registry.openshift-image-registry.svc:5000/guardians-test/core-standalone@sha256:6a2290e522133866cafc4864c30ea6ba591de4238a865936e3e4f953d60c173a", + "lastState": {}, + "name": "core", + "ready": true, + "restartCount": 0, + "started": true, + "state": { + "running": { + "startedAt": "2024-05-25T13:42:52Z" + } + } + } + ], + "hostIP": "10.8.33.221", + "phase": "Running", + "podIP": "10.251.18.50", + "podIPs": [ + { + "ip": "10.251.18.50" + } + ], + "qosClass": "Guaranteed", + "startTime": "2024-05-25T13:41:18Z" + } + } + ], + "kind": "PodList", + "metadata": { + "resourceVersion": "2886974735" + } + }, + { + "apiVersion": "v1", + "items": [ + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "annotations": { + "k8s.ovn.org/pod-networks": "{\"default\":{\"ip_addresses\":[\"10.251.18.51/24\"],\"mac_address\":\"0a:58:0a:fb:12:33\",\"gateway_ips\":[\"10.251.18.1\"],\"routes\":[{\"dest\":\"10.251.0.0/16\",\"nextHop\":\"10.251.18.1\"},{\"dest\":\"172.30.0.0/16\",\"nextHop\":\"10.251.18.1\"},{\"dest\":\"100.64.0.0/16\",\"nextHop\":\"10.251.18.1\"}],\"ip_address\":\"10.251.18.51/24\",\"gateway_ip\":\"10.251.18.1\"}}", + "k8s.v1.cni.cncf.io/network-status": "[{\n \"name\": \"ovn-kubernetes\",\n \"interface\": \"eth0\",\n \"ips\": [\n \"10.251.18.51\"\n ],\n \"mac\": \"0a:58:0a:fb:12:33\",\n \"default\": true,\n \"dns\": {}\n}]", + "openshift.io/scc": "restricted-v2", + "seccomp.security.alpha.kubernetes.io/pod": "runtime/default" + }, + "creationTimestamp": "2024-05-25T13:41:18Z", + "generateName": "standalone-gateway-5466b58d7c-", + "labels": { + "app.kubernetes.io/instance": "standalone-app", + "app.kubernetes.io/name": "standalone-gateway", + "pod-template-hash": "5466b58d7c" + }, + "name": "standalone-gateway-5466b58d7c-6h87c", + "namespace": "guardians-test", + "ownerReferences": [ + { + "apiVersion": "apps/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "ReplicaSet", + "name": "standalone-gateway-5466b58d7c", + "uid": "86bc0197-c804-47fe-97ce-34655e72347e" + } + ], + "resourceVersion": "2865332161", + "uid": "ad9109db-bb3e-4302-8b51-66e8a647b06d" + }, + "spec": { + "containers": [ + { + "image": "image-registry.openshift-image-registry.svc:5000/guardians-test/standalone-gateway:7b5e50e13fd78502967881f4970484ae08b76dc4", + "imagePullPolicy": "IfNotPresent", + "livenessProbe": { + "failureThreshold": 3, + "httpGet": { + "path": "/ready", + "port": 9901, + "scheme": "HTTP" + }, + "periodSeconds": 5, + "successThreshold": 1, + "timeoutSeconds": 1 + }, + "name": "standalone-gateway", + "ports": [ + { + "containerPort": 8000, + "name": "http", + "protocol": "TCP" + } + ], + "resources": { + "limits": { + "cpu": "1", + "memory": "512Mi" + }, + "requests": { + "cpu": "100m", + "memory": "256Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "runAsUser": 1001270000 + }, + "startupProbe": { + "failureThreshold": 30, + "httpGet": { + "path": "/ready", + "port": 9901, + "scheme": "HTTP" + }, + "initialDelaySeconds": 1, + "periodSeconds": 1, + "successThreshold": 1, + "timeoutSeconds": 1 + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", + "name": "kube-api-access-6tc2p", + "readOnly": true + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "enableServiceLinks": true, + "imagePullSecrets": [ + { + "name": "default-dockercfg-kn5ds" + } + ], + "nodeName": "ip-10-8-33-221.ec2.internal", + "preemptionPolicy": "PreemptLowerPriority", + "priority": 0, + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": { + "fsGroup": 1001270000, + "seLinuxOptions": { + "level": "s0:c36,c5" + }, + "seccompProfile": { + "type": "RuntimeDefault" + } + }, + "serviceAccount": "default", + "serviceAccountName": "default", + "terminationGracePeriodSeconds": 30, + "tolerations": [ + { + "effect": "NoExecute", + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoExecute", + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoSchedule", + "key": "node.kubernetes.io/memory-pressure", + "operator": "Exists" + } + ], + "volumes": [ + { + "name": "kube-api-access-6tc2p", + "projected": { + "defaultMode": 420, + "sources": [ + { + "serviceAccountToken": { + "expirationSeconds": 3607, + "path": "token" + } + }, + { + "configMap": { + "items": [ + { + "key": "ca.crt", + "path": "ca.crt" + } + ], + "name": "kube-root-ca.crt" + } + }, + { + "downwardAPI": { + "items": [ + { + "fieldRef": { + "apiVersion": "v1", + "fieldPath": "metadata.namespace" + }, + "path": "namespace" + } + ] + } + }, + { + "configMap": { + "items": [ + { + "key": "service-ca.crt", + "path": "service-ca.crt" + } + ], + "name": "openshift-service-ca.crt" + } + } + ] + } + } + ] + }, + "status": { + "conditions": [ + { + "lastProbeTime": null, + "lastTransitionTime": "2024-05-25T13:41:18Z", + "status": "True", + "type": "Initialized" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2024-05-25T13:43:54Z", + "status": "True", + "type": "Ready" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2024-05-25T13:43:54Z", + "status": "True", + "type": "ContainersReady" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2024-05-25T13:41:18Z", + "status": "True", + "type": "PodScheduled" + } + ], + "containerStatuses": [ + { + "containerID": "cri-o://14b477cac3d4639fd0dbb9c024ac5274c506f827034783ac5934df6d06da784c", + "image": "image-registry.openshift-image-registry.svc:5000/guardians-test/standalone-gateway:7b5e50e13fd78502967881f4970484ae08b76dc4", + "imageID": "image-registry.openshift-image-registry.svc:5000/guardians-test/standalone-gateway@sha256:c347bebe2497e1e7701bc57b34778e62ac072d223d121e438958e3ffdae4df1a", + "lastState": {}, + "name": "standalone-gateway", + "ready": true, + "restartCount": 0, + "started": true, + "state": { + "running": { + "startedAt": "2024-05-25T13:43:50Z" + } + } + } + ], + "hostIP": "10.8.33.221", + "phase": "Running", + "podIP": "10.251.18.51", + "podIPs": [ + { + "ip": "10.251.18.51" + } + ], + "qosClass": "Burstable", + "startTime": "2024-05-25T13:41:18Z" + } + } + ], + "kind": "PodList", + "metadata": { + "resourceVersion": "2886974735" + } + } + ], + "v1/Secret": [ + { + "apiVersion": "v1", + "data": { + "rsaKey": "REDACTED" + }, + "kind": "Secret", + "metadata": { + "annotations": { + "meta.helm.sh/release-name": "standalone-app", + "meta.helm.sh/release-namespace": "guardians-test" + }, + "creationTimestamp": "2023-08-25T08:54:46Z", + "labels": { + "app.kubernetes.io/instance": "standalone-app", + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "core", + "app.kubernetes.io/version": "ea80478c0052a5d76c505d7e5f56556ac63ea982", + "helm.sh/chart": "core-0.1.0_ea80478c0052a5d76c505d7e5f56556ac63ea982" + }, + "name": "core-rsa-key-secret", + "namespace": "guardians-test", + "resourceVersion": "2880969794", + "uid": "45e94955-0ee4-41c6-ab53-582ceabd3274" + }, + "type": "Opaque" + }, + { + "apiVersion": "v1", + "data": { + "clientId": "REDACTED", + "clientSecret": "REDACTED" + }, + "kind": "Secret", + "metadata": { + "annotations": { + "meta.helm.sh/release-name": "standalone-app", + "meta.helm.sh/release-namespace": "guardians-test" + }, + "creationTimestamp": "2023-08-25T08:54:46Z", + "labels": { + "app.kubernetes.io/instance": "standalone-app", + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "core", + "app.kubernetes.io/version": "ea80478c0052a5d76c505d7e5f56556ac63ea982", + "helm.sh/chart": "core-0.1.0_ea80478c0052a5d76c505d7e5f56556ac63ea982" + }, + "name": "core-security-exandradev-secret", + "namespace": "guardians-test", + "resourceVersion": "2880969795", + "uid": "4db28b76-f90f-4431-a19d-73ef7e5d5ae7" + }, + "type": "Opaque" + }, + { + "apiVersion": "v1", + "data": { + "clientId": "REDACTED", + "clientSecret": "REDACTED" + }, + "kind": "Secret", + "metadata": { + "annotations": { + "meta.helm.sh/release-name": "standalone-app", + "meta.helm.sh/release-namespace": "guardians-test" + }, + "creationTimestamp": "2023-05-16T15:41:54Z", + "labels": { + "app.kubernetes.io/instance": "standalone-app", + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "core", + "app.kubernetes.io/version": "ea80478c0052a5d76c505d7e5f56556ac63ea982", + "helm.sh/chart": "core-0.1.0_ea80478c0052a5d76c505d7e5f56556ac63ea982" + }, + "name": "core-security-unify-secret", + "namespace": "guardians-test", + "resourceVersion": "2880969797", + "uid": "536ceb38-0457-4186-bd09-efe234b5fca1" + }, + "type": "Opaque" + } + ], + "v1/Service": [ + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "annotations": { + "meta.helm.sh/release-name": "standalone-app", + "meta.helm.sh/release-namespace": "guardians-test" + }, + "creationTimestamp": "2022-12-19T09:44:33Z", + "labels": { + "app.kubernetes.io/instance": "standalone-app", + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "core", + "app.kubernetes.io/version": "ea80478c0052a5d76c505d7e5f56556ac63ea982", + "helm.sh/chart": "core-0.1.0_ea80478c0052a5d76c505d7e5f56556ac63ea982" + }, + "name": "core", + "namespace": "guardians-test", + "resourceVersion": "2687980260", + "uid": "287bf074-73da-4546-a43d-6d1f23c82365" + }, + "spec": { + "clusterIP": "172.30.21.158", + "clusterIPs": [ + "172.30.21.158" + ], + "internalTrafficPolicy": "Cluster", + "ipFamilies": [ + "IPv4" + ], + "ipFamilyPolicy": "SingleStack", + "ports": [ + { + "name": "http", + "port": 8081, + "protocol": "TCP", + "targetPort": 8081 + } + ], + "selector": { + "app.kubernetes.io/instance": "standalone-app", + "app.kubernetes.io/name": "core" + }, + "sessionAffinity": "None", + "type": "ClusterIP" + }, + "status": { + "loadBalancer": {} + } + }, + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "annotations": { + "meta.helm.sh/release-name": "standalone-app", + "meta.helm.sh/release-namespace": "guardians-test" + }, + "creationTimestamp": "2023-05-08T09:40:33Z", + "labels": { + "app.kubernetes.io/instance": "standalone-app", + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "standalone-gateway", + "app.kubernetes.io/version": "7b5e50e13fd78502967881f4970484ae08b76dc4", + "helm.sh/chart": "standalone-gateway-0.1.0_7b5e50e13fd78502967881f4970484ae08b76d" + }, + "name": "standalone-gateway", + "namespace": "guardians-test", + "resourceVersion": "2497441712", + "uid": "87924b81-ff69-4676-b03c-31cb84fcea1e" + }, + "spec": { + "clusterIP": "172.30.187.73", + "clusterIPs": [ + "172.30.187.73" + ], + "internalTrafficPolicy": "Cluster", + "ipFamilies": [ + "IPv4" + ], + "ipFamilyPolicy": "SingleStack", + "ports": [ + { + "name": "http", + "port": 80, + "protocol": "TCP", + "targetPort": 8000 + } + ], + "selector": { + "app.kubernetes.io/instance": "standalone-app", + "app.kubernetes.io/name": "standalone-gateway" + }, + "sessionAffinity": "None", + "type": "ClusterIP" + }, + "status": { + "loadBalancer": {} + } + } + ] + } + }, + "manifest": "REDACTED\n", + "version": 43, + "namespace": "guardians-test" +} From 5a0a021d33e395a241e585a94ea22fd4c69e84a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrich=20Kra=CC=88mer?= Date: Tue, 2 Jul 2024 13:46:44 +0200 Subject: [PATCH 03/36] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3e78f77b..341e42d3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Changed * Enhance SSDS Document Generation Performance using New Atlassian APIs ([#1084](https://github.com/opendevstack/ods-jenkins-shared-library/issues/1084)) +* TIR - remove dynamic pod data and surface helm status ([#1135](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1135)) ### Fixed * Fix Tailor deployment drifts for D, Q envs ([#1055](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1055)) From 0e23c03a40e52955c884f087bbc19c941deca7e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrich=20Kr=C3=A4mer?= Date: Tue, 2 Jul 2024 12:02:55 +0000 Subject: [PATCH 04/36] Report HelmStatus first in TIR under release-deploymentStatus --- src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy index f143889e0..55a39f38f 100644 --- a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy +++ b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy @@ -1176,10 +1176,10 @@ class LeVADocumentUseCase extends DocGenUseCase { if (releaseName in helmReleasesCovered) { return } - def withoutHelmStatus = deployment.findAll { k, v -> k != 'helmStatus' } - deploymentsForTir.put("${releaseName}-deploymentMean".toString(), withoutHelmStatus) def helmStatus = assembleHelmStatus(deployment?.helmStatus ?: [:]) deploymentsForTir.put("${releaseName}-deploymentStatus".toString(), helmStatus) + def withoutHelmStatus = deployment.findAll { k, v -> k != 'helmStatus' } + deploymentsForTir.put("${releaseName}-deploymentMean".toString(), withoutHelmStatus) helmReleasesCovered << (releaseName) } else { deploymentsForTir.put(deploymentName, deployment) From 555745c4f6289ee61e781153433a0accf5468844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrich=20Kr=C3=A4mer?= Date: Thu, 4 Jul 2024 11:54:06 +0000 Subject: [PATCH 05/36] TIR: render empty helm/tailor values as None or similar --- .../usecase/LeVADocumentUseCase.groovy | 37 +++++++++++++++--- .../usecase/LeVADocumentUseCaseSpec.groovy | 39 +++++++++++++++++-- ...s-data.json => deployments-data-helm.json} | 0 test/resources/deployments-data-tailor.json | 21 ++++++++++ 4 files changed, 88 insertions(+), 9 deletions(-) rename test/resources/{deployments-data.json => deployments-data-helm.json} (100%) create mode 100644 test/resources/deployments-data-tailor.json diff --git a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy index 55a39f38f..db473ddee 100644 --- a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy +++ b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy @@ -1164,8 +1164,8 @@ class LeVADocumentUseCase extends DocGenUseCase { } Set helmReleasesCovered = [] - Map deploymentsForTir = [:] - deployments.each { String deploymentName, Map deployment -> + Map > deploymentsForTir = [:] + deployments.each { String deploymentName, Map deployment -> if (deploymentName.endsWith('-deploymentMean')) { if (deployment.type == "helm") { String releaseName = deployment?.helmReleaseName @@ -1176,13 +1176,14 @@ class LeVADocumentUseCase extends DocGenUseCase { if (releaseName in helmReleasesCovered) { return } - def helmStatus = assembleHelmStatus(deployment?.helmStatus ?: [:]) + def helmStatus = assembleHelmStatus( (deployment?.helmStatus ?: [:] ) as Map) deploymentsForTir.put("${releaseName}-deploymentStatus".toString(), helmStatus) def withoutHelmStatus = deployment.findAll { k, v -> k != 'helmStatus' } - deploymentsForTir.put("${releaseName}-deploymentMean".toString(), withoutHelmStatus) + deploymentsForTir.put("${releaseName}-deploymentMean".toString(), + handleEmptyValues(withoutHelmStatus)) helmReleasesCovered << (releaseName) } else { - deploymentsForTir.put(deploymentName, deployment) + deploymentsForTir.put(deploymentName, handleEmptyValues(deployment)) } } else { if (deploymentName in componentsCoveredByHelm) { @@ -1217,6 +1218,32 @@ class LeVADocumentUseCase extends DocGenUseCase { return assembledHelmStatus } + Map handleEmptyValues(Map deployment) { + if (deployment?.type == 'tailor') { + def tailorEmptyValues = [ + tailorParamFile: 'None', + tailorParams: 'None', + tailorPreserve: 'No extra resources specified to be preserved' + ] + return deployment.collectEntries { k, v -> + def newValue = (tailorEmptyValues.containsKey(k) && !v) ? tailorEmptyValues[k] : v + [(k): newValue] + } + } + if (deployment?.type == 'helm') { + def helmEmptyValues = [ + helmAdditionalFlags: 'None', + helmEnvBasedValuesFiles: 'None', + helmValues: 'None', + ] + return deployment.collectEntries { k, v -> + def newValue = (helmEmptyValues.containsKey(k) && !v) ? helmEmptyValues[k] : v + [(k): newValue] + } + } + return deployment + } + String createOverallTIR(Map repo = null, Map data = null) { def documentTypeName = DOCUMENT_TYPE_NAMES[DocumentType.OVERALL_TIR as String] def metadata = this.getDocumentMetadata(documentTypeName) diff --git a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy index b643ed9b2..f0f539905 100644 --- a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy +++ b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy @@ -1281,9 +1281,9 @@ class LeVADocumentUseCaseSpec extends SpecHelper { 1 * usecase.createDocument(documentType, repo, _, [:], _, documentTemplate, watermarkText) } - def "assemble deploymentInfo for TIR"() { + def "assemble deploymentInfo for TIR with helm"() { given: - def file = new FixtureHelper().getResource("deployments-data.json") + def file = new FixtureHelper().getResource("deployments-data-helm.json") os.isDeploymentKind(*_) >> true when: @@ -1295,8 +1295,8 @@ class LeVADocumentUseCaseSpec extends SpecHelper { assembledData["backend-helm-monorepo-deploymentMean"] == [ chartDir : "chart", repoId : "backend-helm-monorepo", - helmAdditionalFlags: [], - helmEnvBasedValuesFiles :[], + helmAdditionalFlags: "None", + helmEnvBasedValuesFiles :"None", helmValues :[ registry :"image-registry.openshift-image-registry.svc:5000", componentId :"backend-helm-monorepo" @@ -1318,6 +1318,37 @@ class LeVADocumentUseCaseSpec extends SpecHelper { ] } + def "assemble deploymentInfo for TIR with tailor"() { + given: + def file = new FixtureHelper().getResource("deployments-data-tailor.json") + os.isDeploymentKind(*_) >> true + + when: + def deploymentsData = new JsonSlurperClassic().parseText(file.text) + def assembledData = usecase.assembleDeployments(deploymentsData.deployments) + + then: + + assembledData["backend-first-deploymentMean"] == [ + type: "tailor", + selector: "app=kraemerh-backend-first", + tailorSelectors: [ + selector: "app=kraemerh-backend-first", + exclude: "bc,is", + ], + tailorParamFile: "None", + tailorParams: "None", + tailorPreserve: "No extra resources specified to be preserved", + tailorVerify: true + ] + + assembledData["backend-first"] == [ + containers: [ + "backend-first": "backend-first@sha256:fc5fb63f4ac45e207a4a1ceba37534814489c16e82306cf46aca76627c0f5e1e" + ] + ] + } + def "create overall DTR"() { given: // Argument Constraints diff --git a/test/resources/deployments-data.json b/test/resources/deployments-data-helm.json similarity index 100% rename from test/resources/deployments-data.json rename to test/resources/deployments-data-helm.json diff --git a/test/resources/deployments-data-tailor.json b/test/resources/deployments-data-tailor.json new file mode 100644 index 000000000..3a511365b --- /dev/null +++ b/test/resources/deployments-data-tailor.json @@ -0,0 +1,21 @@ +{ + "deployments": { + "backend-first": { + "containers": { + "backend-first": "backend-first@sha256:fc5fb63f4ac45e207a4a1ceba37534814489c16e82306cf46aca76627c0f5e1e" + } + }, + "backend-first-deploymentMean": { + "type": "tailor", + "selector": "app=kraemerh-backend-first", + "tailorSelectors": { + "selector": "app=kraemerh-backend-first", + "exclude": "bc,is" + }, + "tailorParamFile": "", + "tailorParams": [], + "tailorPreserve": [], + "tailorVerify": true + } + } +} From 31f4590887d05abec6ce01ad910d9600d896a6ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrich=20Kr=C3=A4mer?= Date: Tue, 23 Jul 2024 16:10:40 +0000 Subject: [PATCH 06/36] Introduce JsonLogUtil and exception tweaks --- .../component/HelmDeploymentStrategy.groovy | 42 ++++++++++--------- .../phases/DeployOdsComponent.groovy | 16 +++---- .../usecase/LeVADocumentUseCase.groovy | 16 +++---- src/org/ods/services/OpenShiftService.groovy | 4 +- src/org/ods/util/HelmStatusData.groovy | 7 ++-- src/org/ods/util/JsonLogUtil.groovy | 25 +++++++++++ 6 files changed, 68 insertions(+), 42 deletions(-) create mode 100644 src/org/ods/util/JsonLogUtil.groovy diff --git a/src/org/ods/component/HelmDeploymentStrategy.groovy b/src/org/ods/component/HelmDeploymentStrategy.groovy index c706c24ee..75803b05e 100644 --- a/src/org/ods/component/HelmDeploymentStrategy.groovy +++ b/src/org/ods/component/HelmDeploymentStrategy.groovy @@ -1,12 +1,12 @@ package org.ods.component -import groovy.json.JsonOutput import groovy.transform.TypeChecked import groovy.transform.TypeCheckingMode import org.ods.services.JenkinsService import org.ods.services.OpenShiftService import org.ods.util.HelmStatusSimpleData import org.ods.util.ILogger +import org.ods.util.JsonLogUtil import org.ods.util.PipelineSteps import org.ods.util.PodData @@ -96,26 +96,19 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { logger.info "Rolling out ${context.componentId} with HELM, selector: ${options.selector}" helmUpgrade(context.targetProject) HelmStatusSimpleData helmStatus = openShift.helmStatus(context.targetProject, options.helmReleaseName) - def helmStatusMap = helmStatus.toMap() - def deploymentResources = getDeploymentResources(helmStatus) - - logger.info("${this.class.name} -- HELM STATUS") - logger.info( - JsonOutput.prettyPrint( - JsonOutput.toJson(helmStatusMap))) - - // not sure if we need both HELM STATUS and DEPLOYMENT RESOURCES" - logger.info("${this.class.name} -- DEPLOYMENT RESOURCES") - logger.info( - JsonOutput.prettyPrint( - JsonOutput.toJson(deploymentResources))) + if (logger.debugMode) { + def deploymentResources = getDeploymentResources(helmStatus) + def helmStatusMap = helmStatus.toMap() + JsonLogUtil.debug(logger, "${this.class.name} -- HELM STATUS", helmStatusMap) + JsonLogUtil.debug(logger, "${this.class.name} -- DEPLOYMENT RESOURCES", deploymentResources) + } // // FIXME: pauseRollouts is non trivial to determine! // // we assume that Helm does "Deployment" that should work for most // // cases since they don't have triggers. // metadataSvc.updateMetadata(false, deploymentResources) def rolloutData = getRolloutData(helmStatus) - logger.info(JsonOutput.prettyPrint(JsonOutput.toJson(rolloutData))) + logger.info(JsonLogUtil.jsonToString(rolloutData)) return rolloutData } @@ -132,7 +125,7 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { options.helmValues['componentId'] = context.componentId // we persist the original ones set from outside - here we just add ours - Map mergedHelmValues = [:] as Map + def mergedHelmValues = [:] as Map mergedHelmValues << options.helmValues // we add the global ones - this allows usage in subcharts @@ -182,6 +175,19 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { private Map> getRolloutData( HelmStatusSimpleData helmStatus ) { + @SuppressWarnings(['LineLength']) // for the github permalinks + // Why do we need podData when helm should take care of the install + // 1. FinalizeOdsComponent#verifyDeploymentsBuiltByODS() will fail at + // https://github.com/opendevstack/ods-jenkins-shared-library/blob/7aab67dad73298b1388eca3517a1a2ea856a2b8e/src/org/ods/orchestration/phases/FinalizeOdsComponent.groovy#L165 + // unless it can associate all pods with a deployment resource. + // 2. DeploymentDescripter requires to have for each deployment resource + // a deployment mean with postfix -deploymentMean + // 3. Image importing in DeployOdsComponent at + // https://github.com/opendevstack/ods-jenkins-shared-library/blob/97463eebc986f1558c37d5ff5a84ec188f9e92d0/src/org/ods/orchestration/phases/DeployOdsComponent.groovy#L51) + // currently is driven by images in PodData#containers. + // + // If possible this should be redesigned so that the shared library does not have to + // concern itself with pods anymore. def rolloutData = [:] helmStatus.resources.each { resource -> if (! (resource.kind in DEPLOYMENT_KINDS)) { @@ -219,9 +225,7 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { if (!podData) { throw new RuntimeException(msgPodsNotFound) } - - logger.debug("Helm podData for ${podDataContext.join(', ')}: " + - "${JsonOutput.prettyPrint(JsonOutput.toJson(podData))}") + JsonLogUtil.debug(logger, "Helm podData for ${podDataContext.join(', ')}:", podData) rolloutData["${resource.kind}/${resource.name}"] = podData // TODO: Once the orchestration pipeline can deal with multiple replicas, diff --git a/src/org/ods/orchestration/phases/DeployOdsComponent.groovy b/src/org/ods/orchestration/phases/DeployOdsComponent.groovy index 4c697bb26..2d53c141d 100644 --- a/src/org/ods/orchestration/phases/DeployOdsComponent.groovy +++ b/src/org/ods/orchestration/phases/DeployOdsComponent.groovy @@ -1,6 +1,5 @@ package org.ods.orchestration.phases -import groovy.json.JsonOutput import groovy.transform.TypeChecked import groovy.transform.TypeCheckingMode import org.ods.util.IPipelineSteps @@ -12,6 +11,7 @@ import org.ods.services.GitService import org.ods.orchestration.util.DeploymentDescriptor import org.ods.orchestration.util.MROPipelineUtil import org.ods.orchestration.util.Project +import org.ods.util.JsonLogUtil import org.ods.util.PodData // Deploy ODS comnponent (code or service) to 'qa' or 'prod'. @@ -42,6 +42,8 @@ class DeployOdsComponent { DeploymentDescriptor deploymentDescriptor steps.dir(openShiftDir) { deploymentDescriptor = DeploymentDescriptor.readFromFile(steps) + JsonLogUtil.debug(logger, "DeploymentDescriptor '${openShiftDir}': ", + deploymentDescriptor.deployments) } if (!repo.data.openshift.deployments) { repo.data.openshift.deployments = [:] @@ -79,9 +81,7 @@ class DeployOdsComponent { if (!podData) { throw new RuntimeException(msgPodsNotFound) } - - logger.info("Helm podData for '${podDataContext.join(', ')}': " + - "${JsonOutput.prettyPrint(JsonOutput.toJson(podData))}") + JsonLogUtil.debug(logger, "Helm podData for '${podDataContext.join(', ')}': ", podData) // TODO: Once the orchestration pipeline can deal with multiple replicas, // update this to deal with multiple pods. @@ -238,14 +238,10 @@ class DeployOdsComponent { deploymentMean.helmAdditionalFlags, true) - def helmStatus = os. helmStatus(project.targetProject, deploymentMean.helmReleaseName) + def helmStatus = os.helmStatus(project.targetProject, deploymentMean.helmReleaseName) def helmStatusMap = helmStatus.toMap() deploymentMean.helmStatus = helmStatusMap - logger.info("${this.class.name} -- HELM STATUS") - logger.info( - JsonOutput.prettyPrint( - JsonOutput.toJson(helmStatusMap))) - + JsonLogUtil.debug(logger, "${this.class.name} -- HELM STATUS", helmStatusMap) } } jenkins.maybeWithPrivateKeyCredentials(secretName) { String pkeyFile -> diff --git a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy index db473ddee..a938d3bde 100644 --- a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy +++ b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy @@ -1,7 +1,6 @@ package org.ods.orchestration.usecase -import static groovy.json.JsonOutput.prettyPrint -import static groovy.json.JsonOutput.toJson +import org.ods.util.JsonLogUtil import com.cloudbees.groovy.cps.NonCPS import groovy.xml.XmlUtil @@ -1072,7 +1071,8 @@ class LeVADocumentUseCase extends DocGenUseCase { @SuppressWarnings('CyclomaticComplexity') String createTIR(Map repo, Map data) { - logger.debug("createTIR - repo:${prettyPrint(toJson(repo))}, data:${prettyPrint(toJson(data))}") + JsonLogUtil.debug(logger, "createTIR - repo:", repo) + JsonLogUtil.debug(logger, "createTIR - data:", data) def documentType = DocumentType.TIR as String @@ -1137,9 +1137,10 @@ class LeVADocumentUseCase extends DocGenUseCase { @SuppressWarnings('CyclomaticComplexity') Map assembleDeployments(Map> deployments) { // collect helm releases - Map> deploymentsMeansHelm = deployments.findAll { + def deploymentsMeansHelm = deployments.findAll { it.key.endsWith('-deploymentMean') && it.value.type == "helm" } + // codeNarc complains if we use def x = [:] as Map<...> that this is an Unnecessary cast Map> deploymentsHelmByRelease = [:] deploymentsMeansHelm.each { String deploymentName, Map deploymentMeanHelm -> deploymentsHelmByRelease << [(deploymentMeanHelm.helmReleaseName): deploymentMeanHelm] @@ -1176,7 +1177,7 @@ class LeVADocumentUseCase extends DocGenUseCase { if (releaseName in helmReleasesCovered) { return } - def helmStatus = assembleHelmStatus( (deployment?.helmStatus ?: [:] ) as Map) + def helmStatus = assembleHelmStatus((deployment?.helmStatus ?: [:]) as Map) deploymentsForTir.put("${releaseName}-deploymentStatus".toString(), helmStatus) def withoutHelmStatus = deployment.findAll { k, v -> k != 'helmStatus' } deploymentsForTir.put("${releaseName}-deploymentMean".toString(), @@ -1193,13 +1194,12 @@ class LeVADocumentUseCase extends DocGenUseCase { } } } - logger.debug("createTIR - assembled deployments data:${prettyPrint(toJson(deploymentsForTir))}") + JsonLogUtil.debug("createTIR - assembled deployments data:", deploymentsForTir) deploymentsForTir } - @SuppressWarnings('UnnecessaryCast') Map assembleHelmStatus(Map helmStatus) { - def resources = helmStatus?.resources ?: [] as List > + List > resources = helmStatus?.resources ?: [] def properResources = resources.findAll { it?.kind && it?.name } diff --git a/src/org/ods/services/OpenShiftService.groovy b/src/org/ods/services/OpenShiftService.groovy index 2c1430640..d1c383c3a 100644 --- a/src/org/ods/services/OpenShiftService.groovy +++ b/src/org/ods/services/OpenShiftService.groovy @@ -178,8 +178,8 @@ class OpenShiftService { def object = new JsonSlurperClassic().parseText(helmStdout) def helmStatusData = HelmStatusData.fromJsonObject(object) helmStatusData - } catch (Exception e) { - throw new RuntimeException("Helm status Failed (${e.message})!" + + } catch (ex) { + throw new RuntimeException("Helm status Failed (${ex.message})!" + "Helm could not gather status of ${release} in ${project}") } } diff --git a/src/org/ods/util/HelmStatusData.groovy b/src/org/ods/util/HelmStatusData.groovy index f1127f46a..16f4fd011 100644 --- a/src/org/ods/util/HelmStatusData.groovy +++ b/src/org/ods/util/HelmStatusData.groovy @@ -60,9 +60,9 @@ class HelmStatusData { info.resources = resourcesMap status.info = new HelmStatusInfoData(info) new HelmStatusData(status) - } catch (Exception e) { + } catch (IllegalArgumentException ex) { throw new IllegalArgumentException( - "Unexpected helm status information in JSON at 'info': ${e.getMessage()}") + "Unexpected helm status information in JSON at 'info': ${ex.message}") } } @@ -127,7 +127,8 @@ class HelmStatusData { } try { new HelmStatusResourceData(resource) - } catch (Exception e) { + } catch (IllegalArgumentException e) { + // provide context throw new IllegalArgumentException( "Unexpected helm status JSON at '${context}': ${e.getMessage()}") } diff --git a/src/org/ods/util/JsonLogUtil.groovy b/src/org/ods/util/JsonLogUtil.groovy new file mode 100644 index 000000000..c927ee2f6 --- /dev/null +++ b/src/org/ods/util/JsonLogUtil.groovy @@ -0,0 +1,25 @@ +package org.ods.util + +import com.cloudbees.groovy.cps.NonCPS +import groovy.json.JsonOutput +import groovy.transform.TypeChecked + +@TypeChecked +class JsonLogUtil { + + @NonCPS + static String debug(ILogger logger, String msg, Object jsonObject) { + if (logger.debugMode) { + if (msg) { + logger.debug(msg) + } + logger.debug(jsonToString(jsonObject)) + } + } + + @NonCPS + static String jsonToString(Object jsonObject) { + JsonOutput.prettyPrint(JsonOutput.toJson(jsonObject)) + } + +} From 8ed9042f10b9c66fe409072bedd6b4e9110c1848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrich=20Kr=C3=A4mer?= Date: Tue, 23 Jul 2024 17:50:44 +0000 Subject: [PATCH 07/36] createTir keep helmStatus separate in template data --- .../usecase/LeVADocumentUseCase.groovy | 101 ++++++++---------- src/org/ods/services/OpenShiftService.groovy | 2 +- .../usecase/LeVADocumentUseCaseSpec.groovy | 8 +- 3 files changed, 51 insertions(+), 60 deletions(-) diff --git a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy index a938d3bde..c5cb6f921 100644 --- a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy +++ b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy @@ -1095,9 +1095,10 @@ class LeVADocumentUseCase extends DocGenUseCase { def data_ = [ metadata : this.getDocumentMetadata(this.DOCUMENT_TYPE_NAMES[documentType], repo), deployNote : deploynoteData, + helmStatus: getHelmStatusAndMean(repo.data.openshift.deployments ?: [:]), openShiftData: [ builds : repo.data.openshift.builds ?: '', - deployments: assembleDeployments(repo.data.openshift.deployments ?: [:]), + deployments: getNonHelmDeployments(repo.data.openshift.deployments ?: [:]), ], testResults : [ installation: installationTestData?.testResults @@ -1109,6 +1110,7 @@ class LeVADocumentUseCase extends DocGenUseCase { documentHistoryLatestVersionId: docHistory?.latestVersionId ?: 1, ] ] + JsonLogUtil.debug(logger, "createTIR - assembled data:", data_) // Code review report - in the special case of NO jira .. def codeReviewReport @@ -1132,73 +1134,62 @@ class LeVADocumentUseCase extends DocGenUseCase { } /** - * Helm releases become top level elements, tailor deployments are left alone. + * Retrieves first helm release in deployments. + * + * @return An empty map if there is no helm release in deployments. + * Otherwise keys 'status' and 'means' each contain a + * map suited to format the information in a template. */ @SuppressWarnings('CyclomaticComplexity') - Map assembleDeployments(Map> deployments) { - // collect helm releases - def deploymentsMeansHelm = deployments.findAll { + Map > getHelmStatusAndMean(Map> deployments) { + // collect first helm release + def deploymentMeanHelm = deployments.find { it.key.endsWith('-deploymentMean') && it.value.type == "helm" } - // codeNarc complains if we use def x = [:] as Map<...> that this is an Unnecessary cast - Map> deploymentsHelmByRelease = [:] - deploymentsMeansHelm.each { String deploymentName, Map deploymentMeanHelm -> - deploymentsHelmByRelease << [(deploymentMeanHelm.helmReleaseName): deploymentMeanHelm] - } - Set componentsCoveredByHelm = [] - deploymentsHelmByRelease.each { String release, Map deploymentMeanHelm -> - List> resources = deploymentMeanHelm?.helmStatus?.resources ?: [] - resources.each { - if (!it?.kind) { - logger.debug("skipping resource - no kind defined: ${it}") - return - } - if (!it?.name) { - logger.debug("skipping resource - no name defined: ${it}") - return - } - if (!os.isDeploymentKind(it.kind)) { - return - } - componentsCoveredByHelm << it.name - } + + def meanHelm = deploymentMeanHelm?.value + if (!meanHelm) { + return [:] + } + String releaseName = meanHelm?.helmReleaseName + if (!releaseName) { + logger.warn("No helmReleaseName name in ${meanHelm}: skipping") + return [:] } + def helmStatus = formatHelmStatus((meanHelm?.helmStatus ?: [:]) as Map) + def meanHelmWithoutStatus = meanHelm.findAll { k, v -> k != 'helmStatus' } + [ + status: helmStatus, + mean: formatEmptyValues(meanHelmWithoutStatus) + ] + } - Set helmReleasesCovered = [] - Map > deploymentsForTir = [:] + /** + * Retrieves all non-helm deployments. + * + * The processed map is suited to be displayed with Helm. + * + * @return An empty map if no such deployments exist. + * Otherwise keys indicate the deployment resource or deployment mean if they + * have a suffix of -deploymentMean. + */ + Map getNonHelmDeployments(Map> deployments) { + // collect non helm deployments + def deploymentsNotHelm = deployments.findAll { + !it.key.endsWith('-deploymentMean') || it.value?.type != "helm" + } + Map> deploymentsForTir = [:] deployments.each { String deploymentName, Map deployment -> if (deploymentName.endsWith('-deploymentMean')) { - if (deployment.type == "helm") { - String releaseName = deployment?.helmReleaseName - if (!releaseName) { - logger.warn("No helmReleaseName name in ${deploymentName}: skipping") - return - } - if (releaseName in helmReleasesCovered) { - return - } - def helmStatus = assembleHelmStatus((deployment?.helmStatus ?: [:]) as Map) - deploymentsForTir.put("${releaseName}-deploymentStatus".toString(), helmStatus) - def withoutHelmStatus = deployment.findAll { k, v -> k != 'helmStatus' } - deploymentsForTir.put("${releaseName}-deploymentMean".toString(), - handleEmptyValues(withoutHelmStatus)) - helmReleasesCovered << (releaseName) - } else { - deploymentsForTir.put(deploymentName, handleEmptyValues(deployment)) - } + deploymentsForTir.put(deploymentName, formatEmptyValues(deployment)) } else { - if (deploymentName in componentsCoveredByHelm) { - return - } else { - deploymentsForTir.put(deploymentName, deployment.findAll { k, v -> k != 'podName' }) - } + deploymentsForTir.put(deploymentName, deployment.findAll { k, v -> k != 'podName' }) } } - JsonLogUtil.debug("createTIR - assembled deployments data:", deploymentsForTir) deploymentsForTir } - Map assembleHelmStatus(Map helmStatus) { + Map formatHelmStatus(Map helmStatus) { List > resources = helmStatus?.resources ?: [] def properResources = resources.findAll { it?.kind && it?.name @@ -1218,7 +1209,7 @@ class LeVADocumentUseCase extends DocGenUseCase { return assembledHelmStatus } - Map handleEmptyValues(Map deployment) { + Map formatEmptyValues(Map deployment) { if (deployment?.type == 'tailor') { def tailorEmptyValues = [ tailorParamFile: 'None', diff --git a/src/org/ods/services/OpenShiftService.groovy b/src/org/ods/services/OpenShiftService.groovy index d1c383c3a..49f0584a4 100644 --- a/src/org/ods/services/OpenShiftService.groovy +++ b/src/org/ods/services/OpenShiftService.groovy @@ -171,7 +171,7 @@ class OpenShiftService { ) { try { def helmStdout = steps.sh( - script: "helm -n ${project} status ${release} --show-resources -o json", + script: "helm -n ${project} status ${release} --show-resources -o json | jq", label: "Gather Helm status for release ${release} in ${project}", returnStdout: true ).toString().trim() diff --git a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy index f0f539905..22241e4a7 100644 --- a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy +++ b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy @@ -1288,11 +1288,11 @@ class LeVADocumentUseCaseSpec extends SpecHelper { when: def deploymentsData = new JsonSlurperClassic().parseText(file.text) - def assembledData = usecase.assembleDeployments(deploymentsData.deployments) + def assembledData = usecase.getHelmStatusAndMean(deploymentsData.deployments) then: - assembledData["backend-helm-monorepo-deploymentMean"] == [ + assembledData["mean"] == [ chartDir : "chart", repoId : "backend-helm-monorepo", helmAdditionalFlags: "None", @@ -1307,7 +1307,7 @@ class LeVADocumentUseCaseSpec extends SpecHelper { helmValuesFiles :["values.yaml"], type :"helm", ] - assembledData["backend-helm-monorepo-deploymentStatus"] == [ + assembledData["status"] == [ releaseRevision :"2", releaseName :"backend-helm-monorepo", namespace: "kraemerh-dev", @@ -1325,7 +1325,7 @@ class LeVADocumentUseCaseSpec extends SpecHelper { when: def deploymentsData = new JsonSlurperClassic().parseText(file.text) - def assembledData = usecase.assembleDeployments(deploymentsData.deployments) + def assembledData = usecase.getNonHelmDeployments(deploymentsData.deployments) then: From 690e3698795a07de6e7b2305e6fb59a2ea7e45ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrich=20Kr=C3=A4mer?= Date: Tue, 23 Jul 2024 18:23:39 +0000 Subject: [PATCH 08/36] running into a cps issue + oc -n kraemerh-dev get pod -l app.kubernetes.io/instance=backend-helm-monorepo -o json expected to call org.ods.util.JsonLogUtil.debug but wound up catching org.ods.util.Logger.getDebugMode; see: https://jenkins.io/redirect/pipeline-cps-method-mismatches/ --- src/org/ods/component/HelmDeploymentStrategy.groovy | 6 +++--- src/org/ods/orchestration/phases/DeployOdsComponent.groovy | 6 +++--- src/org/ods/util/JsonLogUtil.groovy | 1 - 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/org/ods/component/HelmDeploymentStrategy.groovy b/src/org/ods/component/HelmDeploymentStrategy.groovy index 75803b05e..c218120ac 100644 --- a/src/org/ods/component/HelmDeploymentStrategy.groovy +++ b/src/org/ods/component/HelmDeploymentStrategy.groovy @@ -99,8 +99,8 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { if (logger.debugMode) { def deploymentResources = getDeploymentResources(helmStatus) def helmStatusMap = helmStatus.toMap() - JsonLogUtil.debug(logger, "${this.class.name} -- HELM STATUS", helmStatusMap) - JsonLogUtil.debug(logger, "${this.class.name} -- DEPLOYMENT RESOURCES", deploymentResources) + JsonLogUtil.debug(logger, "${this.class.name} -- HELM STATUS".toString(), helmStatusMap) + JsonLogUtil.debug(logger, "${this.class.name} -- DEPLOYMENT RESOURCES".toString(), deploymentResources) } // // FIXME: pauseRollouts is non trivial to determine! @@ -225,7 +225,7 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { if (!podData) { throw new RuntimeException(msgPodsNotFound) } - JsonLogUtil.debug(logger, "Helm podData for ${podDataContext.join(', ')}:", podData) + JsonLogUtil.debug(logger, "Helm podData for ${podDataContext.join(', ')}:".toString(), podData) rolloutData["${resource.kind}/${resource.name}"] = podData // TODO: Once the orchestration pipeline can deal with multiple replicas, diff --git a/src/org/ods/orchestration/phases/DeployOdsComponent.groovy b/src/org/ods/orchestration/phases/DeployOdsComponent.groovy index 2d53c141d..54287add4 100644 --- a/src/org/ods/orchestration/phases/DeployOdsComponent.groovy +++ b/src/org/ods/orchestration/phases/DeployOdsComponent.groovy @@ -42,7 +42,7 @@ class DeployOdsComponent { DeploymentDescriptor deploymentDescriptor steps.dir(openShiftDir) { deploymentDescriptor = DeploymentDescriptor.readFromFile(steps) - JsonLogUtil.debug(logger, "DeploymentDescriptor '${openShiftDir}': ", + JsonLogUtil.debug(logger, "DeploymentDescriptor '${openShiftDir}': ".toString(), deploymentDescriptor.deployments) } if (!repo.data.openshift.deployments) { @@ -81,7 +81,7 @@ class DeployOdsComponent { if (!podData) { throw new RuntimeException(msgPodsNotFound) } - JsonLogUtil.debug(logger, "Helm podData for '${podDataContext.join(', ')}': ", podData) + JsonLogUtil.debug(logger, "Helm podData for '${podDataContext.join(', ')}': ".toString(), podData) // TODO: Once the orchestration pipeline can deal with multiple replicas, // update this to deal with multiple pods. @@ -241,7 +241,7 @@ class DeployOdsComponent { def helmStatus = os.helmStatus(project.targetProject, deploymentMean.helmReleaseName) def helmStatusMap = helmStatus.toMap() deploymentMean.helmStatus = helmStatusMap - JsonLogUtil.debug(logger, "${this.class.name} -- HELM STATUS", helmStatusMap) + JsonLogUtil.debug(logger, "${this.class.name} -- HELM STATUS".toString(), helmStatusMap) } } jenkins.maybeWithPrivateKeyCredentials(secretName) { String pkeyFile -> diff --git a/src/org/ods/util/JsonLogUtil.groovy b/src/org/ods/util/JsonLogUtil.groovy index c927ee2f6..3ce767e58 100644 --- a/src/org/ods/util/JsonLogUtil.groovy +++ b/src/org/ods/util/JsonLogUtil.groovy @@ -7,7 +7,6 @@ import groovy.transform.TypeChecked @TypeChecked class JsonLogUtil { - @NonCPS static String debug(ILogger logger, String msg, Object jsonObject) { if (logger.debugMode) { if (msg) { From e8ee01aeecc37a7a04859eefdd807c664311b04f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrich=20Kr=C3=A4mer?= Date: Tue, 23 Jul 2024 19:42:07 +0000 Subject: [PATCH 09/36] non helm deployment fix --- .../usecase/LeVADocumentUseCase.groovy | 2 +- .../usecase/LeVADocumentUseCaseSpec.groovy | 31 +++++++++++++++---- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy index c5cb6f921..d00ab370b 100644 --- a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy +++ b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy @@ -1179,7 +1179,7 @@ class LeVADocumentUseCase extends DocGenUseCase { !it.key.endsWith('-deploymentMean') || it.value?.type != "helm" } Map> deploymentsForTir = [:] - deployments.each { String deploymentName, Map deployment -> + deploymentsNotHelm.each { String deploymentName, Map deployment -> if (deploymentName.endsWith('-deploymentMean')) { deploymentsForTir.put(deploymentName, formatEmptyValues(deployment)) } else { diff --git a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy index 22241e4a7..4e22060c6 100644 --- a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy +++ b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy @@ -1288,11 +1288,12 @@ class LeVADocumentUseCaseSpec extends SpecHelper { when: def deploymentsData = new JsonSlurperClassic().parseText(file.text) - def assembledData = usecase.getHelmStatusAndMean(deploymentsData.deployments) + def helmStatusAndMean = usecase.getHelmStatusAndMean(deploymentsData.deployments) + def nonHelmDeployments = usecase.getNonHelmDeployments(deploymentsData.deployments) then: - assembledData["mean"] == [ + helmStatusAndMean["mean"] == [ chartDir : "chart", repoId : "backend-helm-monorepo", helmAdditionalFlags: "None", @@ -1307,7 +1308,7 @@ class LeVADocumentUseCaseSpec extends SpecHelper { helmValuesFiles :["values.yaml"], type :"helm", ] - assembledData["status"] == [ + helmStatusAndMean["status"] == [ releaseRevision :"2", releaseName :"backend-helm-monorepo", namespace: "kraemerh-dev", @@ -1316,6 +1317,24 @@ class LeVADocumentUseCaseSpec extends SpecHelper { deployStatus :"deployed", lastDeployed :"2024-06-26T12:59:51.270713404Z" ] + nonHelmDeployments["backend-helm-monorepo-chart-component-a"] == [ + podNamespace: "kraemerh-test", + podMetaDataCreationTimestamp: "2024-06-26T13:05:33Z", + deploymentId: "backend-helm-monorepo-chart-component-a-7d6659884", + podStatus: "Running", + containers: [ + "chart-component-a": "image-registry.openshift-image-registry.svc:5000/kraemerh-test/backend-helm-monorepo-component-a@sha256:5c6440e6179138842d75a9b4a0eb9dd283097839931119e79ee0da43656c8870" + ] + ] + nonHelmDeployments["backend-helm-monorepo-chart-component-b"] == [ + podNamespace: "kraemerh-test", + podMetaDataCreationTimestamp: "2024-06-26T13:05:33Z", + deploymentId: "backend-helm-monorepo-chart-component-b-87c7f548d", + podStatus: "Running", + containers: [ + "chart-component-b": "image-registry.openshift-image-registry.svc:5000/kraemerh-test/backend-helm-monorepo-component-b@sha256:5e9ed6ba8458a9501a9d973398ff27e6e50411d3745cec0dac761e07378185a2" + ] + ] } def "assemble deploymentInfo for TIR with tailor"() { @@ -1325,11 +1344,11 @@ class LeVADocumentUseCaseSpec extends SpecHelper { when: def deploymentsData = new JsonSlurperClassic().parseText(file.text) - def assembledData = usecase.getNonHelmDeployments(deploymentsData.deployments) + def nonHelmDeployments = usecase.getNonHelmDeployments(deploymentsData.deployments) then: - assembledData["backend-first-deploymentMean"] == [ + nonHelmDeployments["backend-first-deploymentMean"] == [ type: "tailor", selector: "app=kraemerh-backend-first", tailorSelectors: [ @@ -1342,7 +1361,7 @@ class LeVADocumentUseCaseSpec extends SpecHelper { tailorVerify: true ] - assembledData["backend-first"] == [ + nonHelmDeployments["backend-first"] == [ containers: [ "backend-first": "backend-first@sha256:fc5fb63f4ac45e207a4a1ceba37534814489c16e82306cf46aca76627c0f5e1e" ] From f6fbcb6699e8c02e95b9b11526f6c9f46eb34486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrich=20Kr=C3=A4mer?= Date: Wed, 24 Jul 2024 06:19:44 +0000 Subject: [PATCH 10/36] non helm deployment wording changes --- src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy index d00ab370b..1c995d327 100644 --- a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy +++ b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy @@ -1140,7 +1140,6 @@ class LeVADocumentUseCase extends DocGenUseCase { * Otherwise keys 'status' and 'means' each contain a * map suited to format the information in a template. */ - @SuppressWarnings('CyclomaticComplexity') Map > getHelmStatusAndMean(Map> deployments) { // collect first helm release def deploymentMeanHelm = deployments.find { @@ -1167,7 +1166,7 @@ class LeVADocumentUseCase extends DocGenUseCase { /** * Retrieves all non-helm deployments. * - * The processed map is suited to be displayed with Helm. + * The processed map is suited to format the resource information in the TIR. * * @return An empty map if no such deployments exist. * Otherwise keys indicate the deployment resource or deployment mean if they From 4fe044fc44552852487fac6c72dfbf667989028d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrich=20Kr=C3=A4mer?= Date: Wed, 24 Jul 2024 11:35:02 +0000 Subject: [PATCH 11/36] Remove detailed helm status parsing and rename same fields. --- .../component/HelmDeploymentStrategy.groovy | 80 ++++---- src/org/ods/services/OpenShiftService.groovy | 13 +- .../util/HelmStatusContainerStatusData.groovy | 32 --- src/org/ods/util/HelmStatusData.groovy | 182 ----------------- src/org/ods/util/HelmStatusInfoData.groovy | 48 ----- src/org/ods/util/HelmStatusResource.groovy | 25 --- .../ods/util/HelmStatusResourceData.groovy | 39 ---- src/org/ods/util/HelmStatusSimpleData.groovy | 190 ++++++++++++++---- .../HelmDeploymentStrategySpec.groovy | 24 +-- .../usecase/LeVADocumentUseCaseSpec.groovy | 8 +- .../ods/services/OpenShiftServiceSpec.groovy | 2 +- test/groovy/util/HelmStatusSpec.groovy | 30 ++- ...StageRolloutOpenShiftDeploymentSpec.groovy | 4 +- test/resources/deployments-data-helm.json | 62 +++--- 14 files changed, 253 insertions(+), 486 deletions(-) delete mode 100644 src/org/ods/util/HelmStatusContainerStatusData.groovy delete mode 100644 src/org/ods/util/HelmStatusData.groovy delete mode 100644 src/org/ods/util/HelmStatusInfoData.groovy delete mode 100644 src/org/ods/util/HelmStatusResource.groovy delete mode 100644 src/org/ods/util/HelmStatusResourceData.groovy diff --git a/src/org/ods/component/HelmDeploymentStrategy.groovy b/src/org/ods/component/HelmDeploymentStrategy.groovy index c218120ac..6cb7071e8 100644 --- a/src/org/ods/component/HelmDeploymentStrategy.groovy +++ b/src/org/ods/component/HelmDeploymentStrategy.groovy @@ -189,50 +189,52 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { // If possible this should be redesigned so that the shared library does not have to // concern itself with pods anymore. def rolloutData = [:] - helmStatus.resources.each { resource -> - if (! (resource.kind in DEPLOYMENT_KINDS)) { + helmStatus.resourcesByKind.each { kind, names -> + if (!(kind in DEPLOYMENT_KINDS)) { return // continues with next } - context.addDeploymentToArtifactURIs("${resource.name}-deploymentMean", - [ - 'type': 'helm', - 'selector': options.selector, - 'chartDir': options.chartDir, - 'helmReleaseName': options.helmReleaseName, - 'helmEnvBasedValuesFiles': options.helmEnvBasedValuesFiles, - 'helmValuesFiles': options.helmValuesFiles, - 'helmValues': options.helmValues, - 'helmDefaultFlags': options.helmDefaultFlags, - 'helmAdditionalFlags': options.helmAdditionalFlags, - 'helmStatus': helmStatus.toMap(), + names.each { name -> + context.addDeploymentToArtifactURIs("${name}-deploymentMean", + [ + type: 'helm', + selector: options.selector, + chartDir: options.chartDir, + helmReleaseName: options.helmReleaseName, + helmEnvBasedValuesFiles: options.helmEnvBasedValuesFiles, + helmValuesFiles: options.helmValuesFiles, + helmValues: options.helmValues, + helmDefaultFlags: options.helmDefaultFlags, + helmAdditionalFlags: options.helmAdditionalFlags, + helmStatus: helmStatus.toMap(), + ] + ) + def podDataContext = [ + "targetProject=${context.targetProject}", + "selector=${options.selector}", + "name=${name}", ] - ) - def podDataContext = [ - "targetProject=${context.targetProject}", - "selector=${options.selector}", - "name=${resource.name}", - ] - def msgPodsNotFound = "Could not find 'running' pod(s) for '${podDataContext.join(', ')}'" - List podData = null - for (def i = 0; i < options.deployTimeoutRetries; i++) { - podData = openShift.checkForPodData(context.targetProject, options.selector, resource.name) - if (podData) { - break + def msgPodsNotFound = "Could not find 'running' pod(s) for '${podDataContext.join(', ')}'" + List podData = null + for (def i = 0; i < options.deployTimeoutRetries; i++) { + podData = openShift.checkForPodData(context.targetProject, options.selector, name) + if (podData) { + break + } + steps.echo("${msgPodsNotFound} - waiting") + steps.sleep(12) } - steps.echo("${msgPodsNotFound} - waiting") - steps.sleep(12) - } - if (!podData) { - throw new RuntimeException(msgPodsNotFound) + if (!podData) { + throw new RuntimeException(msgPodsNotFound) + } + JsonLogUtil.debug(logger, "Helm podData for ${podDataContext.join(', ')}:".toString(), podData) + + rolloutData["${kind}/${name}"] = podData + // TODO: Once the orchestration pipeline can deal with multiple replicas, + // update this to store multiple pod artifacts. + // TODO: Potential conflict if resourceName is duplicated between + // Deployment and DeploymentConfig resource. + context.addDeploymentToArtifactURIs(name, podData[0]?.toMap()) } - JsonLogUtil.debug(logger, "Helm podData for ${podDataContext.join(', ')}:".toString(), podData) - - rolloutData["${resource.kind}/${resource.name}"] = podData - // TODO: Once the orchestration pipeline can deal with multiple replicas, - // update this to store multiple pod artifacts. - // TODO: Potential conflict if resourceName is duplicated between - // Deployment and DeploymentConfig resource. - context.addDeploymentToArtifactURIs(resource.name, podData[0]?.toMap()) } rolloutData } diff --git a/src/org/ods/services/OpenShiftService.groovy b/src/org/ods/services/OpenShiftService.groovy index 49f0584a4..29c78ebae 100644 --- a/src/org/ods/services/OpenShiftService.groovy +++ b/src/org/ods/services/OpenShiftService.groovy @@ -5,7 +5,6 @@ import groovy.json.JsonOutput import groovy.json.JsonSlurperClassic import groovy.transform.TypeChecked import groovy.transform.TypeCheckingMode -import org.ods.util.HelmStatusData import org.ods.util.HelmStatusSimpleData import org.ods.util.ILogger import org.ods.util.IPipelineSteps @@ -160,23 +159,15 @@ class OpenShiftService { HelmStatusSimpleData helmStatus( String project, String release - ) { - def helmStatusData = retrieveHelmStatus(project, release) - HelmStatusSimpleData.from(helmStatusData) - } - - HelmStatusData retrieveHelmStatus( - String project, - String release ) { try { def helmStdout = steps.sh( - script: "helm -n ${project} status ${release} --show-resources -o json | jq", + script: "helm -n ${project} status ${release} --show-resources -o json", label: "Gather Helm status for release ${release} in ${project}", returnStdout: true ).toString().trim() def object = new JsonSlurperClassic().parseText(helmStdout) - def helmStatusData = HelmStatusData.fromJsonObject(object) + def helmStatusData = HelmStatusSimpleData.fromJsonObject(object) helmStatusData } catch (ex) { throw new RuntimeException("Helm status Failed (${ex.message})!" + diff --git a/src/org/ods/util/HelmStatusContainerStatusData.groovy b/src/org/ods/util/HelmStatusContainerStatusData.groovy deleted file mode 100644 index e3cb46c75..000000000 --- a/src/org/ods/util/HelmStatusContainerStatusData.groovy +++ /dev/null @@ -1,32 +0,0 @@ -package org.ods.util - -import groovy.transform.TypeChecked - -@TypeChecked -class HelmStatusContainerStatusData { - - String name - String image - String imageID - - @SuppressWarnings(['Instanceof']) - HelmStatusContainerStatusData(Map map) { - def missingKeys = [] - def badTypes = [] - - def stringTypes = [ "name", "image", "imageID"] - for (att in stringTypes) { - if (!map.containsKey(att)) { - missingKeys << att - } else if (!(map[att] instanceof String)) { - badTypes << "${att}: expected String, found ${map[att].getClass()}" - } - } - HelmStatusData.handleMissingKeysOrBadTypes(missingKeys, badTypes) - - this.name = map['name'] as String - this.image = map['image'] as String - this.imageID = map['imageID'] as String - } - -} diff --git a/src/org/ods/util/HelmStatusData.groovy b/src/org/ods/util/HelmStatusData.groovy deleted file mode 100644 index 16f4fd011..000000000 --- a/src/org/ods/util/HelmStatusData.groovy +++ /dev/null @@ -1,182 +0,0 @@ -package org.ods.util - -import com.cloudbees.groovy.cps.NonCPS -import groovy.transform.PackageScope -import groovy.transform.TypeChecked - -// Relevant data returned by helm status --show-resources -o json -@TypeChecked -class HelmStatusData { - - // release name - String name - String namespace - - HelmStatusInfoData info - - // version is an integer but we map it to a string. - String version - - @SuppressWarnings(['IfStatementBraces']) - @NonCPS - static HelmStatusData fromJsonObject(Object object) { - try { - def jsonObject = ensureMap(object, "") - - Map status = [:] - - // Constructors of classes receiving the data catches missing keys or unexpected types and - // report them in summary as IllegalArgumentException. - if (jsonObject.name) status.name = jsonObject.name - if (jsonObject.version) status.version = jsonObject.version - if (jsonObject.namespace) status.namespace = jsonObject.namespace - - def infoObject = ensureMap(jsonObject.info, "info") - Map info = [:] - - if (infoObject.status) info.status = infoObject.status - if (infoObject.description) info.description = infoObject.description - if (infoObject['last_deployed']) info.lastDeployed = infoObject['last_deployed'] - - def resourcesObject = ensureMap(infoObject.resources, "info.resources") - // All resources are in an json object which organize resources by keys - // Examples are "v1/Cluster", "v1/ConfigMap" "v1/Deployment", "v1/Pod(related)" - // For these keys the map contains list of resources. - Map> resourcesMap = [:] - for (entry in resourcesObject.entrySet()) { - def key = entry.key as String - def resourceList = ensureList(entry.value, "info.resources.${key}") - def resources = [] - resourceList.eachWithIndex { resourceJsonObject, i -> - def resourceData = fromJsonObjectHelmStatusResource( - resourceJsonObject, - "info.resources.${key}.[${i}]") - if (resourceData != null) { - resources << resourceData - } - } - resourcesMap.put(key, resources) - } - info.resources = resourcesMap - status.info = new HelmStatusInfoData(info) - new HelmStatusData(status) - } catch (IllegalArgumentException ex) { - throw new IllegalArgumentException( - "Unexpected helm status information in JSON at 'info': ${ex.message}") - } - } - - @SuppressWarnings(['Instanceof']) - @NonCPS - private static Map ensureMap(Object obj, String context) { - if (obj == null) { - return [:] - } - if (!(obj instanceof Map)) { - def msg = context ? - "${context}: expected JSON object, found ${obj.getClass()}" : - "Expected JSON object, found ${obj.getClass()}" - - throw new IllegalArgumentException(msg) - } - obj as Map - } - - @SuppressWarnings(['Instanceof']) - @NonCPS - private static List ensureList(Object obj, String context) { - if (obj == null) { - return [] - } - if (!(obj instanceof List)) { - throw new IllegalArgumentException( - "${context}: expected JSON array, found ${obj.getClass()}") - } - obj as List - } - - @SuppressWarnings(['IfStatementBraces', 'Instanceof']) - @NonCPS - private static HelmStatusResourceData fromJsonObjectHelmStatusResource( - resourceJsonObject, String context) { - def resourceObject = ensureMap(resourceJsonObject, context) - Map resource = [:] - if (resourceObject.apiVersion) resource.apiVersion = resourceObject.apiVersion - if (resourceObject.kind) resource.kind = resourceObject.kind - Map metadataObject = ensureMap( - resourceObject.metadata, "${context}.metadata") - if (metadataObject.name) resource.metadataName = metadataObject.name - if (resource.kind == "PodList") { - return null - } - if (resource.kind == "Pod") { - def statusObject = ensureMap(resourceObject.status, - "${context}.status") - List containerStatuses = [] - def containerStatusesJsonArray = ensureList(statusObject.containerStatuses, - "${context}.status.containerStatuses") - containerStatusesJsonArray.eachWithIndex { cs, int csi -> - def cso = ensureMap(cs, "${context}.status.containerStatuses[${csi}]") - Map containerStatus = [:] - if (cso.name) containerStatus.name = cso.name - if (cso.image) containerStatus.image = cso.image - if (cso.imageID) containerStatus.imageID = cso.imageID - containerStatuses << new HelmStatusContainerStatusData(cso) - } - resource.containerStatuses = containerStatuses - } - try { - new HelmStatusResourceData(resource) - } catch (IllegalArgumentException e) { - // provide context - throw new IllegalArgumentException( - "Unexpected helm status JSON at '${context}': ${e.getMessage()}") - } - } - - @SuppressWarnings(['PublicMethodsBeforeNonPublicMethods']) - @NonCPS - static @PackageScope void handleMissingKeysOrBadTypes(List missingKeys, List badTypes) { - if (missingKeys || badTypes) { - def msgs = [] - if (missingKeys) { - msgs << "Missing keys: ${missingKeys.join(', ')}" - } - if (badTypes) { - msgs << "Bad types: ${badTypes.join(', ')}" - } - throw new IllegalArgumentException(msgs.join(".")) - } - } - - @SuppressWarnings(['Instanceof']) - HelmStatusData(Map map) { - def missingKeys = [] - def badTypes = [] - - def stringTypes = [ "name", "namespace"] - for (att in stringTypes) { - if (!map.containsKey(att)) { - missingKeys << att - } else if (!(map[att] instanceof String)) { - badTypes << "${att}: expected String, found ${map[att].getClass()}" - } - } - if (!map.containsKey('info')) { - missingKeys << 'info' - } else if (!(map['info'] instanceof HelmStatusInfoData)) { - badTypes << "info: expected HelmStatusInfoData, found ${map['info'].getClass()}" - } - - if (!map.containsKey('version')) { - missingKeys << 'version' - } - handleMissingKeysOrBadTypes(missingKeys, badTypes) - - this.name = map['name'] as String - this.namespace = map['namespace'] as String - this.info = map['info'] as HelmStatusInfoData - this.version = map['version'].toString() - } - -} diff --git a/src/org/ods/util/HelmStatusInfoData.groovy b/src/org/ods/util/HelmStatusInfoData.groovy deleted file mode 100644 index e0525595e..000000000 --- a/src/org/ods/util/HelmStatusInfoData.groovy +++ /dev/null @@ -1,48 +0,0 @@ -package org.ods.util - -import groovy.transform.TypeChecked - -//Relevant helm status info data we want to capture -@TypeChecked -class HelmStatusInfoData { - - // deployment status - see `helm status --help` for full list - // Example: "deployed" - String status - // description of the release (can be completion message or error message) - // Example: "Upgrade complete" - String description - // last-deployed field. - // Example: "2024-03-04T15:21:09.34520527Z" - String lastDeployed - - Map> resources - - @SuppressWarnings(['Instanceof']) - HelmStatusInfoData(Map map) { - def missingKeys = [] - def badTypes = [] - - def stringTypes = [ "status", "description", "lastDeployed"] - for (att in stringTypes) { - if (!map.containsKey(att)) { - missingKeys << att - } else if (!(map[att] instanceof String)) { - badTypes << "${att}: expected String, found ${map[att].getClass()}" - } - } - if (!map.containsKey('resources')) { - missingKeys << 'resources' - } else if (!(map['resources'] instanceof Map)) { - badTypes << "resources: expected Map, found ${map['resources'].getClass()}" - } - - HelmStatusData.handleMissingKeysOrBadTypes(missingKeys, badTypes) - - this.status = map['status'] as String - this.description = map['description'] as String - this.lastDeployed = map['lastDeployed'] as String - this.resources = map['resources'] as Map - } - -} diff --git a/src/org/ods/util/HelmStatusResource.groovy b/src/org/ods/util/HelmStatusResource.groovy deleted file mode 100644 index 7069d22ff..000000000 --- a/src/org/ods/util/HelmStatusResource.groovy +++ /dev/null @@ -1,25 +0,0 @@ -package org.ods.util - -import com.cloudbees.groovy.cps.NonCPS -import groovy.transform.TypeChecked - -@TypeChecked -class HelmStatusResource { - - String kind - String name - - @NonCPS - Map toMap() { - [ - kind: kind, - name: name, - ] - } - - @NonCPS - String toString() { - toMap().toMapString() - } - -} diff --git a/src/org/ods/util/HelmStatusResourceData.groovy b/src/org/ods/util/HelmStatusResourceData.groovy deleted file mode 100644 index d5a8c7492..000000000 --- a/src/org/ods/util/HelmStatusResourceData.groovy +++ /dev/null @@ -1,39 +0,0 @@ -package org.ods.util - -import groovy.transform.TypeChecked - -//Relevant helm status resource data we want to capture. This is inside the info data. -@TypeChecked -class HelmStatusResourceData { - - String apiVersion - String kind - String metadataName - // for kind "Pod" containerStatusData may be present - List containerStatuses = [] - - @SuppressWarnings(['Instanceof']) - HelmStatusResourceData(Map map) { - def missingKeys = [] - def badTypes = [] - - def stringTypes = [ "apiVersion", "kind", "metadataName"] - for (att in stringTypes) { - if (!map.containsKey(att)) { - missingKeys << att - } else if (!(map[att] instanceof String)) { - badTypes << "${att}: expected String, found ${map[att].getClass()}" - } - } - - HelmStatusData.handleMissingKeysOrBadTypes(missingKeys, badTypes) - - this.apiVersion = map['apiVersion'] as String - this.kind = map['kind'] as String - this.metadataName = map['metadataName'] as String - if (map.containsKey('containerStatuses')) { - this.containerStatuses = (map['containerStatuses'] as List) - } - } - -} diff --git a/src/org/ods/util/HelmStatusSimpleData.groovy b/src/org/ods/util/HelmStatusSimpleData.groovy index 12a70a660..8ef2a0c73 100644 --- a/src/org/ods/util/HelmStatusSimpleData.groovy +++ b/src/org/ods/util/HelmStatusSimpleData.groovy @@ -6,58 +6,98 @@ import groovy.transform.TypeChecked @TypeChecked class HelmStatusSimpleData { - String releaseName - String releaseRevision + String name + String version String namespace - String deployStatus - String deployDescription + String status + String description String lastDeployed - List resources + /** + * Resources names by kind + */ + Map> resourcesByKind - static HelmStatusSimpleData fromJsonObject(Object jsonObject) { - from(HelmStatusData.fromJsonObject(jsonObject)) - } + @SuppressWarnings(['Instanceof']) + static HelmStatusSimpleData fromJsonObject(Object object) { + try { + def rootObject = ensureMap(object, "") + + def infoObject = ensureMap(rootObject.info, "info") - @SuppressWarnings(['NestedForLoop']) - static HelmStatusSimpleData from(HelmStatusData status) { - def simpleResources = [] - for (resourceList in status.info.resources.values()) { - for (hsr in resourceList) { - simpleResources << new HelmStatusResource(kind: hsr.kind, name: hsr.metadataName) + // validation of missing keys or types + def missingKeys = [] + def badTypes = [] + def expectedStringAttribsForRoot = collectMissingStringAttributes(rootObject, + ["name", "namespace"]) + def expectedStringAttribsForInfo = collectMissingStringAttributes(infoObject, + ["status", "description", "last_deployed"]) + missingKeys.addAll(expectedStringAttribsForRoot.first) + missingKeys.addAll(expectedStringAttribsForInfo.first) + badTypes.addAll(expectedStringAttribsForRoot.second) + badTypes.addAll(expectedStringAttribsForInfo.second) + def att = "version" + if (!rootObject.containsKey(att)) { + missingKeys << att + } else if (!(rootObject[att] instanceof Integer)) { + badTypes << "${att}: expected Integer, found ${rootObject[att].getClass()}" + } + handleMissingKeysOrBadTypes(missingKeys, badTypes) + def resourcesObject = ensureMap(infoObject.resources, "info.resources") + // All resources are in an json object which organize resources by keys + // Examples are "v1/Cluster", "v1/ConfigMap" "v1/Deployment", "v1/Pod(related)" + // For these keys the map contains list of resources. + Map> resourcesByKind = [:] .withDefault { [] } + for (entry in resourcesObject.entrySet()) { + def key = entry.key as String + def resourceList = ensureList(entry.value, "info.resources.${key}") + resourceList.eachWithIndex { resourceJsonObject, i -> + def resourceContext = "info.resources.${key}.[${i}]" + def resource = extractResource(resourceJsonObject, resourceContext) + if (resource) { + resourcesByKind[resource.first] << resource.second + } + } } + String name = rootObject.name + String version = rootObject.version as String + String namespace = rootObject.namespace + String status = infoObject.status + String description = infoObject.description + String lastDeployed = infoObject["last_deployed"] + new HelmStatusSimpleData(name, version, namespace, status, description, lastDeployed, resourcesByKind) + } catch (IllegalArgumentException ex) { + throw new IllegalArgumentException( + "Unexpected helm status information in JSON at 'info': ${ex.message}") } - new HelmStatusSimpleData( - releaseName: status.name, - releaseRevision: status.version, - namespace: status.namespace, - deployStatus: status.info.status, - deployDescription: status.info.description, - lastDeployed: status.info.lastDeployed, - resources: simpleResources,) + } + + @SuppressWarnings(['ParameterCount']) + HelmStatusSimpleData(String name, String version, String namespace, + String status, String description, String lastDeployed, + Map> resourcesByKind) { + this.name = name + this.version = version + this.namespace = namespace + this.status = status + this.description = description + this.lastDeployed = lastDeployed + this.resourcesByKind = resourcesByKind } Map> getResourcesByKind(List kinds) { - def deploymentResources = resources.findAll { it.kind in kinds } - Map> resourcesByKind = [:] - deploymentResources.each { - if (!resourcesByKind.containsKey(it.kind)) { - resourcesByKind[it.kind] = [] - } - resourcesByKind[it.kind] << it.name - } - resourcesByKind + resourcesByKind.subMap(kinds) } @NonCPS Map toMap() { def result = [ - releaseName: releaseName, - releaseRevision: releaseRevision, + name: name, + version: version, namespace: namespace, - deployStatus: deployStatus, - deployDescription: deployDescription, + status: status, + description: description, lastDeployed: lastDeployed, - resources: resources.collect { [kind: it.kind, name: it.name] } + resourcesByKind: resourcesByKind, ] result } @@ -67,4 +107,82 @@ class HelmStatusSimpleData { toMap().toMapString() } + private static Tuple2 extractResource( + resourceJsonObject, String context) { + def resourceObject = ensureMap(resourceJsonObject, context) + Map resource = [:] + if (resourceObject.kind) { + resource.kind = resourceObject.kind + } + if (resource.kind == "PodList") { + return null + } + Map metadataObject = ensureMap( + resourceObject.metadata, "${context}.metadata") + if (metadataObject.name) { + resource["name"] = metadataObject.name + } + + def expected = collectMissingStringAttributes(resource, ["name", "kind"]) + handleMissingKeysOrBadTypes(expected.first, expected.second) + return new Tuple2(resource.kind, resource.name) + } + + @SuppressWarnings(['Instanceof']) + @NonCPS + private static Map ensureMap(Object obj, String context) { + if (obj == null) { + return [:] + } + if (!(obj instanceof Map)) { + def msg = context ? + "${context}: expected JSON object, found ${obj.getClass()}" : + "Expected JSON object, found ${obj.getClass()}" + + throw new IllegalArgumentException(msg) + } + obj as Map + } + + @SuppressWarnings(['Instanceof']) + @NonCPS + private static List ensureList(Object obj, String context) { + if (obj == null) { + return [] + } + if (!(obj instanceof List)) { + throw new IllegalArgumentException( + "${context}: expected JSON array, found ${obj.getClass()}") + } + obj as List + } + + @SuppressWarnings(['Instanceof']) + private static Tuple2, List> collectMissingStringAttributes( + Map jsonObject, List stringAttributes) { + def missingOrEmptyKeys = [] + def badTypes = [] + for (att in stringAttributes) { + if (! (jsonObject.containsKey(att) && jsonObject[att])) { + missingOrEmptyKeys << att + } else if (!(jsonObject[att] instanceof String)) { + badTypes << "${att}: expected String, found ${jsonObject[att].getClass()}" + } + } + new Tuple2(missingOrEmptyKeys, badTypes) + } + + @NonCPS + private static void handleMissingKeysOrBadTypes(List missingKeys, List badTypes) { + if (missingKeys || badTypes) { + def msgs = [] + if (missingKeys) { + msgs << "Missing keys: ${missingKeys.join(', ')}" + } + if (badTypes) { + msgs << "Bad types: ${badTypes.join(', ')}" + } + throw new IllegalArgumentException(msgs.join(".")) + } + } } diff --git a/test/groovy/org/ods/component/HelmDeploymentStrategySpec.groovy b/test/groovy/org/ods/component/HelmDeploymentStrategySpec.groovy index f4f7e73d3..e67e74023 100644 --- a/test/groovy/org/ods/component/HelmDeploymentStrategySpec.groovy +++ b/test/groovy/org/ods/component/HelmDeploymentStrategySpec.groovy @@ -44,22 +44,18 @@ class HelmDeploymentStrategySpec extends PipelineSpockTestBase { helmDefaultFlags : ["--install", "--atomic"], helmAdditionalFlags : [], helmStatus : [ - releaseName : "standalone-app", - releaseRevision : "43", + name : "standalone-app", + version : "43", namespace : "guardians-test", - deployStatus : "deployed", - deployDescription: "Upgrade complete", + status : "deployed", + description: "Upgrade complete", lastDeployed : "2024-03-04T15:21:09.34520527Z", - resources : [ - [kind: "ConfigMap", name: "core-appconfig-configmap"], - [kind: "Deployment", name: "core"], - [kind: "Deployment", name: "standalone-gateway"], - [kind: "Service", name: "core"], - [kind: "Service", name: "standalone-gateway"], - [kind: "Cluster", name: "edb-cluster"], - [kind: "Secret", name: "core-rsa-key-secret"], - [kind: "Secret", name: "core-security-exandradev-secret"], - [kind: "Secret", name: "core-security-unify-secret"] + resourcesByKind : [ + ConfigMap: ['core-appconfig-configmap'], + Deployment: ['core', 'standalone-gateway'], + Service: ['core', 'standalone-gateway'], + Cluster: ['edb-cluster'], + Secret: ['core-rsa-key-secret', 'core-security-exandradev-secret', 'core-security-unify-secret'] ] ] ] diff --git a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy index 4e22060c6..67965118c 100644 --- a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy +++ b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy @@ -1309,12 +1309,12 @@ class LeVADocumentUseCaseSpec extends SpecHelper { type :"helm", ] helmStatusAndMean["status"] == [ - releaseRevision :"2", - releaseName :"backend-helm-monorepo", + version :"2", + name :"backend-helm-monorepo", namespace: "kraemerh-dev", - deployDescription :"Upgrade complete", + description :"Upgrade complete", resources : "Deployment: backend-helm-monorepo-chart-component-a, backend-helm-monorepo-chart-component-b, Service: backend-helm-monorepo-chart", - deployStatus :"deployed", + status :"deployed", lastDeployed :"2024-06-26T12:59:51.270713404Z" ] nonHelmDeployments["backend-helm-monorepo-chart-component-a"] == [ diff --git a/test/groovy/org/ods/services/OpenShiftServiceSpec.groovy b/test/groovy/org/ods/services/OpenShiftServiceSpec.groovy index 8a96b37cf..492fe0923 100644 --- a/test/groovy/org/ods/services/OpenShiftServiceSpec.groovy +++ b/test/groovy/org/ods/services/OpenShiftServiceSpec.groovy @@ -261,7 +261,7 @@ class OpenShiftServiceSpec extends SpecHelper { def helmJsonText = new FixtureHelper().getResource("helmstatus.json").text when: - def helmStatusData = service.retrieveHelmStatus('guardians-test', 'standalone-app') + def helmStatusData = service.helmStatus('guardians-test', 'standalone-app') // OpenShiftService.DEPLOYMENT_KIND, OpenShiftService.DEPLOYMENTCONFIG_KIND,]) then: 1 * steps.sh( diff --git a/test/groovy/util/HelmStatusSpec.groovy b/test/groovy/util/HelmStatusSpec.groovy index de63030a9..f1a893173 100644 --- a/test/groovy/util/HelmStatusSpec.groovy +++ b/test/groovy/util/HelmStatusSpec.groovy @@ -11,31 +11,27 @@ class HelmStatusSpec extends SpecHelper { def file = new FixtureHelper().getResource("helmstatus.json") when: - def helmParsedStatus = HelmStatusData.fromJsonObject(new JsonSlurperClassic().parseText(file.text)) - def helmStatus = HelmStatusSimpleData.from(helmParsedStatus) + def jsonObject = new JsonSlurperClassic().parseText(file.text) + def helmStatus = HelmStatusSimpleData.fromJsonObject(jsonObject) def simpleStatusMap = helmStatus.toMap() - def simpleStatusNoResources = simpleStatusMap.findAll { k,v -> k != "resources"} + def simpleStatusNoResources = simpleStatusMap.findAll { k,v -> k != "resourcesByKind"} def deploymentResources = helmStatus.getResourcesByKind([ OpenShiftService.DEPLOYMENT_KIND, OpenShiftService.DEPLOYMENTCONFIG_KIND,]) then: simpleStatusNoResources == [ - releaseName: 'standalone-app', - releaseRevision: '43', + name: 'standalone-app', + version: '43', namespace: 'guardians-test', - deployStatus: 'deployed', - deployDescription: 'Upgrade complete', + status: 'deployed', + description: 'Upgrade complete', lastDeployed: '2024-03-04T15:21:09.34520527Z' ] - simpleStatusMap.resources == [ - [kind: 'ConfigMap', name:'core-appconfig-configmap'], - [kind: 'Deployment', name:'core'], - [kind: 'Deployment', name:'standalone-gateway'], - [kind: 'Service', name:'core'], - [kind: 'Service', name:'standalone-gateway'], - [kind: 'Cluster', name:'edb-cluster'], - [kind: 'Secret', name:'core-rsa-key-secret'], - [kind: 'Secret', name:'core-security-exandradev-secret'], - [kind: 'Secret', name:'core-security-unify-secret'] + simpleStatusMap.resourcesByKind == [ + ConfigMap: ['core-appconfig-configmap'], + Deployment: ['core', 'standalone-gateway'], + Service: ['core', 'standalone-gateway'], + Cluster: ['edb-cluster'], + Secret: ['core-rsa-key-secret', 'core-security-exandradev-secret', 'core-security-unify-secret'] ] deploymentResources == [ Deployment: [ 'core', 'standalone-gateway'] diff --git a/test/groovy/vars/OdsComponentStageRolloutOpenShiftDeploymentSpec.groovy b/test/groovy/vars/OdsComponentStageRolloutOpenShiftDeploymentSpec.groovy index e47edde1c..69bbecdaa 100644 --- a/test/groovy/vars/OdsComponentStageRolloutOpenShiftDeploymentSpec.groovy +++ b/test/groovy/vars/OdsComponentStageRolloutOpenShiftDeploymentSpec.groovy @@ -8,7 +8,7 @@ import org.ods.component.IContext import org.ods.services.OpenShiftService import org.ods.services.JenkinsService import org.ods.services.ServiceRegistry -import org.ods.util.HelmStatusData + import org.ods.util.HelmStatusSimpleData import org.ods.util.Logger import org.ods.util.PodData @@ -165,7 +165,7 @@ class OdsComponentStageRolloutOpenShiftDeploymentSpec extends PipelineSpockTestB IContext context = new Context(null, c, logger) OpenShiftService openShiftService = Mock(OpenShiftService.class) - openShiftService.helmStatus('guardians-test', 'standalone-app') >> HelmStatusSimpleData.from(HelmStatusData.fromJsonObject(new JsonSlurperClassic().parseText(helmJsonText))) + openShiftService.helmStatus('guardians-test', 'standalone-app') >> HelmStatusSimpleData.fromJsonObject(new JsonSlurperClassic().parseText(helmJsonText)) // todo: verify that we did not want to ensure that build images are tagged here. // - the org.ods.component.Context.artifactUriStore is not initialized with c when created above! // - as a consequence the build artifacts are empty so no retagging happens here. diff --git a/test/resources/deployments-data-helm.json b/test/resources/deployments-data-helm.json index 9a9f26b68..d90a2b15e 100644 --- a/test/resources/deployments-data-helm.json +++ b/test/resources/deployments-data-helm.json @@ -31,26 +31,21 @@ "helmAdditionalFlags": [ ], "helmStatus": { - "releaseName": "backend-helm-monorepo", - "releaseRevision": "2", + "name": "backend-helm-monorepo", + "version": "2", "namespace": "kraemerh-dev", - "deployStatus": "deployed", - "deployDescription": "Upgrade complete", + "status": "deployed", + "description": "Upgrade complete", "lastDeployed": "2024-06-26T12:59:51.270713404Z", - "resources": [ - { - "kind": "Deployment", - "name": "backend-helm-monorepo-chart-component-a" - }, - { - "kind": "Deployment", - "name": "backend-helm-monorepo-chart-component-b" - }, - { - "kind": "Service", - "name": "backend-helm-monorepo-chart" - } - ] + "resourcesByKind": { + "Deployment": [ + "backend-helm-monorepo-chart-component-a", + "backend-helm-monorepo-chart-component-b" + ], + "Service": [ + "backend-helm-monorepo-chart" + ] + } }, "repoId": "backend-helm-monorepo" }, @@ -85,26 +80,21 @@ "helmAdditionalFlags": [ ], "helmStatus": { - "releaseName": "backend-helm-monorepo", - "releaseRevision": "2", + "name": "backend-helm-monorepo", + "version": "2", "namespace": "kraemerh-dev", - "deployStatus": "deployed", - "deployDescription": "Upgrade complete", + "status": "deployed", + "description": "Upgrade complete", "lastDeployed": "2024-06-26T12:59:51.270713404Z", - "resources": [ - { - "kind": "Deployment", - "name": "backend-helm-monorepo-chart-component-a" - }, - { - "kind": "Deployment", - "name": "backend-helm-monorepo-chart-component-b" - }, - { - "kind": "Service", - "name": "backend-helm-monorepo-chart" - } - ] + "resourcesByKind": { + "Deployment": [ + "backend-helm-monorepo-chart-component-a", + "backend-helm-monorepo-chart-component-b" + ], + "Service": [ + "backend-helm-monorepo-chart" + ] + } }, "repoId": "backend-helm-monorepo" } From df43fe3afc5e1db7f68f6721c08b56d8166d5152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrich=20Kr=C3=A4mer?= Date: Wed, 24 Jul 2024 13:05:58 +0000 Subject: [PATCH 12/36] Helm status data to copy resourcesByKind --- src/org/ods/util/HelmStatusSimpleData.groovy | 36 ++++++++----------- .../usecase/LeVADocumentUseCaseSpec.groovy | 5 ++- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/org/ods/util/HelmStatusSimpleData.groovy b/src/org/ods/util/HelmStatusSimpleData.groovy index 8ef2a0c73..2818777ab 100644 --- a/src/org/ods/util/HelmStatusSimpleData.groovy +++ b/src/org/ods/util/HelmStatusSimpleData.groovy @@ -15,7 +15,7 @@ class HelmStatusSimpleData { /** * Resources names by kind */ - Map> resourcesByKind + Map > resourcesByKind @SuppressWarnings(['Instanceof']) static HelmStatusSimpleData fromJsonObject(Object object) { @@ -58,32 +58,25 @@ class HelmStatusSimpleData { } } } - String name = rootObject.name - String version = rootObject.version as String - String namespace = rootObject.namespace - String status = infoObject.status - String description = infoObject.description - String lastDeployed = infoObject["last_deployed"] - new HelmStatusSimpleData(name, version, namespace, status, description, lastDeployed, resourcesByKind) + def hs = new HelmStatusSimpleData() + hs.with { + name = rootObject.name + version = rootObject.version as String + namespace = rootObject.namespace + status = infoObject.status + description = infoObject.description + lastDeployed = infoObject["last_deployed"] + } + hs.resourcesByKind = resourcesByKind.collectEntries { kind, names -> + [ kind, names.collect() ] + } as Map > + hs } catch (IllegalArgumentException ex) { throw new IllegalArgumentException( "Unexpected helm status information in JSON at 'info': ${ex.message}") } } - @SuppressWarnings(['ParameterCount']) - HelmStatusSimpleData(String name, String version, String namespace, - String status, String description, String lastDeployed, - Map> resourcesByKind) { - this.name = name - this.version = version - this.namespace = namespace - this.status = status - this.description = description - this.lastDeployed = lastDeployed - this.resourcesByKind = resourcesByKind - } - Map> getResourcesByKind(List kinds) { resourcesByKind.subMap(kinds) } @@ -185,4 +178,5 @@ class HelmStatusSimpleData { throw new IllegalArgumentException(msgs.join(".")) } } + } diff --git a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy index 67965118c..af47457c8 100644 --- a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy +++ b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy @@ -1313,7 +1313,10 @@ class LeVADocumentUseCaseSpec extends SpecHelper { name :"backend-helm-monorepo", namespace: "kraemerh-dev", description :"Upgrade complete", - resources : "Deployment: backend-helm-monorepo-chart-component-a, backend-helm-monorepo-chart-component-b, Service: backend-helm-monorepo-chart", + resourcesByKind : [ + Deployment: ["backend-helm-monorepo-chart-component-a", "backend-helm-monorepo-chart-component-b"], + Service: ["backend-helm-monorepo-chart"], + ], status :"deployed", lastDeployed :"2024-06-26T12:59:51.270713404Z" ] From 0570dfe26542cc521c858d62a628cf1b38b2972a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrich=20Kr=C3=A4mer?= Date: Wed, 24 Jul 2024 14:56:31 +0000 Subject: [PATCH 13/36] Smaller tweaks --- .../usecase/LeVADocumentUseCase.groovy | 13 +++++++++--- src/org/ods/services/OpenShiftService.groovy | 5 ++--- src/org/ods/util/HelmStatusSimpleData.groovy | 20 +++++++++---------- src/org/ods/util/JsonLogUtil.groovy | 6 ++++-- 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy index 1c995d327..894d01286 100644 --- a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy +++ b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy @@ -1092,10 +1092,17 @@ class LeVADocumentUseCase extends DocGenUseCase { def keysInDoc = ['Technology-' + repo.id] def docHistory = this.getAndStoreDocumentHistory(documentType + '-' + repo.id, keysInDoc) + def helmStatusAndMean = getHelmStatusAndMean(repo.data.openshift.deployments ?: [:]) def data_ = [ metadata : this.getDocumentMetadata(this.DOCUMENT_TYPE_NAMES[documentType], repo), deployNote : deploynoteData, - helmStatus: getHelmStatusAndMean(repo.data.openshift.deployments ?: [:]), + deployment: [ + status: helmStatusAndMean?.status ?: [:], + mean: helmStatusAndMean?.mean ?: [:], + ], + deploymentSame: helmStatusAndMean, + deploymentStatus: helmStatusAndMean?.status ?: [:], + deploymentMean: helmStatusAndMean?.mean ?: [:], openShiftData: [ builds : repo.data.openshift.builds ?: '', deployments: getNonHelmDeployments(repo.data.openshift.deployments ?: [:]), @@ -1157,7 +1164,7 @@ class LeVADocumentUseCase extends DocGenUseCase { } def helmStatus = formatHelmStatus((meanHelm?.helmStatus ?: [:]) as Map) def meanHelmWithoutStatus = meanHelm.findAll { k, v -> k != 'helmStatus' } - [ + return [ status: helmStatus, mean: formatEmptyValues(meanHelmWithoutStatus) ] @@ -1185,7 +1192,7 @@ class LeVADocumentUseCase extends DocGenUseCase { deploymentsForTir.put(deploymentName, deployment.findAll { k, v -> k != 'podName' }) } } - deploymentsForTir + return deploymentsForTir } Map formatHelmStatus(Map helmStatus) { diff --git a/src/org/ods/services/OpenShiftService.groovy b/src/org/ods/services/OpenShiftService.groovy index 29c78ebae..04fbac86e 100644 --- a/src/org/ods/services/OpenShiftService.groovy +++ b/src/org/ods/services/OpenShiftService.groovy @@ -166,9 +166,8 @@ class OpenShiftService { label: "Gather Helm status for release ${release} in ${project}", returnStdout: true ).toString().trim() - def object = new JsonSlurperClassic().parseText(helmStdout) - def helmStatusData = HelmStatusSimpleData.fromJsonObject(object) - helmStatusData + def helmStatusMap = new JsonSlurperClassic().parseText(helmStdout) + return HelmStatusSimpleData.fromJsonObject(helmStatusMap) } catch (ex) { throw new RuntimeException("Helm status Failed (${ex.message})!" + "Helm could not gather status of ${release} in ${project}") diff --git a/src/org/ods/util/HelmStatusSimpleData.groovy b/src/org/ods/util/HelmStatusSimpleData.groovy index 2818777ab..9b81f3fa9 100644 --- a/src/org/ods/util/HelmStatusSimpleData.groovy +++ b/src/org/ods/util/HelmStatusSimpleData.groovy @@ -70,7 +70,7 @@ class HelmStatusSimpleData { hs.resourcesByKind = resourcesByKind.collectEntries { kind, names -> [ kind, names.collect() ] } as Map > - hs + return hs } catch (IllegalArgumentException ex) { throw new IllegalArgumentException( "Unexpected helm status information in JSON at 'info': ${ex.message}") @@ -78,7 +78,7 @@ class HelmStatusSimpleData { } Map> getResourcesByKind(List kinds) { - resourcesByKind.subMap(kinds) + return resourcesByKind.subMap(kinds) } @NonCPS @@ -92,27 +92,27 @@ class HelmStatusSimpleData { lastDeployed: lastDeployed, resourcesByKind: resourcesByKind, ] - result + return result } @NonCPS String toString() { - toMap().toMapString() + return toMap().toMapString() } private static Tuple2 extractResource( resourceJsonObject, String context) { def resourceObject = ensureMap(resourceJsonObject, context) Map resource = [:] - if (resourceObject.kind) { + if (resourceObject?.kind) { resource.kind = resourceObject.kind } - if (resource.kind == "PodList") { + if (resource?.kind == "PodList") { return null } Map metadataObject = ensureMap( resourceObject.metadata, "${context}.metadata") - if (metadataObject.name) { + if (metadataObject?.name) { resource["name"] = metadataObject.name } @@ -134,7 +134,7 @@ class HelmStatusSimpleData { throw new IllegalArgumentException(msg) } - obj as Map + return obj as Map } @SuppressWarnings(['Instanceof']) @@ -147,7 +147,7 @@ class HelmStatusSimpleData { throw new IllegalArgumentException( "${context}: expected JSON array, found ${obj.getClass()}") } - obj as List + return obj as List } @SuppressWarnings(['Instanceof']) @@ -162,7 +162,7 @@ class HelmStatusSimpleData { badTypes << "${att}: expected String, found ${jsonObject[att].getClass()}" } } - new Tuple2(missingOrEmptyKeys, badTypes) + return new Tuple2(missingOrEmptyKeys, badTypes) } @NonCPS diff --git a/src/org/ods/util/JsonLogUtil.groovy b/src/org/ods/util/JsonLogUtil.groovy index 3ce767e58..2b510cb21 100644 --- a/src/org/ods/util/JsonLogUtil.groovy +++ b/src/org/ods/util/JsonLogUtil.groovy @@ -12,13 +12,15 @@ class JsonLogUtil { if (msg) { logger.debug(msg) } - logger.debug(jsonToString(jsonObject)) + if (jsonObject) { + logger.debug(jsonToString(jsonObject)) + } } } @NonCPS static String jsonToString(Object jsonObject) { - JsonOutput.prettyPrint(JsonOutput.toJson(jsonObject)) + return JsonOutput.prettyPrint(JsonOutput.toJson(jsonObject)) } } From 9287243226f5dccdabe1227293036c2d7fd8ef2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrich=20Kr=C3=A4mer?= Date: Wed, 24 Jul 2024 18:04:32 +0000 Subject: [PATCH 14/36] Add @NonCPS in HelmStatusSimpleData --- src/org/ods/util/HelmStatusSimpleData.groovy | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/org/ods/util/HelmStatusSimpleData.groovy b/src/org/ods/util/HelmStatusSimpleData.groovy index 9b81f3fa9..21a5d8ffb 100644 --- a/src/org/ods/util/HelmStatusSimpleData.groovy +++ b/src/org/ods/util/HelmStatusSimpleData.groovy @@ -18,7 +18,8 @@ class HelmStatusSimpleData { Map > resourcesByKind @SuppressWarnings(['Instanceof']) - static HelmStatusSimpleData fromJsonObject(Object object) { + @NonCPS + static HelmStatusSimpleData fromJsonObject(Object object) { try { def rootObject = ensureMap(object, "") @@ -77,6 +78,7 @@ class HelmStatusSimpleData { } } + @NonCPS Map> getResourcesByKind(List kinds) { return resourcesByKind.subMap(kinds) } @@ -100,6 +102,7 @@ class HelmStatusSimpleData { return toMap().toMapString() } + @NonCPS private static Tuple2 extractResource( resourceJsonObject, String context) { def resourceObject = ensureMap(resourceJsonObject, context) @@ -151,6 +154,7 @@ class HelmStatusSimpleData { } @SuppressWarnings(['Instanceof']) + @NonCPS private static Tuple2, List> collectMissingStringAttributes( Map jsonObject, List stringAttributes) { def missingOrEmptyKeys = [] From bd54a5f044a63221fedef6281d06365029df938b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrich=20Kr=C3=A4mer?= Date: Wed, 24 Jul 2024 20:46:16 +0000 Subject: [PATCH 15/36] createTir keep helmStatus separate as deployment in template data --- .../ods/orchestration/usecase/LeVADocumentUseCase.groovy | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy index 894d01286..37442b6a1 100644 --- a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy +++ b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy @@ -1096,13 +1096,7 @@ class LeVADocumentUseCase extends DocGenUseCase { def data_ = [ metadata : this.getDocumentMetadata(this.DOCUMENT_TYPE_NAMES[documentType], repo), deployNote : deploynoteData, - deployment: [ - status: helmStatusAndMean?.status ?: [:], - mean: helmStatusAndMean?.mean ?: [:], - ], - deploymentSame: helmStatusAndMean, - deploymentStatus: helmStatusAndMean?.status ?: [:], - deploymentMean: helmStatusAndMean?.mean ?: [:], + deployment: helmStatusAndMean, openShiftData: [ builds : repo.data.openshift.builds ?: '', deployments: getNonHelmDeployments(repo.data.openshift.deployments ?: [:]), From e9c6eb90535b58c3761ff132f2c3ef15b1eaaba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrich=20Kr=C3=A4mer?= Date: Thu, 25 Jul 2024 11:09:11 +0000 Subject: [PATCH 16/36] createTir empty map for deployment appears to evaluate as truthy in templates --- .../ods/orchestration/usecase/LeVADocumentUseCase.groovy | 6 ++++-- .../orchestration/usecase/LeVADocumentUseCaseSpec.groovy | 8 ++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy index 37442b6a1..a3eab2d67 100644 --- a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy +++ b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy @@ -1092,11 +1092,9 @@ class LeVADocumentUseCase extends DocGenUseCase { def keysInDoc = ['Technology-' + repo.id] def docHistory = this.getAndStoreDocumentHistory(documentType + '-' + repo.id, keysInDoc) - def helmStatusAndMean = getHelmStatusAndMean(repo.data.openshift.deployments ?: [:]) def data_ = [ metadata : this.getDocumentMetadata(this.DOCUMENT_TYPE_NAMES[documentType], repo), deployNote : deploynoteData, - deployment: helmStatusAndMean, openShiftData: [ builds : repo.data.openshift.builds ?: '', deployments: getNonHelmDeployments(repo.data.openshift.deployments ?: [:]), @@ -1111,6 +1109,10 @@ class LeVADocumentUseCase extends DocGenUseCase { documentHistoryLatestVersionId: docHistory?.latestVersionId ?: 1, ] ] + def helmStatusAndMean = getHelmStatusAndMean(repo.data.openshift.deployments ?: [:]) + if (helmStatusAndMean) { + data_ << [deployment: helmStatusAndMean] + } JsonLogUtil.debug(logger, "createTIR - assembled data:", data_) // Code review report - in the special case of NO jira .. diff --git a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy index af47457c8..d9301f667 100644 --- a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy +++ b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy @@ -1347,10 +1347,18 @@ class LeVADocumentUseCaseSpec extends SpecHelper { when: def deploymentsData = new JsonSlurperClassic().parseText(file.text) + def helmStatusAndMean = usecase.getHelmStatusAndMean(deploymentsData.deployments) def nonHelmDeployments = usecase.getNonHelmDeployments(deploymentsData.deployments) + def data = [:] + if (helmStatusAndMean) { // must be falsy + data << [deployment: helmStatusAndMean] + } + helmStatusAndMean == [:] then: + data == [:] + nonHelmDeployments["backend-first-deploymentMean"] == [ type: "tailor", selector: "app=kraemerh-backend-first", From 7db11b4b5499391ba88aadf39a50db33b3050fe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrich=20Kr=C3=A4mer?= Date: Fri, 26 Jul 2024 06:12:22 +0000 Subject: [PATCH 17/36] human format for helm status info --- .../usecase/LeVADocumentUseCase.groovy | 50 +++++++++++-------- .../usecase/LeVADocumentUseCaseSpec.groovy | 17 +++---- 2 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy index a3eab2d67..7e3435d7f 100644 --- a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy +++ b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy @@ -1166,6 +1166,36 @@ class LeVADocumentUseCase extends DocGenUseCase { ] } + Map formatHelmStatus(Map helmStatus) { + Map > byKind = helmStatus?.resourcesByKind ?: [:] + String formattedResourcesByKind = byKind.collect { kind, resourcesOfKind -> + "${kind}: " + resourcesOfKind.collect { it }.join(', ') + }.sort().join('. ') + + def assembledHelmStatus = helmStatus.collectEntries { k, v -> + def formattedKey = formatHelmStatusKey(k) + if (k == 'resourcesByKind') { + [(formattedKey): formattedResourcesByKind ] + } else { + [(formattedKey): v] + } + } + return assembledHelmStatus + } + + String formatHelmStatusKey(String key) { + def keyFormat = [ + name: "Na\nme", + version: "Ver\\nsion", + namespace: "Name
space", + status: "Deployment
Status", + description: "Deployment{{{
}}} Description", + lastDeployed: "Deployment{{{br}}}Timestamp", + resourcesByKind: "Res\\\nources", + ] + return keyFormat.getOrDefault(key, key) + } + /** * Retrieves all non-helm deployments. * @@ -1191,26 +1221,6 @@ class LeVADocumentUseCase extends DocGenUseCase { return deploymentsForTir } - Map formatHelmStatus(Map helmStatus) { - List > resources = helmStatus?.resources ?: [] - def properResources = resources.findAll { - it?.kind && it?.name - } - def byKind = properResources.groupBy { it.kind } - String formattedResourcesByKind = byKind.collect { kind, resourcesOfKind -> - "${kind}: " + resourcesOfKind.collect { it.name }.join(', ') - }.sort().join(', ') - - def assembledHelmStatus = helmStatus.collectEntries { k, v -> - if (k == 'resources') { - [(k): formattedResourcesByKind ] - } else { - [(k): v] - } - } - return assembledHelmStatus - } - Map formatEmptyValues(Map deployment) { if (deployment?.type == 'tailor') { def tailorEmptyValues = [ diff --git a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy index d9301f667..18b4a5bb3 100644 --- a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy +++ b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy @@ -1309,16 +1309,13 @@ class LeVADocumentUseCaseSpec extends SpecHelper { type :"helm", ] helmStatusAndMean["status"] == [ - version :"2", - name :"backend-helm-monorepo", - namespace: "kraemerh-dev", - description :"Upgrade complete", - resourcesByKind : [ - Deployment: ["backend-helm-monorepo-chart-component-a", "backend-helm-monorepo-chart-component-b"], - Service: ["backend-helm-monorepo-chart"], - ], - status :"deployed", - lastDeployed :"2024-06-26T12:59:51.270713404Z" + "Version": "2", + "Name": "backend-helm-monorepo", + "Namespace": "kraemerh-dev", + "Deployment Description": "Upgrade complete", + "Resources": "Deployment: backend-helm-monorepo-chart-component-a, backend-helm-monorepo-chart-component-b. Service: backend-helm-monorepo-chart", + "Deployment Status" :"deployed", + "Deployment Timestamp" :"2024-06-26T12:59:51.270713404Z" ] nonHelmDeployments["backend-helm-monorepo-chart-component-a"] == [ podNamespace: "kraemerh-test", From 0c29daba6fad4c70e39ae4a214dcebcb05cbe071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrich=20Kr=C3=A4mer?= Date: Fri, 26 Jul 2024 07:18:10 +0000 Subject: [PATCH 18/36] remove newline embedding experiment - none worked --- .../usecase/LeVADocumentUseCase.groovy | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy index 7e3435d7f..c613bc2f7 100644 --- a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy +++ b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy @@ -1185,13 +1185,13 @@ class LeVADocumentUseCase extends DocGenUseCase { String formatHelmStatusKey(String key) { def keyFormat = [ - name: "Na\nme", - version: "Ver\\nsion", - namespace: "Name
space", - status: "Deployment
Status", - description: "Deployment{{{
}}} Description", - lastDeployed: "Deployment{{{br}}}Timestamp", - resourcesByKind: "Res\\\nources", + name: "Name", + version: "Version", + namespace: "Namespace", + status: "Deployment Status", + description: "Deployment Description", + lastDeployed: "Deployment Timestamp", + resourcesByKind: "Resources", ] return keyFormat.getOrDefault(key, key) } From c762b3b39eec20d63c1a433bb1f157ce279913b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrich=20Kr=C3=A4mer?= Date: Fri, 26 Jul 2024 07:18:32 +0000 Subject: [PATCH 19/36] Adjust changelog for new PR --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 341e42d3c..f58541334 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ### Changed * Enhance SSDS Document Generation Performance using New Atlassian APIs ([#1084](https://github.com/opendevstack/ods-jenkins-shared-library/issues/1084)) -* TIR - remove dynamic pod data and surface helm status ([#1135](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1135)) +* TIR - remove dynamic pod data and surface helm status ([#1143](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1143)) ### Fixed * Fix Tailor deployment drifts for D, Q envs ([#1055](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1055)) From 51a99f624edad537785d8046c5996cf1e189ccab Mon Sep 17 00:00:00 2001 From: Carlos Antelo <180194797+anteloro-boeh@users.noreply.github.com> Date: Mon, 14 Oct 2024 12:03:01 +0200 Subject: [PATCH 20/36] chore: TIR HTML formatting and helm tables reformatted --- .../usecase/LeVADocumentUseCase.groovy | 283 +++++++++++------- .../util/HtmlFormatterUtil.groovy | 64 ++++ 2 files changed, 231 insertions(+), 116 deletions(-) create mode 100644 src/org/ods/orchestration/util/HtmlFormatterUtil.groovy diff --git a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy index c613bc2f7..31c4ac5da 100644 --- a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy +++ b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy @@ -1,5 +1,7 @@ package org.ods.orchestration.usecase +import org.apache.commons.lang.StringUtils +import org.ods.orchestration.util.HtmlFormatterUtil import org.ods.util.JsonLogUtil import com.cloudbees.groovy.cps.NonCPS @@ -177,9 +179,9 @@ class LeVADocumentUseCase extends DocGenUseCase { def data_ = [ metadata: this.getDocumentMetadata(this.DOCUMENT_TYPE_NAMES[documentType]), data : [ - sections : sections, - requirements: requirementsForDocument, - documentHistory: docHistory?.getDocGenFormat() ?: [], + sections : sections, + requirements : requirementsForDocument, + documentHistory : docHistory?.getDocGenFormat() ?: [], documentHistoryLatestVersionId: docHistory?.latestVersionId ?: 1, ] ] @@ -243,10 +245,10 @@ class LeVADocumentUseCase extends DocGenUseCase { def data_ = [ metadata: this.getDocumentMetadata(this.DOCUMENT_TYPE_NAMES[documentType], repo), data : [ - sections: sections, - tests: tests, - modules: modules, - documentHistory: docHistory?.getDocGenFormat() ?: [], + sections : sections, + tests : tests, + modules : modules, + documentHistory : docHistory?.getDocGenFormat() ?: [], documentHistoryLatestVersionId: docHistory?.latestVersionId ?: 1, ] ] @@ -581,12 +583,12 @@ class LeVADocumentUseCase extends DocGenUseCase { def riskPriority = obtainEnum("RiskPriority", r.riskPriority) return [ - key: r.key, - name: r.name, - description: convertImages(r.description), - proposedMeasures: "Mitigations: ${ mitigationsText }
Tests: ${ testsText }", - requirement: requirement, - gxpRelevance: gxpRelevance ? gxpRelevance."short" : "None", + key : r.key, + name : r.name, + description : convertImages(r.description), + proposedMeasures : "Mitigations: ${mitigationsText}
Tests: ${testsText}", + requirement : requirement, + gxpRelevance : gxpRelevance ? gxpRelevance."short" : "None", probabilityOfOccurrence: probabilityOfOccurrence ? probabilityOfOccurrence."short" : "None", severityOfImpact : severityOfImpact ? severityOfImpact."short" : "None", probabilityOfDetection : probabilityOfDetection ? probabilityOfDetection."short" : "None", @@ -614,8 +616,8 @@ class LeVADocumentUseCase extends DocGenUseCase { def data_ = [ metadata: metadata, data : [ - sections: sections, - documentHistory: docHistory?.getDocGenFormat() ?: [], + sections : sections, + documentHistory : docHistory?.getDocGenFormat() ?: [], documentHistoryLatestVersionId: docHistory?.latestVersionId ?: 1, ] ] @@ -708,9 +710,9 @@ class LeVADocumentUseCase extends DocGenUseCase { techSpec: testIssue.techSpecs.join(", ") ?: "N/A" ] }), - testsOdsService: testsOfRepoTypeOdsService, - testsOdsCode : testsOfRepoTypeOdsCode, - documentHistory: docHistory?.getDocGenFormat() ?: [], + testsOdsService : testsOfRepoTypeOdsService, + testsOdsCode : testsOfRepoTypeOdsCode, + documentHistory : docHistory?.getDocGenFormat() ?: [], documentHistoryLatestVersionId: docHistory?.latestVersionId ?: 1, ] ] @@ -784,9 +786,9 @@ class LeVADocumentUseCase extends DocGenUseCase { summary : discrepancies.conclusion.summary, statement: discrepancies.conclusion.statement ], - testsOdsService : testsOfRepoTypeOdsService, - testsOdsCode : testsOfRepoTypeOdsCode, - documentHistory : docHistory?.getDocGenFormat() ?: [], + testsOdsService : testsOfRepoTypeOdsService, + testsOdsCode : testsOfRepoTypeOdsCode, + documentHistory : docHistory?.getDocGenFormat() ?: [], documentHistoryLatestVersionId: docHistory?.latestVersionId ?: 1, ] ] @@ -879,7 +881,7 @@ class LeVADocumentUseCase extends DocGenUseCase { acceptanceTestFiles : SortUtil.sortIssuesByProperties(acceptanceTestData.testReportFiles.collect { file -> [name: file.name, path: file.path, text: file.text] } ?: [], ["name"]), - documentHistory: docHistory?.getDocGenFormat() ?: [], + documentHistory : docHistory?.getDocGenFormat() ?: [], documentHistoryLatestVersionId: docHistory?.latestVersionId ?: 1, ] ] @@ -923,7 +925,7 @@ class LeVADocumentUseCase extends DocGenUseCase { steps : sortTestSteps(testIssue.steps) ] }), - documentHistory: docHistory?.getDocGenFormat() ?: [], + documentHistory : docHistory?.getDocGenFormat() ?: [], documentHistoryLatestVersionId: docHistory?.latestVersionId ?: 1, ] ] @@ -1009,10 +1011,10 @@ class LeVADocumentUseCase extends DocGenUseCase { def data_ = [ metadata: this.getDocumentMetadata(this.DOCUMENT_TYPE_NAMES[documentType], repo), data : [ - sections: sections, - documentHistory: docHistory?.getDocGenFormat() ?: [], + sections : sections, + documentHistory : docHistory?.getDocGenFormat() ?: [], documentHistoryLatestVersionId: docHistory?.latestVersionId ?: 1, - isGxpProject: this.project.isGxp(), + isGxpProject : this.project.isGxp(), ] ] def uri = this.createDocument(documentType, null, data_, [:], null, getDocumentTemplateName(documentType), watermarkText) @@ -1036,7 +1038,7 @@ class LeVADocumentUseCase extends DocGenUseCase { def repos = this.project.repositories.collect { Map it -> def clone = it.clone() - clone.printurl = it.url.replaceAll('/+','$0\u200B') + clone.printurl = it.url.replaceAll('/+', '$0\u200B') //Add break space in url in manufacturer def p = ~'https?://\\S*' @@ -1056,10 +1058,10 @@ class LeVADocumentUseCase extends DocGenUseCase { def data_ = [ metadata: this.getDocumentMetadata(this.DOCUMENT_TYPE_NAMES[documentType]), data : [ - project_key : this.project.key, - repositories: repos, - sections : sections, - documentHistory: docHistory?.getDocGenFormat() ?: [], + project_key : this.project.key, + repositories : repos, + sections : sections, + documentHistory : docHistory?.getDocGenFormat() ?: [], documentHistoryLatestVersionId: docHistory?.latestVersionId ?: 1, ] ] @@ -1082,6 +1084,7 @@ class LeVADocumentUseCase extends DocGenUseCase { def watermarkText = this.getWatermarkText(documentType, this.project.hasWipJiraIssues()) def deploynoteData = 'Components were built & deployed during installation.' + if (repo.data.openshift.resurrectedBuild) { deploynoteData = "Components were found, and are 'up to date' with version control -no deployments happend!\r" + " SCRR was restored from the corresponding creation build (${repo.data.openshift.resurrectedBuild})" @@ -1102,17 +1105,20 @@ class LeVADocumentUseCase extends DocGenUseCase { testResults : [ installation: installationTestData?.testResults ], - data: [ - repo : repo, - sections: sections, - documentHistory: docHistory?.getDocGenFormat() ?: [], + data : [ + repo : repo, + sections : sections, + documentHistory : docHistory?.getDocGenFormat() ?: [], documentHistoryLatestVersionId: docHistory?.latestVersionId ?: 1, ] ] + def helmStatusAndMean = getHelmStatusAndMean(repo.data.openshift.deployments ?: [:]) + if (helmStatusAndMean) { data_ << [deployment: helmStatusAndMean] } + JsonLogUtil.debug(logger, "createTIR - assembled data:", data_) // Code review report - in the special case of NO jira .. @@ -1133,7 +1139,77 @@ class LeVADocumentUseCase extends DocGenUseCase { return document } - return this.createDocument(documentType, repo, data_, [:], modifier, getDocumentTemplateName(documentType, repo), watermarkText) + def documentData = data_ + + // This will alter the data_ map, due to documentData being a reference to data_, not a deep copy + formatDocumentTIRData(documentData) + + JsonLogUtil.debug(logger, "createTIR - createDocument documentData:", documentData) + + return this.createDocument(documentType, repo, documentData, [:], modifier, getDocumentTemplateName(documentType, repo), watermarkText) + } + + private static def formatDocumentTIRData(Map data) { + if (data.openShiftData?.builds) { + formatTIRBuilds(data) + } + + if (data.deployment?.mean?.type == "helm") { + formatTIRHelmDeployment(data) + } + } + + private static def formatTIRBuilds(Map data) { + def builds = data.openShiftData.builds as Map + + def capitalizeKey = { entry -> + [(StringUtils.capitalize(entry.key)): entry.value ] + } + + def formattedBuilds = builds.collectEntries {res, resValue -> + def newResValue = resValue.collectEntries { capitalizeKey(it) } + + [(res): newResValue] + } + + data.openShiftData.builds = formattedBuilds + } + + private static def formatTIRHelmDeployment(Map data) { + def htmlOrDefault = { value, defaultVal, toHtml -> + value?.isEmpty() + ? defaultVal + : toHtml(value) + } + + // Move values to its final place and format them accordingly + def mean = data.deployment.mean + def status = data.deployment.status + + mean.releaseName = status?.name ?: "None" + mean.releaseRevision = status?.version ?: "None" + mean.namespace = status?.namespace ?: "None" + + mean.selector = mean.selector.replaceAll("=", ": ") + mean.helmValues = htmlOrDefault(mean.helmValues, 'None', HtmlFormatterUtil.&toUl) + mean.helmValuesFiles = htmlOrDefault(mean.helmValuesFiles, 'None', HtmlFormatterUtil.&toUl) + mean.helmDefaultFlags = htmlOrDefault(mean.helmDefaultFlags, 'None', { HtmlFormatterUtil.toSpan(it as List, "inner-span", " ") }) + mean.helmAdditionalFlags = htmlOrDefault(mean.helmAdditionalFlags, 'None', { HtmlFormatterUtil.toSpan(it as List, "inner-span", " ") }) + mean.helmEnvBasedValuesFiles = htmlOrDefault(mean.helmEnvBasedValuesFiles, 'None', HtmlFormatterUtil.&toUl) + + status.deployStatus = (status.status == "deployed") + ? "Successfully deployed" + : status.status + + status?.resources = htmlOrDefault(status?.resourcesByKind, 'None', HtmlFormatterUtil.&toUl) + + // These fields are not needed anymore due to their values + // being moved to their final place + status?.remove("name") + status?.remove("version") + status?.remove("namespace") + status?.remove("status") + status?.remove("resourcesByKind") } /** @@ -1143,59 +1219,32 @@ class LeVADocumentUseCase extends DocGenUseCase { * Otherwise keys 'status' and 'means' each contain a * map suited to format the information in a template. */ - Map > getHelmStatusAndMean(Map> deployments) { + Map> getHelmStatusAndMean(Map> deployments) { // collect first helm release def deploymentMeanHelm = deployments.find { it.key.endsWith('-deploymentMean') && it.value.type == "helm" } def meanHelm = deploymentMeanHelm?.value + if (!meanHelm) { return [:] } + String releaseName = meanHelm?.helmReleaseName if (!releaseName) { logger.warn("No helmReleaseName name in ${meanHelm}: skipping") return [:] } - def helmStatus = formatHelmStatus((meanHelm?.helmStatus ?: [:]) as Map) + def helmStatus = (meanHelm?.helmStatus ?: [:]) as Map def meanHelmWithoutStatus = meanHelm.findAll { k, v -> k != 'helmStatus' } + return [ status: helmStatus, - mean: formatEmptyValues(meanHelmWithoutStatus) + mean : meanHelmWithoutStatus ] } - Map formatHelmStatus(Map helmStatus) { - Map > byKind = helmStatus?.resourcesByKind ?: [:] - String formattedResourcesByKind = byKind.collect { kind, resourcesOfKind -> - "${kind}: " + resourcesOfKind.collect { it }.join(', ') - }.sort().join('. ') - - def assembledHelmStatus = helmStatus.collectEntries { k, v -> - def formattedKey = formatHelmStatusKey(k) - if (k == 'resourcesByKind') { - [(formattedKey): formattedResourcesByKind ] - } else { - [(formattedKey): v] - } - } - return assembledHelmStatus - } - - String formatHelmStatusKey(String key) { - def keyFormat = [ - name: "Name", - version: "Version", - namespace: "Namespace", - status: "Deployment Status", - description: "Deployment Description", - lastDeployed: "Deployment Timestamp", - resourcesByKind: "Resources", - ] - return keyFormat.getOrDefault(key, key) - } - /** * Retrieves all non-helm deployments. * @@ -1213,7 +1262,7 @@ class LeVADocumentUseCase extends DocGenUseCase { Map> deploymentsForTir = [:] deploymentsNotHelm.each { String deploymentName, Map deployment -> if (deploymentName.endsWith('-deploymentMean')) { - deploymentsForTir.put(deploymentName, formatEmptyValues(deployment)) + deploymentsForTir.put(deploymentName, setEmptyValuesDefaults(deployment)) } else { deploymentsForTir.put(deploymentName, deployment.findAll { k, v -> k != 'podName' }) } @@ -1221,12 +1270,12 @@ class LeVADocumentUseCase extends DocGenUseCase { return deploymentsForTir } - Map formatEmptyValues(Map deployment) { + Map setEmptyValuesDefaults(Map deployment) { if (deployment?.type == 'tailor') { def tailorEmptyValues = [ tailorParamFile: 'None', - tailorParams: 'None', - tailorPreserve: 'No extra resources specified to be preserved' + tailorParams : 'None', + tailorPreserve : 'No extra resources specified to be preserved' ] return deployment.collectEntries { k, v -> def newValue = (tailorEmptyValues.containsKey(k) && !v) ? tailorEmptyValues[k] : v @@ -1235,9 +1284,9 @@ class LeVADocumentUseCase extends DocGenUseCase { } if (deployment?.type == 'helm') { def helmEmptyValues = [ - helmAdditionalFlags: 'None', + helmAdditionalFlags : 'None', helmEnvBasedValuesFiles: 'None', - helmValues: 'None', + helmValues : 'None', ] return deployment.collectEntries { k, v -> def newValue = (helmEmptyValues.containsKey(k) && !v) ? helmEmptyValues[k] : v @@ -1280,7 +1329,7 @@ class LeVADocumentUseCase extends DocGenUseCase { @NonCPS private def computeKeysInDocForTRC(def data) { - return data.collect { it.subMap(['key', 'risks', 'tests']).values() }.flatten() + return data.collect { it.subMap(['key', 'risks', 'tests']).values() }.flatten() } String createTRC(Map repo = null, Map data = null) { @@ -1325,8 +1374,8 @@ class LeVADocumentUseCase extends DocGenUseCase { def data_ = [ metadata: this.getDocumentMetadata(this.DOCUMENT_TYPE_NAMES[documentType], repo), data : [ - sections: sections, - documentHistory: docHistory?.getDocGenFormat() ?: [], + sections : sections, + documentHistory : docHistory?.getDocGenFormat() ?: [], documentHistoryLatestVersionId: docHistory?.latestVersionId ?: 1, ] ] @@ -1373,10 +1422,10 @@ class LeVADocumentUseCase extends DocGenUseCase { String getDocumentTemplatesVersion() { def capability = this.project.getCapability('LeVADocs') - return capability.templatesVersion ? "${capability.templatesVersion}": Project.DEFAULT_TEMPLATE_VERSION + return capability.templatesVersion ? "${capability.templatesVersion}" : Project.DEFAULT_TEMPLATE_VERSION } - boolean shouldCreateArtifact (String documentType, Map repo) { + boolean shouldCreateArtifact(String documentType, Map repo) { List nonArtifactDocTypes = [ DocumentType.TIR as String, DocumentType.DTR as String @@ -1385,11 +1434,11 @@ class LeVADocumentUseCase extends DocGenUseCase { return !(documentType && nonArtifactDocTypes.contains(documentType) && repo) } - Map getFiletypeForDocumentType (String documentType) { + Map getFiletypeForDocumentType(String documentType) { if (!documentType) { - throw new RuntimeException ('Cannot lookup Null docType for storage!') + throw new RuntimeException('Cannot lookup Null docType for storage!') } - Map defaultTypes = [storage: 'zip', content: 'pdf' ] + Map defaultTypes = [storage: 'zip', content: 'pdf'] if (DOCUMENT_TYPE_NAMES.containsKey(documentType)) { return defaultTypes @@ -1495,7 +1544,7 @@ class LeVADocumentUseCase extends DocGenUseCase { def softwareDesignSpecs = testIssue.getResolvedTechnicalSpecifications() .findAll { it.softwareDesignSpec } .collect { it.key } - def riskLevels = testIssue.getResolvedRisks(). collect { + def riskLevels = testIssue.getResolvedRisks().collect { def value = obtainEnum("SeverityOfImpact", it.severityOfImpact) return value ? value.text : "None" } @@ -1507,18 +1556,18 @@ class LeVADocumentUseCase extends DocGenUseCase { } [ - moduleName: testIssue.components.join(", "), - testKey: testIssue.key, - description: this.convertImages(description ?: 'N/A'), - systemRequirement: testIssue.requirements ? testIssue.requirements.join(", ") : "N/A", + moduleName : testIssue.components.join(", "), + testKey : testIssue.key, + description : this.convertImages(description ?: 'N/A'), + systemRequirement : testIssue.requirements ? testIssue.requirements.join(", ") : "N/A", softwareDesignSpec: (softwareDesignSpecs.join(", ")) ?: "N/A", - riskLevel: riskLevels ? riskLevels.join(", ") : "N/A" + riskLevel : riskLevels ? riskLevels.join(", ") : "N/A" ] } } protected List obtainCodeReviewReport(List repos) { - def reports = repos.collect { r -> + def reports = repos.collect { r -> // resurrect? Map resurrectedDocument = resurrectAndStashDocument('SCRR-MD', r, false) this.steps.echo "Resurrected 'SCRR' for ${r.id} -> (${resurrectedDocument.found})" @@ -1570,14 +1619,14 @@ class LeVADocumentUseCase extends DocGenUseCase { def isReleaseManagerComponent = gitUrl.endsWith("${this.project.key}-${normComponentName}.git".toLowerCase()) if (isReleaseManagerComponent) { - return [ : ] + return [:] } def repo_ = this.project.repositories.find { [it.id, it.name, it.metadata.name].contains(normComponentName) } if (!repo_) { - def repoNamesAndIds = this.project.repositories. collect { [id: it.id, name: it.name] } + def repoNamesAndIds = this.project.repositories.collect { [id: it.id, name: it.name] } throw new RuntimeException("Error: unable to create ${documentType}. Could not find a repository " + "configuration with id or name equal to '${normComponentName}' for " + "Jira component '${component.name}' in project '${this.project.key}'. Please check " + @@ -1593,23 +1642,23 @@ class LeVADocumentUseCase extends DocGenUseCase { return [ (component.name): [ - key : component.name, - componentName : component.name, - componentId : metadata.id ?: 'N/A - part of this application', - componentType : INTERNAL_TO_EXT_COMPONENT_TYPES.get(repo_.type?.toLowerCase()), - doInstall : MROPipelineUtil.PipelineConfig.INSTALLABLE_REPO_TYPES.contains(repo_.type), - odsRepoType : repo_.type?.toLowerCase(), - description : metadata.description, - nameOfSoftware : normComponentName ?: metadata.name, - references : metadata.references ?: 'N/A', - supplier : metadata.supplier, - version : (repo_.type?.toLowerCase() == MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_CODE) ? + key : component.name, + componentName : component.name, + componentId : metadata.id ?: 'N/A - part of this application', + componentType : INTERNAL_TO_EXT_COMPONENT_TYPES.get(repo_.type?.toLowerCase()), + doInstall : MROPipelineUtil.PipelineConfig.INSTALLABLE_REPO_TYPES.contains(repo_.type), + odsRepoType : repo_.type?.toLowerCase(), + description : metadata.description, + nameOfSoftware : normComponentName ?: metadata.name, + references : metadata.references ?: 'N/A', + supplier : metadata.supplier, + version : (repo_.type?.toLowerCase() == MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_CODE) ? this.project.buildParams.version : metadata.version, - requirements : component.getResolvedSystemRequirements(), - requirementKeys : component.requirements, + requirements : component.getResolvedSystemRequirements(), + requirementKeys : component.requirements, softwareDesignSpecKeys: sowftwareDesignSpecs.collect { it.key }, - softwareDesignSpec: sowftwareDesignSpecs + softwareDesignSpec : sowftwareDesignSpecs ] ] } @@ -1620,7 +1669,7 @@ class LeVADocumentUseCase extends DocGenUseCase { test.getResolvedComponents().collect { [test: test.key, component: it.name] } }.flatten() issueComponentMapping.groupBy { it.component }.collectEntries { c, v -> - [(c.replaceAll("Technology-", "")): v.collect { it.test } ] + [(c.replaceAll("Technology-", "")): v.collect { it.test }] } } @@ -1628,9 +1677,9 @@ class LeVADocumentUseCase extends DocGenUseCase { def componentTestMapping = computeComponentsUnitTests(unitTests) this.project.repositories.collect { [ - id: it.id, + id : it.id, description: it.metadata?.description, - tests: componentTestMapping[it.id]? componentTestMapping[it.id].join(", "): "None defined" + tests : componentTestMapping[it.id] ? componentTestMapping[it.id].join(", ") : "None defined" ] } } @@ -1677,7 +1726,7 @@ class LeVADocumentUseCase extends DocGenUseCase { buildUrl : this.steps.env.BUILD_URL, jobName : this.steps.env.JOB_NAME ], - referencedDocs : this.getReferencedDocumentsVersion() + referencedDocs: this.getReferencedDocumentsVersion() ] metadata.header = ["${documentTypeName}, Config Item: ${metadata.buildParameter.configItem}"] @@ -1739,7 +1788,7 @@ class LeVADocumentUseCase extends DocGenUseCase { " this document cannot be considered final.*" } - if (! documentVersionId) { + if (!documentVersionId) { def metadata = this.getDocumentMetadata(documentType) documentVersionId = "${metadata.version}-${metadata.jenkins.buildNumber}" } @@ -1819,6 +1868,7 @@ class LeVADocumentUseCase extends DocGenUseCase { } return jiraIssues } + protected List getDocumentTrackingIssuesForHistory(String documentType, List environments = null) { def jiraDocumentLabels = this.getJiraTrackingIssueLabelsForDocTypeAndEnvs(documentType, environments) def jiraIssues = this.project.getDocumentTrackingIssuesForHistory(jiraDocumentLabels) @@ -1847,7 +1897,7 @@ class LeVADocumentUseCase extends DocGenUseCase { [(sec.section): sec + [content: this.convertImages(sec.content), show: this.project.isIssueToBeShown(sec)]] } - if (!sections || sections.isEmpty() ) { + if (!sections || sections.isEmpty()) { sections = this.levaFiles.getDocumentChapterData(documentType) if (!this.project.data.jira.undoneDocChapters) { this.project.data.jira.undoneDocChapters = [:] @@ -1876,7 +1926,7 @@ class LeVADocumentUseCase extends DocGenUseCase { if (this.project.historyForDocumentExists(document)) { this.project.getHistoryForDocument(document).getVersion() } else { - def trackingIssues = this.getDocumentTrackingIssuesForHistory(document, environments) + def trackingIssues = this.getDocumentTrackingIssuesForHistory(document, environments) this.jiraUseCase.getLatestDocVersionId(trackingIssues) } } @@ -1906,7 +1956,7 @@ class LeVADocumentUseCase extends DocGenUseCase { def doc = dt as String def version = getVersion(this.project, doc) - return [(doc): "${this.project.buildParams.configItem} / See version created within this change", + return [(doc) : "${this.project.buildParams.configItem} / See version created within this change", ("${doc}_version" as String): version] } } @@ -1919,11 +1969,11 @@ class LeVADocumentUseCase extends DocGenUseCase { if (!version) { // The document has not (yet) been generated in this pipeline run. def envs = Environment.values().collect { it.toString() } - def trackingIssues = this.getDocumentTrackingIssuesForHistory(doc, envs) + def trackingIssues = this.getDocumentTrackingIssuesForHistory(doc, envs) version = this.jiraUseCase.getLatestDocVersionId(trackingIssues) if (project.isWorkInProgress || LeVADocumentScheduler.getFirstCreationEnvironment(doc) == - project.buildParams.targetEnvironmentToken ) { + project.buildParams.targetEnvironmentToken) { // Either this is a developer preview or the history is to be updated in this environment. version += 1L } @@ -1940,6 +1990,7 @@ class LeVADocumentUseCase extends DocGenUseCase { private def computeKeysInDocForTCR(def data) { return data.collect { it.subMap(['key', 'requirements', 'bugs']).values() }.flatten() } + protected String replaceDashToNonBreakableUnicode(theString) { return theString?.replaceAll('-', '‑') } diff --git a/src/org/ods/orchestration/util/HtmlFormatterUtil.groovy b/src/org/ods/orchestration/util/HtmlFormatterUtil.groovy new file mode 100644 index 000000000..c8f1ac7ff --- /dev/null +++ b/src/org/ods/orchestration/util/HtmlFormatterUtil.groovy @@ -0,0 +1,64 @@ +package org.ods.orchestration.util + +class HtmlFormatterUtil { + + static toUl(Map map, String selector = "inner-ul") { + return objectToUl(map, selector) + } + + static toUl(List list, String selector = "inner-ul") { + return objectToUl(list, selector) + } + + static toSpan(Map map, String selector = "inner-span", String sep = ", ") { + return objectToSpan(map, selector, sep) + } + + static toSpan(List list, String selector = "inner-span", String sep = ", ") { + return objectToSpan(list, selector, sep) + } + + private static objectToUl(Object obj, String selector) { + if (obj == null) { + return "" + } + + def resolve = resolver({ HtmlFormatterUtil.toUl(it, selector) }) + + def li = { it -> + it instanceof Map.Entry + ? "
  • ${it.key}: ${resolve(it.value)}
  • " + : "
  • ${resolve(it)}
  • " + } + + def lis = obj.collect { li(it) }.join() + + return "
      ${lis}
    " + } + + private static objectToSpan(Object obj, String selector, String sep) { + if (obj == null) { + return "" + } + + def resolve = resolver({ HtmlFormatterUtil.toSpan(it, selector, sep) }) + + def spanContent = { it -> + it instanceof Map.Entry + ? "${it.key}: ${resolve(it.value)}" + : "${resolve(it)}" + } + + def spanBody = obj.collect { spanContent(it) }.join(sep) + + return "${spanBody}" + } + + private static Closure resolver(Closure collectionValue) { + { value -> + value instanceof Collection + ? collectionValue(value) + : value + } + } +} From 73129a740239c1202af9c202191a12137c43760a Mon Sep 17 00:00:00 2001 From: Carlos Antelo <180194797+anteloro-boeh@users.noreply.github.com> Date: Mon, 14 Oct 2024 13:46:52 +0200 Subject: [PATCH 21/36] fix: CodeNarc rules compliance --- CHANGELOG.md | 2 +- .../usecase/LeVADocumentUseCase.groovy | 14 +++++++------- .../orchestration/util/HtmlFormatterUtil.groovy | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f58541334..ca24d4077 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ### Changed * Enhance SSDS Document Generation Performance using New Atlassian APIs ([#1084](https://github.com/opendevstack/ods-jenkins-shared-library/issues/1084)) -* TIR - remove dynamic pod data and surface helm status ([#1143](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1143)) +* TIR - remove dynamic pod data, surface helm status and helm report tables reformatting ([#1143](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1143)) ### Fixed * Fix Tailor deployment drifts for D, Q envs ([#1055](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1055)) diff --git a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy index 31c4ac5da..accbebbc5 100644 --- a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy +++ b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy @@ -632,7 +632,7 @@ class LeVADocumentUseCase extends DocGenUseCase { * return the technical specification task * otherwise, return the system requirements * @param risk - * @return + * @return the requirement */ private Project.JiraDataItem getRequirement(Project.JiraDataItem risk) { List requirements = risk.getResolvedTechnicalSpecifications() @@ -1149,7 +1149,7 @@ class LeVADocumentUseCase extends DocGenUseCase { return this.createDocument(documentType, repo, documentData, [:], modifier, getDocumentTemplateName(documentType, repo), watermarkText) } - private static def formatDocumentTIRData(Map data) { + private def formatDocumentTIRData(Map data) { if (data.openShiftData?.builds) { formatTIRBuilds(data) } @@ -1159,14 +1159,14 @@ class LeVADocumentUseCase extends DocGenUseCase { } } - private static def formatTIRBuilds(Map data) { + private def formatTIRBuilds(Map data) { def builds = data.openShiftData.builds as Map def capitalizeKey = { entry -> [(StringUtils.capitalize(entry.key)): entry.value ] } - def formattedBuilds = builds.collectEntries {res, resValue -> + def formattedBuilds = builds.collectEntries { res, resValue -> def newResValue = resValue.collectEntries { capitalizeKey(it) } [(res): newResValue] @@ -1175,7 +1175,7 @@ class LeVADocumentUseCase extends DocGenUseCase { data.openShiftData.builds = formattedBuilds } - private static def formatTIRHelmDeployment(Map data) { + private def formatTIRHelmDeployment(Map data) { def htmlOrDefault = { value, defaultVal, toHtml -> value?.isEmpty() ? defaultVal @@ -1193,8 +1193,8 @@ class LeVADocumentUseCase extends DocGenUseCase { mean.selector = mean.selector.replaceAll("=", ": ") mean.helmValues = htmlOrDefault(mean.helmValues, 'None', HtmlFormatterUtil.&toUl) mean.helmValuesFiles = htmlOrDefault(mean.helmValuesFiles, 'None', HtmlFormatterUtil.&toUl) - mean.helmDefaultFlags = htmlOrDefault(mean.helmDefaultFlags, 'None', { HtmlFormatterUtil.toSpan(it as List, "inner-span", " ") }) - mean.helmAdditionalFlags = htmlOrDefault(mean.helmAdditionalFlags, 'None', { HtmlFormatterUtil.toSpan(it as List, "inner-span", " ") }) + mean.helmDefaultFlags = htmlOrDefault(mean.helmDefaultFlags, 'None') { HtmlFormatterUtil.toSpan(it as List, "inner-span", " ") } + mean.helmAdditionalFlags = htmlOrDefault(mean.helmAdditionalFlags, 'None') { HtmlFormatterUtil.toSpan(it as List, "inner-span", " ") } mean.helmEnvBasedValuesFiles = htmlOrDefault(mean.helmEnvBasedValuesFiles, 'None', HtmlFormatterUtil.&toUl) status.deployStatus = (status.status == "deployed") diff --git a/src/org/ods/orchestration/util/HtmlFormatterUtil.groovy b/src/org/ods/orchestration/util/HtmlFormatterUtil.groovy index c8f1ac7ff..e1cf0e83f 100644 --- a/src/org/ods/orchestration/util/HtmlFormatterUtil.groovy +++ b/src/org/ods/orchestration/util/HtmlFormatterUtil.groovy @@ -26,7 +26,7 @@ class HtmlFormatterUtil { def resolve = resolver({ HtmlFormatterUtil.toUl(it, selector) }) def li = { it -> - it instanceof Map.Entry + it in Map.Entry ? "
  • ${it.key}: ${resolve(it.value)}
  • " : "
  • ${resolve(it)}
  • " } @@ -44,7 +44,7 @@ class HtmlFormatterUtil { def resolve = resolver({ HtmlFormatterUtil.toSpan(it, selector, sep) }) def spanContent = { it -> - it instanceof Map.Entry + it in Map.Entry ? "${it.key}: ${resolve(it.value)}" : "${resolve(it)}" } @@ -56,7 +56,7 @@ class HtmlFormatterUtil { private static Closure resolver(Closure collectionValue) { { value -> - value instanceof Collection + value in Collection ? collectionValue(value) : value } From 673adb8938b78a6ccaf6a854833dbb7bc27f5dc2 Mon Sep 17 00:00:00 2001 From: Carlos Antelo <180194797+anteloro-boeh@users.noreply.github.com> Date: Mon, 14 Oct 2024 14:30:06 +0200 Subject: [PATCH 22/36] fix: TIR spec tests failing due to change in mean and status structures --- .../usecase/LeVADocumentUseCaseSpec.groovy | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy index 18b4a5bb3..0aa380fdf 100644 --- a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy +++ b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy @@ -1296,8 +1296,8 @@ class LeVADocumentUseCaseSpec extends SpecHelper { helmStatusAndMean["mean"] == [ chartDir : "chart", repoId : "backend-helm-monorepo", - helmAdditionalFlags: "None", - helmEnvBasedValuesFiles :"None", + helmAdditionalFlags: [], + helmEnvBasedValuesFiles : [], helmValues :[ registry :"image-registry.openshift-image-registry.svc:5000", componentId :"backend-helm-monorepo" @@ -1309,13 +1309,19 @@ class LeVADocumentUseCaseSpec extends SpecHelper { type :"helm", ] helmStatusAndMean["status"] == [ - "Version": "2", - "Name": "backend-helm-monorepo", - "Namespace": "kraemerh-dev", - "Deployment Description": "Upgrade complete", - "Resources": "Deployment: backend-helm-monorepo-chart-component-a, backend-helm-monorepo-chart-component-b. Service: backend-helm-monorepo-chart", - "Deployment Status" :"deployed", - "Deployment Timestamp" :"2024-06-26T12:59:51.270713404Z" + "version": "2", + "name": "backend-helm-monorepo", + "namespace": "kraemerh-dev", + "description": "Upgrade complete", + "resourcesByKind": [ + "Deployment": [ + "backend-helm-monorepo-chart-component-a", + "backend-helm-monorepo-chart-component-b" + ], + "Service": ["backend-helm-monorepo-chart"] + ], + "status" :"deployed", + "lastDeployed" :"2024-06-26T12:59:51.270713404Z" ] nonHelmDeployments["backend-helm-monorepo-chart-component-a"] == [ podNamespace: "kraemerh-test", From b6e4c344106216f3d7538f7eb379ae2cda8d10b5 Mon Sep 17 00:00:00 2001 From: Carlos Antelo <180194797+anteloro-boeh@users.noreply.github.com> Date: Mon, 14 Oct 2024 17:11:40 +0200 Subject: [PATCH 23/36] chore: merge with master --- .../orchestration/usecase/LeVADocumentUseCaseSpec.groovy | 6 ------ test/groovy/org/ods/services/OpenShiftServiceSpec.groovy | 1 - 2 files changed, 7 deletions(-) diff --git a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy index 5b3d9e488..78c3d675f 100644 --- a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy +++ b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy @@ -2,16 +2,10 @@ package org.ods.orchestration.usecase import groovy.json.JsonSlurper import groovy.json.JsonSlurperClassic -import groovy.util.logging.Log import groovy.util.logging.Slf4j import org.apache.commons.io.FileUtils import org.junit.Rule import org.junit.rules.TemporaryFolder -import org.ods.util.ILogger -import org.ods.services.ServiceRegistry -import org.ods.util.PodData -import spock.lang.Unroll - import org.ods.orchestration.service.DocGenService import org.ods.orchestration.service.JiraService import org.ods.orchestration.service.LeVADocumentChaptersFileService diff --git a/test/groovy/org/ods/services/OpenShiftServiceSpec.groovy b/test/groovy/org/ods/services/OpenShiftServiceSpec.groovy index 492fe0923..46abf6596 100644 --- a/test/groovy/org/ods/services/OpenShiftServiceSpec.groovy +++ b/test/groovy/org/ods/services/OpenShiftServiceSpec.groovy @@ -2,7 +2,6 @@ package org.ods.services import groovy.json.JsonSlurperClassic -import org.ods.util.HelmStatusSimpleData import org.ods.util.ILogger import org.ods.util.IPipelineSteps import org.ods.util.Logger From c93da508504e441b8f2bd3bfb60f9abcb8a95b09 Mon Sep 17 00:00:00 2001 From: Carlos Antelo <180194797+anteloro-boeh@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:03:07 +0200 Subject: [PATCH 24/36] chore: merge with master --- src/org/ods/component/Context.groovy | 2 + .../component/HelmDeploymentStrategy.groovy | 109 +++++++++++------- .../usecase/LeVADocumentUseCase.groovy | 2 - .../HelmDeploymentStrategySpec.groovy | 2 +- 4 files changed, 72 insertions(+), 43 deletions(-) diff --git a/src/org/ods/component/Context.groovy b/src/org/ods/component/Context.groovy index 7640584d6..30c382330 100644 --- a/src/org/ods/component/Context.groovy +++ b/src/org/ods/component/Context.groovy @@ -57,6 +57,8 @@ class Context implements IContext { } if (executedWithErrors) { + // FIXME this ExecutionException() constructor is protected, + // if there are errors, this exception will fail to be created due to constructor's access restrictions throw new ExecutionException("Jenkins serialization issue, when: context.assemble()") } } diff --git a/src/org/ods/component/HelmDeploymentStrategy.groovy b/src/org/ods/component/HelmDeploymentStrategy.groovy index 908cc14fb..689d45466 100644 --- a/src/org/ods/component/HelmDeploymentStrategy.groovy +++ b/src/org/ods/component/HelmDeploymentStrategy.groovy @@ -1,11 +1,12 @@ package org.ods.component -import groovy.json.JsonOutput import groovy.transform.TypeChecked import groovy.transform.TypeCheckingMode import org.ods.services.JenkinsService import org.ods.services.OpenShiftService +import org.ods.util.HelmStatusSimpleData import org.ods.util.ILogger +import org.ods.util.JsonLogUtil import org.ods.util.PipelineSteps import org.ods.util.PodData @@ -94,24 +95,24 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { logger.info "Rolling out ${context.componentId} with HELM, selector: ${options.selector}" helmUpgrade(context.targetProject) - - def deploymentResources = openShift.getResourcesForComponent( - context.targetProject, DEPLOYMENT_KINDS, options.selector - ) - logger.info("${this.class.name} -- DEPLOYMENT RESOURCES") - logger.info( - JsonOutput.prettyPrint( - JsonOutput.toJson(deploymentResources))) + HelmStatusSimpleData helmStatus = openShift.helmStatus(context.targetProject, options.helmReleaseName) + if (logger.debugMode) { + def deploymentResources = getDeploymentResources(helmStatus) + def helmStatusMap = helmStatus.toMap() + JsonLogUtil.debug(logger, "${this.class.name} -- HELM STATUS".toString(), helmStatusMap) + JsonLogUtil.debug(logger, "${this.class.name} -- DEPLOYMENT RESOURCES".toString(), deploymentResources) + } // // FIXME: pauseRollouts is non trivial to determine! // // we assume that Helm does "Deployment" that should work for most // // cases since they don't have triggers. // metadataSvc.updateMetadata(false, deploymentResources) - def rolloutData = getRolloutData(deploymentResources) ?: [:] - logger.info(JsonOutput.prettyPrint(JsonOutput.toJson(rolloutData))) + def rolloutData = getRolloutData(helmStatus) + logger.info(JsonLogUtil.jsonToString(rolloutData)) return rolloutData } + @SuppressWarnings(['UnnecessaryCast']) // otherwise IDE marked up helmUpgrade call private void helmUpgrade(String targetProject) { steps.dir(options.chartDir) { jenkins.maybeWithPrivateKeyCredentials(options.helmPrivateKeyCredentialsId) { String pkeyFile -> @@ -124,7 +125,7 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { options.helmValues['componentId'] = context.componentId // we persist the original ones set from outside - here we just add ours - Map mergedHelmValues = [:] + def mergedHelmValues = [:] as Map mergedHelmValues << options.helmValues // we add the global ones - this allows usage in subcharts @@ -140,7 +141,7 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { mergedHelmValues['global.imageTag'] = options.imageTag // deal with dynamic value files - which are env dependent - def mergedHelmValuesFiles = [] + def mergedHelmValuesFiles = [] as List mergedHelmValuesFiles.addAll(options.helmValuesFiles) options.helmEnvBasedValuesFiles.each { envValueFile -> @@ -161,45 +162,73 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { } } - // rollout returns a map like this: - // [ - // 'DeploymentConfig/foo': [[podName: 'foo-a', ...], [podName: 'foo-b', ...]], - // 'Deployment/bar': [[podName: 'bar-a', ...]] - // ] + private Map> getDeploymentResources(HelmStatusSimpleData helmStatus) { + helmStatus.getResourcesByKind(DEPLOYMENT_KINDS) + } + @TypeChecked(TypeCheckingMode.SKIP) private Map> getRolloutData( - Map> deploymentResources) { - + HelmStatusSimpleData helmStatus + ) { + @SuppressWarnings(['LineLength']) // for the github permalinks + // Why do we need podData when helm should take care of the install + // 1. FinalizeOdsComponent#verifyDeploymentsBuiltByODS() will fail at + // https://github.com/opendevstack/ods-jenkins-shared-library/blob/7aab67dad73298b1388eca3517a1a2ea856a2b8e/src/org/ods/orchestration/phases/FinalizeOdsComponent.groovy#L165 + // unless it can associate all pods with a deployment resource. + // 2. DeploymentDescripter requires to have for each deployment resource + // a deployment mean with postfix -deploymentMean + // 3. Image importing in DeployOdsComponent at + // https://github.com/opendevstack/ods-jenkins-shared-library/blob/97463eebc986f1558c37d5ff5a84ec188f9e92d0/src/org/ods/orchestration/phases/DeployOdsComponent.groovy#L51) + // currently is driven by images in PodData#containers. + // + // If possible this should be redesigned so that the shared library does not have to + // concern itself with pods anymore. def rolloutData = [:] - deploymentResources.each { resourceKind, resourceNames -> - resourceNames.each { resourceName -> - def podData = [] + helmStatus.resourcesByKind.each { kind, names -> + if (!(kind in DEPLOYMENT_KINDS)) { + return // continues with next + } + names.each { name -> + context.addDeploymentToArtifactURIs("${name}-deploymentMean", + [ + type: 'helm', + selector: options.selector, + chartDir: options.chartDir, + helmReleaseName: options.helmReleaseName, + helmEnvBasedValuesFiles: options.helmEnvBasedValuesFiles, + helmValuesFiles: options.helmValuesFiles, + helmValues: options.helmValues, + helmDefaultFlags: options.helmDefaultFlags, + helmAdditionalFlags: options.helmAdditionalFlags, + helmStatus: helmStatus.toMap(), + ] + ) + def podDataContext = [ + "targetProject=${context.targetProject}", + "selector=${options.selector}", + "name=${name}", + ] + def msgPodsNotFound = "Could not find 'running' pod(s) for '${podDataContext.join(', ')}'" + List podData = null for (def i = 0; i < options.deployTimeoutRetries; i++) { - podData = openShift.checkForPodData(context.targetProject, options.selector, resourceName) - if (!podData.isEmpty()) { + podData = openShift.checkForPodData(context.targetProject, options.selector, name) + if (podData) { break } - steps.echo("Could not find 'running' pod(s) with label '${options.selector}' - waiting") + steps.echo("${msgPodsNotFound} - waiting") steps.sleep(12) } - context.addDeploymentToArtifactURIs("${resourceName}-deploymentMean", - [ - 'type': 'helm', - 'selector': options.selector, - 'chartDir': options.chartDir, - 'helmReleaseName': options.helmReleaseName, - 'helmEnvBasedValuesFiles': options.helmEnvBasedValuesFiles, - 'helmValuesFiles': options.helmValuesFiles, - 'helmValues': options.helmValues, - 'helmDefaultFlags': options.helmDefaultFlags, - 'helmAdditionalFlags': options.helmAdditionalFlags, - ]) - rolloutData["${resourceKind}/${resourceName}"] = podData + if (!podData) { + throw new RuntimeException(msgPodsNotFound) + } + JsonLogUtil.debug(logger, "Helm podData for ${podDataContext.join(', ')}:".toString(), podData) + + rolloutData["${kind}/${name}"] = podData // TODO: Once the orchestration pipeline can deal with multiple replicas, // update this to store multiple pod artifacts. // TODO: Potential conflict if resourceName is duplicated between // Deployment and DeploymentConfig resource. - context.addDeploymentToArtifactURIs(resourceName, podData[0]?.toMap()) + context.addDeploymentToArtifactURIs(name, podData[0]?.toMap()) } } rolloutData diff --git a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy index ceeb4f724..0d0b3500a 100644 --- a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy +++ b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy @@ -1071,8 +1071,6 @@ class LeVADocumentUseCase extends DocGenUseCase { @SuppressWarnings('CyclomaticComplexity') String createTIR(Map repo, Map data) { - logger.debug("createTIR - repo:${prettyPrint(toJson(repo))}, data:${prettyPrint(toJson(data))}") - def documentType = DocumentType.TIR as String def installationTestData = data?.tests?.installation diff --git a/test/groovy/org/ods/component/HelmDeploymentStrategySpec.groovy b/test/groovy/org/ods/component/HelmDeploymentStrategySpec.groovy index e67e74023..7e1480401 100644 --- a/test/groovy/org/ods/component/HelmDeploymentStrategySpec.groovy +++ b/test/groovy/org/ods/component/HelmDeploymentStrategySpec.groovy @@ -101,7 +101,7 @@ class HelmDeploymentStrategySpec extends PipelineSpockTestBase { HelmDeploymentStrategy strategy = Spy(HelmDeploymentStrategy, constructorArgs: [null, context, config, openShiftService, jenkinsService, logger]) when: - strategy.getRolloutData(helmStatus) + strategy.getRolloutData(helmStatus as HelmStatusSimpleData) def actualDeploymentMeans = context.getBuildArtifactURIs() From ec961de969df779475971047f688705f5fa43531 Mon Sep 17 00:00:00 2001 From: Carlos Antelo <180194797+anteloro-boeh@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:29:36 +0200 Subject: [PATCH 25/36] feature: TIR removed Name, Instance and Version rows from Deployment table --- .../ods/orchestration/usecase/LeVADocumentUseCase.groovy | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy index 0d0b3500a..fa609289b 100644 --- a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy +++ b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy @@ -1131,9 +1131,9 @@ class LeVADocumentUseCase extends DocGenUseCase { return document } + // This will alter the data_ map, due to documentData being a reference to data_, not a deep copy def documentData = data_ - // This will alter the data_ map, due to documentData being a reference to data_, not a deep copy formatDocumentTIRData(documentData) return this.createDocument(documentType, repo, documentData, [:], modifier, getDocumentTemplateName(documentType, repo), watermarkText) @@ -1176,8 +1176,6 @@ class LeVADocumentUseCase extends DocGenUseCase { def mean = data.deployment.mean def status = data.deployment.status - mean.releaseName = status?.name ?: "None" - mean.releaseRevision = status?.version ?: "None" mean.namespace = status?.namespace ?: "None" mean.selector = mean.selector.replaceAll("=", ": ") @@ -1195,8 +1193,6 @@ class LeVADocumentUseCase extends DocGenUseCase { // These fields are not needed anymore due to their values // being moved to their final place - status?.remove("name") - status?.remove("version") status?.remove("namespace") status?.remove("status") status?.remove("resourcesByKind") From eefbfe562254de7ed2b13dbb6260162a5f35e198 Mon Sep 17 00:00:00 2001 From: "x2odsantelo@boehringer-ingelheim.com" Date: Fri, 18 Oct 2024 13:07:00 +0200 Subject: [PATCH 26/36] chore: added unit tests for html formatter, and fixed PR reported issues --- .../component/HelmDeploymentStrategy.groovy | 53 ++++++++-------- .../phases/DeployOdsComponent.groovy | 8 +-- .../usecase/LeVADocumentUseCase.groovy | 29 ++++----- .../util/HtmlFormatterUtil.groovy | 63 +++++-------------- src/org/ods/services/OpenShiftService.groovy | 11 +--- ...tusSimpleData.groovy => HelmStatus.groovy} | 29 +++++---- src/org/ods/util/ILogger.groovy | 6 ++ src/org/ods/util/Logger.groovy | 34 ++++++++++ .../HelmDeploymentStrategySpec.groovy | 6 +- .../org/ods/core/test/LoggerStub.groovy | 32 ++++++++++ .../usecase/LeVADocumentUseCaseSpec.groovy | 5 +- .../util/HtmlFormatterUtilSpec.groovy | 46 ++++++++++++++ test/groovy/util/HelmStatusSpec.groovy | 8 ++- test/groovy/util/OpenShiftHelper.groovy | 8 +++ ...StageRolloutOpenShiftDeploymentSpec.groovy | 4 +- 15 files changed, 212 insertions(+), 130 deletions(-) rename src/org/ods/util/{HelmStatusSimpleData.groovy => HelmStatus.groovy} (90%) create mode 100644 test/groovy/org/ods/orchestration/util/HtmlFormatterUtilSpec.groovy diff --git a/src/org/ods/component/HelmDeploymentStrategy.groovy b/src/org/ods/component/HelmDeploymentStrategy.groovy index 689d45466..60f168ffb 100644 --- a/src/org/ods/component/HelmDeploymentStrategy.groovy +++ b/src/org/ods/component/HelmDeploymentStrategy.groovy @@ -4,9 +4,8 @@ import groovy.transform.TypeChecked import groovy.transform.TypeCheckingMode import org.ods.services.JenkinsService import org.ods.services.OpenShiftService -import org.ods.util.HelmStatusSimpleData +import org.ods.util.HelmStatus import org.ods.util.ILogger -import org.ods.util.JsonLogUtil import org.ods.util.PipelineSteps import org.ods.util.PodData @@ -95,12 +94,10 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { logger.info "Rolling out ${context.componentId} with HELM, selector: ${options.selector}" helmUpgrade(context.targetProject) - HelmStatusSimpleData helmStatus = openShift.helmStatus(context.targetProject, options.helmReleaseName) + HelmStatus helmStatus = openShift.helmStatus(context.targetProject, options.helmReleaseName) if (logger.debugMode) { - def deploymentResources = getDeploymentResources(helmStatus) def helmStatusMap = helmStatus.toMap() - JsonLogUtil.debug(logger, "${this.class.name} -- HELM STATUS".toString(), helmStatusMap) - JsonLogUtil.debug(logger, "${this.class.name} -- DEPLOYMENT RESOURCES".toString(), deploymentResources) + logger.jsonDebug(helmStatusMap, "${this.class.name} -- HELM STATUS") } // // FIXME: pauseRollouts is non trivial to determine! @@ -108,11 +105,10 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { // // cases since they don't have triggers. // metadataSvc.updateMetadata(false, deploymentResources) def rolloutData = getRolloutData(helmStatus) - logger.info(JsonLogUtil.jsonToString(rolloutData)) + logger.jsonDebug(rolloutData) return rolloutData } - @SuppressWarnings(['UnnecessaryCast']) // otherwise IDE marked up helmUpgrade call private void helmUpgrade(String targetProject) { steps.dir(options.chartDir) { jenkins.maybeWithPrivateKeyCredentials(options.helmPrivateKeyCredentialsId) { String pkeyFile -> @@ -125,7 +121,7 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { options.helmValues['componentId'] = context.componentId // we persist the original ones set from outside - here we just add ours - def mergedHelmValues = [:] as Map + def mergedHelmValues = [:] mergedHelmValues << options.helmValues // we add the global ones - this allows usage in subcharts @@ -141,7 +137,7 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { mergedHelmValues['global.imageTag'] = options.imageTag // deal with dynamic value files - which are env dependent - def mergedHelmValuesFiles = [] as List + def mergedHelmValuesFiles = [] mergedHelmValuesFiles.addAll(options.helmValuesFiles) options.helmEnvBasedValuesFiles.each { envValueFile -> @@ -153,7 +149,7 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { targetProject, options.helmReleaseName, mergedHelmValuesFiles, - mergedHelmValues, + mergedHelmValues as Map, options.helmDefaultFlags, options.helmAdditionalFlags, options.helmDiff @@ -162,32 +158,37 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { } } - private Map> getDeploymentResources(HelmStatusSimpleData helmStatus) { - helmStatus.getResourcesByKind(DEPLOYMENT_KINDS) - } - @TypeChecked(TypeCheckingMode.SKIP) private Map> getRolloutData( - HelmStatusSimpleData helmStatus + HelmStatus helmStatus ) { - @SuppressWarnings(['LineLength']) // for the github permalinks // Why do we need podData when helm should take care of the install - // 1. FinalizeOdsComponent#verifyDeploymentsBuiltByODS() will fail at - // https://github.com/opendevstack/ods-jenkins-shared-library/blob/7aab67dad73298b1388eca3517a1a2ea856a2b8e/src/org/ods/orchestration/phases/FinalizeOdsComponent.groovy#L165 + // 1. FinalizeOdsComponent#verifyDeploymentsBuiltByODS() will fail at: + // commit: 7aab67dad73298b1388eca3517a1a2ea856a2b8e + // ... + // def message = "DeploymentConfigs (component: '${repo.id}') found that are not ODS managed: " + + // ... // unless it can associate all pods with a deployment resource. - // 2. DeploymentDescripter requires to have for each deployment resource + // 2. DeploymentDescriptor requires to have for each deployment resource // a deployment mean with postfix -deploymentMean // 3. Image importing in DeployOdsComponent at - // https://github.com/opendevstack/ods-jenkins-shared-library/blob/97463eebc986f1558c37d5ff5a84ec188f9e92d0/src/org/ods/orchestration/phases/DeployOdsComponent.groovy#L51) + // commit: 97463eebc986f1558c37d5ff5a84ec188f9e92d0 + // requires to have a deployment resource for each image. + // ... + // if (!openShiftDir.startsWith('openshift')) { + // deploymentDescriptor.deployments.each { String deploymentName, Map deployment -> + // ... // currently is driven by images in PodData#containers. // // If possible this should be redesigned so that the shared library does not have to // concern itself with pods anymore. def rolloutData = [:] - helmStatus.resourcesByKind.each { kind, names -> - if (!(kind in DEPLOYMENT_KINDS)) { - return // continues with next - } + + def deploymentKinds = helmStatus.resourcesByKind + .findAll { it.key in DEPLOYMENT_KINDS } + .collectEntries { it } + + deploymentKinds.each { kind, names -> names.each { name -> context.addDeploymentToArtifactURIs("${name}-deploymentMean", [ @@ -221,7 +222,7 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { if (!podData) { throw new RuntimeException(msgPodsNotFound) } - JsonLogUtil.debug(logger, "Helm podData for ${podDataContext.join(', ')}:".toString(), podData) + logger.jsonDebug(podData, "Helm podData for ${podDataContext.join(', ')}:") rolloutData["${kind}/${name}"] = podData // TODO: Once the orchestration pipeline can deal with multiple replicas, diff --git a/src/org/ods/orchestration/phases/DeployOdsComponent.groovy b/src/org/ods/orchestration/phases/DeployOdsComponent.groovy index 54287add4..a054be7f1 100644 --- a/src/org/ods/orchestration/phases/DeployOdsComponent.groovy +++ b/src/org/ods/orchestration/phases/DeployOdsComponent.groovy @@ -11,7 +11,6 @@ import org.ods.services.GitService import org.ods.orchestration.util.DeploymentDescriptor import org.ods.orchestration.util.MROPipelineUtil import org.ods.orchestration.util.Project -import org.ods.util.JsonLogUtil import org.ods.util.PodData // Deploy ODS comnponent (code or service) to 'qa' or 'prod'. @@ -42,8 +41,7 @@ class DeployOdsComponent { DeploymentDescriptor deploymentDescriptor steps.dir(openShiftDir) { deploymentDescriptor = DeploymentDescriptor.readFromFile(steps) - JsonLogUtil.debug(logger, "DeploymentDescriptor '${openShiftDir}': ".toString(), - deploymentDescriptor.deployments) + logger.jsonDebug(deploymentDescriptor.deployments, "DeploymentDescriptor '${openShiftDir}': ") } if (!repo.data.openshift.deployments) { repo.data.openshift.deployments = [:] @@ -81,7 +79,7 @@ class DeployOdsComponent { if (!podData) { throw new RuntimeException(msgPodsNotFound) } - JsonLogUtil.debug(logger, "Helm podData for '${podDataContext.join(', ')}': ".toString(), podData) + logger.jsonDebug(podData, "Helm podData for '${podDataContext.join(', ')}': ") // TODO: Once the orchestration pipeline can deal with multiple replicas, // update this to deal with multiple pods. @@ -241,7 +239,7 @@ class DeployOdsComponent { def helmStatus = os.helmStatus(project.targetProject, deploymentMean.helmReleaseName) def helmStatusMap = helmStatus.toMap() deploymentMean.helmStatus = helmStatusMap - JsonLogUtil.debug(logger, "${this.class.name} -- HELM STATUS".toString(), helmStatusMap) + logger.jsonDebug(helmStatusMap, "${this.class.name} -- HELM STATUS") } } jenkins.maybeWithPrivateKeyCredentials(secretName) { String pkeyFile -> diff --git a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy index fa609289b..8cdc90478 100644 --- a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy +++ b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy @@ -1071,6 +1071,8 @@ class LeVADocumentUseCase extends DocGenUseCase { @SuppressWarnings('CyclomaticComplexity') String createTIR(Map repo, Map data) { + logger.debug("createTIR - repo:${repo}, data:${data}") + def documentType = DocumentType.TIR as String def installationTestData = data?.tests?.installation @@ -1113,6 +1115,8 @@ class LeVADocumentUseCase extends DocGenUseCase { data_ << [deployment: helmStatusAndMean] } + logger.jsonDebug(data_, "createTIR - assembled data:") + // Code review report - in the special case of NO jira .. def codeReviewReport if (this.project.isAssembleMode && !this.jiraUseCase.jira && @@ -1166,12 +1170,6 @@ class LeVADocumentUseCase extends DocGenUseCase { } private def formatTIRHelmDeployment(Map data) { - def htmlOrDefault = { value, defaultVal, toHtml -> - value?.isEmpty() - ? defaultVal - : toHtml(value) - } - // Move values to its final place and format them accordingly def mean = data.deployment.mean def status = data.deployment.status @@ -1179,17 +1177,14 @@ class LeVADocumentUseCase extends DocGenUseCase { mean.namespace = status?.namespace ?: "None" mean.selector = mean.selector.replaceAll("=", ": ") - mean.helmValues = htmlOrDefault(mean.helmValues, 'None', HtmlFormatterUtil.&toUl) - mean.helmValuesFiles = htmlOrDefault(mean.helmValuesFiles, 'None', HtmlFormatterUtil.&toUl) - mean.helmDefaultFlags = htmlOrDefault(mean.helmDefaultFlags, 'None') { HtmlFormatterUtil.toSpan(it as List, "inner-span", " ") } - mean.helmAdditionalFlags = htmlOrDefault(mean.helmAdditionalFlags, 'None') { HtmlFormatterUtil.toSpan(it as List, "inner-span", " ") } - mean.helmEnvBasedValuesFiles = htmlOrDefault(mean.helmEnvBasedValuesFiles, 'None', HtmlFormatterUtil.&toUl) - - status.deployStatus = (status.status == "deployed") - ? "Successfully deployed" - : status.status - - status?.resources = htmlOrDefault(status?.resourcesByKind, 'None', HtmlFormatterUtil.&toUl) + mean.helmDefaultFlags = mean.helmDefaultFlags.join(" ") ?: 'None' + mean.helmAdditionalFlags = mean.helmAdditionalFlags.join(" ") ?: 'None' + mean.helmValues = HtmlFormatterUtil.toUl(mean.helmValues as List, 'None') + mean.helmValuesFiles = HtmlFormatterUtil.toUl(mean.helmValuesFiles as List, 'None') + mean.helmEnvBasedValuesFiles = HtmlFormatterUtil.toUl(mean.helmEnvBasedValuesFiles as List, 'None') + + status.deployStatus = (status.status == "deployed") ? "Successfully deployed" : status.status + status?.resources = HtmlFormatterUtil.toUl(status.resourcesByKind as Map, 'None') // These fields are not needed anymore due to their values // being moved to their final place diff --git a/src/org/ods/orchestration/util/HtmlFormatterUtil.groovy b/src/org/ods/orchestration/util/HtmlFormatterUtil.groovy index e1cf0e83f..36e8a1d8c 100644 --- a/src/org/ods/orchestration/util/HtmlFormatterUtil.groovy +++ b/src/org/ods/orchestration/util/HtmlFormatterUtil.groovy @@ -2,63 +2,28 @@ package org.ods.orchestration.util class HtmlFormatterUtil { - static toUl(Map map, String selector = "inner-ul") { - return objectToUl(map, selector) - } - - static toUl(List list, String selector = "inner-ul") { - return objectToUl(list, selector) - } - - static toSpan(Map map, String selector = "inner-span", String sep = ", ") { - return objectToSpan(map, selector, sep) - } + static String toUl(Map map, String emptyDefault, String cssClass = 'inner-ul') { + return itemsUl(map, emptyDefault, cssClass) { + def value = it.value in List + ? toUl(it.value as List, emptyDefault, cssClass) + : it.value ?: emptyDefault - static toSpan(List list, String selector = "inner-span", String sep = ", ") { - return objectToSpan(list, selector, sep) - } - - private static objectToUl(Object obj, String selector) { - if (obj == null) { - return "" + return "
  • ${it.key}: ${value}
  • " } - - def resolve = resolver({ HtmlFormatterUtil.toUl(it, selector) }) - - def li = { it -> - it in Map.Entry - ? "
  • ${it.key}: ${resolve(it.value)}
  • " - : "
  • ${resolve(it)}
  • " - } - - def lis = obj.collect { li(it) }.join() - - return "
      ${lis}
    " } - private static objectToSpan(Object obj, String selector, String sep) { - if (obj == null) { - return "" - } + static String toUl(List list, String emptyDefault, String cssClass = 'inner-ul') { + return itemsUl(list, emptyDefault, cssClass) { "
  • ${it}
  • " } + } - def resolve = resolver({ HtmlFormatterUtil.toSpan(it, selector, sep) }) + private static String itemsUl(items, String emptyDefault, String cssClass, Closure formatItem) { - def spanContent = { it -> - it in Map.Entry - ? "${it.key}: ${resolve(it.value)}" - : "${resolve(it)}" + if (!items) { + return emptyDefault } - def spanBody = obj.collect { spanContent(it) }.join(sep) - - return "${spanBody}" - } + String body = items.collect { formatItem(it) }.join() - private static Closure resolver(Closure collectionValue) { - { value -> - value in Collection - ? collectionValue(value) - : value - } + return "
      ${body}
    " } } diff --git a/src/org/ods/services/OpenShiftService.groovy b/src/org/ods/services/OpenShiftService.groovy index f139a8d0d..17f0af1d6 100644 --- a/src/org/ods/services/OpenShiftService.groovy +++ b/src/org/ods/services/OpenShiftService.groovy @@ -5,7 +5,7 @@ import groovy.json.JsonOutput import groovy.json.JsonSlurperClassic import groovy.transform.TypeChecked import groovy.transform.TypeCheckingMode -import org.ods.util.HelmStatusSimpleData +import org.ods.util.HelmStatus import org.ods.util.ILogger import org.ods.util.IPipelineSteps import org.ods.util.PodData @@ -156,7 +156,7 @@ class OpenShiftService { } } - HelmStatusSimpleData helmStatus( + HelmStatus helmStatus( String project, String release ) { @@ -167,7 +167,7 @@ class OpenShiftService { returnStdout: true ).toString().trim() def helmStatusMap = new JsonSlurperClassic().parseText(helmStdout) - return HelmStatusSimpleData.fromJsonObject(helmStatusMap) + return HelmStatus.fromJsonObject(helmStatusMap) } catch (ex) { throw new RuntimeException("Helm status Failed (${ex.message})!" + "Helm could not gather status of ${release} in ${project}") @@ -448,11 +448,6 @@ class OpenShiftService { ) } - boolean isDeploymentKind(String kind) { - boolean b = kind in [DEPLOYMENTCONFIG_KIND, DEPLOYMENT_KIND] - b - } - // Returns data about the pods (replicas) of the deployment. // If not all pods are running until the retries are exhausted, // an exception is thrown. diff --git a/src/org/ods/util/HelmStatusSimpleData.groovy b/src/org/ods/util/HelmStatus.groovy similarity index 90% rename from src/org/ods/util/HelmStatusSimpleData.groovy rename to src/org/ods/util/HelmStatus.groovy index 21a5d8ffb..9b4f8c356 100644 --- a/src/org/ods/util/HelmStatusSimpleData.groovy +++ b/src/org/ods/util/HelmStatus.groovy @@ -1,10 +1,8 @@ package org.ods.util import com.cloudbees.groovy.cps.NonCPS -import groovy.transform.TypeChecked -@TypeChecked -class HelmStatusSimpleData { +class HelmStatus { String name String version @@ -17,9 +15,8 @@ class HelmStatusSimpleData { */ Map > resourcesByKind - @SuppressWarnings(['Instanceof']) @NonCPS - static HelmStatusSimpleData fromJsonObject(Object object) { + static HelmStatus fromJsonObject(Object object) { try { def rootObject = ensureMap(object, "") @@ -39,7 +36,7 @@ class HelmStatusSimpleData { def att = "version" if (!rootObject.containsKey(att)) { missingKeys << att - } else if (!(rootObject[att] instanceof Integer)) { + } else if (!(rootObject[att] in Integer)) { badTypes << "${att}: expected Integer, found ${rootObject[att].getClass()}" } handleMissingKeysOrBadTypes(missingKeys, badTypes) @@ -59,7 +56,7 @@ class HelmStatusSimpleData { } } } - def hs = new HelmStatusSimpleData() + def hs = new HelmStatus() hs.with { name = rootObject.name version = rootObject.version as String @@ -79,8 +76,11 @@ class HelmStatusSimpleData { } @NonCPS - Map> getResourcesByKind(List kinds) { - return resourcesByKind.subMap(kinds) + Map> getResources() { + // Return a cloned copy of the map + return resourcesByKind.collectEntries { kind, names -> + [ kind, names.collect() ] + } } @NonCPS @@ -97,6 +97,7 @@ class HelmStatusSimpleData { return result } + @NonCPS String toString() { return toMap().toMapString() @@ -124,13 +125,12 @@ class HelmStatusSimpleData { return new Tuple2(resource.kind, resource.name) } - @SuppressWarnings(['Instanceof']) @NonCPS private static Map ensureMap(Object obj, String context) { if (obj == null) { return [:] } - if (!(obj instanceof Map)) { + if (!(obj in Map)) { def msg = context ? "${context}: expected JSON object, found ${obj.getClass()}" : "Expected JSON object, found ${obj.getClass()}" @@ -140,20 +140,19 @@ class HelmStatusSimpleData { return obj as Map } - @SuppressWarnings(['Instanceof']) @NonCPS private static List ensureList(Object obj, String context) { if (obj == null) { return [] } - if (!(obj instanceof List)) { + if (!(obj in List)) { throw new IllegalArgumentException( "${context}: expected JSON array, found ${obj.getClass()}") } return obj as List } - @SuppressWarnings(['Instanceof']) + @NonCPS private static Tuple2, List> collectMissingStringAttributes( Map jsonObject, List stringAttributes) { @@ -162,7 +161,7 @@ class HelmStatusSimpleData { for (att in stringAttributes) { if (! (jsonObject.containsKey(att) && jsonObject[att])) { missingOrEmptyKeys << att - } else if (!(jsonObject[att] instanceof String)) { + } else if (!(jsonObject[att] in String)) { badTypes << "${att}: expected String, found ${jsonObject[att].getClass()}" } } diff --git a/src/org/ods/util/ILogger.groovy b/src/org/ods/util/ILogger.groovy index fec0800fe..e0622bf25 100644 --- a/src/org/ods/util/ILogger.groovy +++ b/src/org/ods/util/ILogger.groovy @@ -7,9 +7,15 @@ interface ILogger { String info(String message) String infoClocked(String component, String message) + String jsonInfo(Object jsonObject, String message, boolean pretty) + String jsonInfoClocked(String component, Object jsonObject, String message, boolean pretty) + String debug(String message) String debugClocked(String component, String message) + String jsonDebug(Object jsonObject, String message, boolean pretty) + String jsonDebugClocked(String component, Object jsonObject, String message, boolean pretty) + String warn(String message) String warnClocked(String component, String message) diff --git a/src/org/ods/util/Logger.groovy b/src/org/ods/util/Logger.groovy index 16c21df0c..dc1f97284 100644 --- a/src/org/ods/util/Logger.groovy +++ b/src/org/ods/util/Logger.groovy @@ -1,5 +1,8 @@ package org.ods.util + +import groovy.json.JsonOutput + class Logger implements ILogger, Serializable { private final Object script @@ -20,6 +23,10 @@ class Logger implements ILogger, Serializable { } } + String jsonDebug(def jsonObject, String message = null, boolean pretty = true) { + debug(jsonMessage(jsonObject, message, pretty)) + } + String logWithThrow(String message) { this.script.echo("About to throw: ${message}") this.script.currentBuild.result = 'FAILURE' @@ -31,6 +38,10 @@ class Logger implements ILogger, Serializable { message } + String jsonInfo(Object jsonObject, String message = null, boolean pretty = true) { + info(jsonMessage(jsonObject, message, pretty)) + } + String warn(String message) { message = "WARN: ${message}" info(message) @@ -40,10 +51,18 @@ class Logger implements ILogger, Serializable { debug(timedCall(component, message)) } + String jsonDebugClocked(String component, Object jsonObject, String message = null, boolean pretty = true) { + debug(timedCall(component, jsonMessage(jsonObject, message, pretty))) + } + String infoClocked(String component, String message = null) { info(timedCall(component, message)) } + String jsonInfoClocked(String component, Object jsonObject, String message = null, boolean pretty = true) { + info(timedCall(component, jsonMessage(jsonObject, message, pretty))) + } + String warnClocked(String component, String message = null) { warn(timedCall(component, message)) } @@ -64,6 +83,21 @@ class Logger implements ILogger, Serializable { timedCall(component) } + private def toJson(Object jsonObject, boolean pretty = true) { + def json = JsonOutput.toJson(jsonObject) + json = pretty ? JsonOutput.prettyPrint(json) : json + return json + } + + private def jsonMessage(Object jsonObject, String message, boolean pretty) { + def json = toJson(jsonObject, pretty) + def prefix = message ? "${message}, json" : 'json' + + def msg = "${prefix}: ${json}" + + return msg + } + @SuppressWarnings(['GStringAsMapKey', 'UnnecessaryElseStatement']) private def timedCall(String component, String message = null) { if (!component) { diff --git a/test/groovy/org/ods/component/HelmDeploymentStrategySpec.groovy b/test/groovy/org/ods/component/HelmDeploymentStrategySpec.groovy index 7e1480401..9b3075db6 100644 --- a/test/groovy/org/ods/component/HelmDeploymentStrategySpec.groovy +++ b/test/groovy/org/ods/component/HelmDeploymentStrategySpec.groovy @@ -4,7 +4,7 @@ import groovy.json.JsonSlurperClassic import org.ods.services.JenkinsService import org.ods.services.OpenShiftService import org.ods.services.ServiceRegistry -import org.ods.util.HelmStatusSimpleData +import org.ods.util.HelmStatus import org.ods.util.Logger import org.ods.util.PodData import spock.lang.Shared @@ -86,7 +86,7 @@ class HelmDeploymentStrategySpec extends PipelineSpockTestBase { def config = [:] def helmStatusFile = new FixtureHelper().getResource("helmstatus.json") - def helmStatus = HelmStatusSimpleData.fromJsonObject(new JsonSlurperClassic().parseText(helmStatusFile.text)) + def helmStatus = HelmStatus.fromJsonObject(new JsonSlurperClassic().parseText(helmStatusFile.text)) def ctxData = contextData + [environment: 'test', targetProject: 'guardians-test', openshiftRolloutTimeoutRetries: 5, chartDir: 'chart'] IContext context = new Context(null, ctxData, logger) @@ -101,7 +101,7 @@ class HelmDeploymentStrategySpec extends PipelineSpockTestBase { HelmDeploymentStrategy strategy = Spy(HelmDeploymentStrategy, constructorArgs: [null, context, config, openShiftService, jenkinsService, logger]) when: - strategy.getRolloutData(helmStatus as HelmStatusSimpleData) + strategy.getRolloutData(helmStatus as HelmStatus) def actualDeploymentMeans = context.getBuildArtifactURIs() diff --git a/test/groovy/org/ods/core/test/LoggerStub.groovy b/test/groovy/org/ods/core/test/LoggerStub.groovy index c339eccea..3f63c521d 100644 --- a/test/groovy/org/ods/core/test/LoggerStub.groovy +++ b/test/groovy/org/ods/core/test/LoggerStub.groovy @@ -1,5 +1,6 @@ package org.ods.core.test +import groovy.json.JsonOutput import org.ods.util.ILogger class LoggerStub implements ILogger, Serializable { @@ -17,10 +18,18 @@ class LoggerStub implements ILogger, Serializable { logger.debug message } + String jsonDebug(Object jsonObject, String message = null, boolean pretty = true) { + debug(jsonMessage(jsonObject, message, pretty)) + } + String info(String message) { logger.info message } + String jsonInfo(Object jsonObject, String message = null, boolean pretty = true) { + info(jsonMessage(jsonObject, message, pretty)) + } + String warn(String message) { info ("WARN: ${message}") } @@ -29,10 +38,18 @@ class LoggerStub implements ILogger, Serializable { debug(timedCall(component, message)) } + String jsonDebugClocked(String component, Object jsonObject, String message = null, boolean pretty = true) { + debug(timedCall(component, jsonMessage(jsonObject, message, pretty))) + } + String infoClocked(String component, String message = null) { info(timedCall(component, message)) } + String jsonInfoClocked(String component, Object jsonObject, String message = null, boolean pretty = true) { + info(timedCall(component, jsonMessage(jsonObject, message, pretty))) + } + String warnClocked(String component, String message = null) { warn(timedCall(component, message)) } @@ -62,6 +79,21 @@ class LoggerStub implements ILogger, Serializable { timedCall (component) } + private def toJson(Object jsonObject, boolean pretty = true) { + def json = JsonOutput.toJson(jsonObject) + json = pretty ? JsonOutput.prettyPrint(json) : json + return json + } + + private def jsonMessage(Object jsonObject, String message, boolean pretty) { + def json = toJson(jsonObject, pretty) + def prefix = message ? "${message}, json" : 'json' + + def msg = "${prefix}: ${json}" + + return msg + } + @SuppressWarnings(['GStringAsMapKey', 'UnnecessaryElseStatement']) private def timedCall (String component, String message = null) { if (!component) { diff --git a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy index 78c3d675f..8e8170bcf 100644 --- a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy +++ b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy @@ -23,6 +23,7 @@ import org.ods.util.IPipelineSteps import org.ods.util.Logger import spock.lang.Unroll import util.FixtureHelper +import util.OpenShiftHelper import util.SpecHelper import java.nio.file.Files @@ -1303,7 +1304,7 @@ class LeVADocumentUseCaseSpec extends SpecHelper { def "assemble deploymentInfo for TIR with helm"() { given: def file = new FixtureHelper().getResource("deployments-data-helm.json") - os.isDeploymentKind(*_) >> true + OpenShiftHelper.isDeploymentKind(*_) >> true when: def deploymentsData = new JsonSlurperClassic().parseText(file.text) @@ -1365,7 +1366,7 @@ class LeVADocumentUseCaseSpec extends SpecHelper { def "assemble deploymentInfo for TIR with tailor"() { given: def file = new FixtureHelper().getResource("deployments-data-tailor.json") - os.isDeploymentKind(*_) >> true + OpenShiftHelper.isDeploymentKind(*_) >> true when: def deploymentsData = new JsonSlurperClassic().parseText(file.text) diff --git a/test/groovy/org/ods/orchestration/util/HtmlFormatterUtilSpec.groovy b/test/groovy/org/ods/orchestration/util/HtmlFormatterUtilSpec.groovy new file mode 100644 index 000000000..0bc8b97ec --- /dev/null +++ b/test/groovy/org/ods/orchestration/util/HtmlFormatterUtilSpec.groovy @@ -0,0 +1,46 @@ +package org.ods.orchestration.util + +import spock.lang.Specification + +class HtmlFormatterUtilSpec extends Specification { + + def "Maps and Lists converted to HTML
      or to 'EMPTY_DEFAULT' when empty"() { + given: 'String with nonbreakable white space' + + def emptyList = [] + def list = ['v1', 'v2'] + + def emptyMap = [:] + def map = [ + 'k1': '', + 'k2': [], + 'k3': 'v3', + 'k4': ['v4.1', 'v4.2'] + ] + + when: 'We convert an empty list to HTML
        ' + def emptyListHtml = HtmlFormatterUtil.toUl(emptyList as List, 'EMPTY_DEFAULT') + + then: 'We get the String "EMPTY_DEFAULT" as a result for the empty list' + emptyListHtml == 'EMPTY_DEFAULT' + + when: 'We convert an empty map to HTML
          ' + def emptyMapHtml = HtmlFormatterUtil.toUl(emptyMap as Map, 'EMPTY_DEFAULT') + + then: 'We get the String "EMPTY_DEFAULT" as a result for the empty map' + emptyMapHtml == 'EMPTY_DEFAULT' + + when: "We convert a list to HTML
            with class 'some-ul-class'" + def listHtml = HtmlFormatterUtil.toUl(list as List, 'EMPTY_DEFAULT', 'some-ul-class') + + then: "We get the HTML String for a HTML
              with the list items as
            • sub-elements" + listHtml == "
              • v1
              • v2
              " + + when: "We convert a map to HTML
                with class 'some-ul-class'" + def mapHtml = HtmlFormatterUtil.toUl(map as Map, 'EMPTY_DEFAULT', 'some-ul-class') + + then: "We get the HTML String for a HTML
                  with the map items as
                • sub-elements" + mapHtml == "
                  • k1: EMPTY_DEFAULT
                  • k2: EMPTY_DEFAULT
                  • k3: v3
                  • k4:
                    • v4.1
                    • v4.2
                  " + + } +} diff --git a/test/groovy/util/HelmStatusSpec.groovy b/test/groovy/util/HelmStatusSpec.groovy index f1a893173..f4edeffaa 100644 --- a/test/groovy/util/HelmStatusSpec.groovy +++ b/test/groovy/util/HelmStatusSpec.groovy @@ -12,11 +12,12 @@ class HelmStatusSpec extends SpecHelper { when: def jsonObject = new JsonSlurperClassic().parseText(file.text) - def helmStatus = HelmStatusSimpleData.fromJsonObject(jsonObject) + def helmStatus = HelmStatus.fromJsonObject(jsonObject) def simpleStatusMap = helmStatus.toMap() def simpleStatusNoResources = simpleStatusMap.findAll { k,v -> k != "resourcesByKind"} - def deploymentResources = helmStatus.getResourcesByKind([ - OpenShiftService.DEPLOYMENT_KIND, OpenShiftService.DEPLOYMENTCONFIG_KIND,]) + def helmStatusResources = helmStatus.getResources() + def deploymentResources = helmStatusResources.subMap([ + OpenShiftService.DEPLOYMENT_KIND, OpenShiftService.DEPLOYMENTCONFIG_KIND]) then: simpleStatusNoResources == [ name: 'standalone-app', @@ -38,4 +39,5 @@ class HelmStatusSpec extends SpecHelper { ] } + // TODO add tests for fromJsonData() } diff --git a/test/groovy/util/OpenShiftHelper.groovy b/test/groovy/util/OpenShiftHelper.groovy index 748acb3db..a468835bf 100644 --- a/test/groovy/util/OpenShiftHelper.groovy +++ b/test/groovy/util/OpenShiftHelper.groovy @@ -2,7 +2,15 @@ package util import groovy.json.JsonSlurper +import static org.ods.services.OpenShiftService.DEPLOYMENTCONFIG_KIND +import static org.ods.services.OpenShiftService.DEPLOYMENT_KIND + class OpenShiftHelper { + + static isDeploymentKind(String kind) { + return kind in [DEPLOYMENTCONFIG_KIND, DEPLOYMENT_KIND] + } + static def validateResourceParams(script, project, resources) { if (project) { assert script =~ /\s-n\s+\Q${project}\E(?:\s|$)/ diff --git a/test/groovy/vars/OdsComponentStageRolloutOpenShiftDeploymentSpec.groovy b/test/groovy/vars/OdsComponentStageRolloutOpenShiftDeploymentSpec.groovy index 69bbecdaa..c974a6b51 100644 --- a/test/groovy/vars/OdsComponentStageRolloutOpenShiftDeploymentSpec.groovy +++ b/test/groovy/vars/OdsComponentStageRolloutOpenShiftDeploymentSpec.groovy @@ -9,7 +9,7 @@ import org.ods.services.OpenShiftService import org.ods.services.JenkinsService import org.ods.services.ServiceRegistry -import org.ods.util.HelmStatusSimpleData +import org.ods.util.HelmStatus import org.ods.util.Logger import org.ods.util.PodData import util.FixtureHelper @@ -165,7 +165,7 @@ class OdsComponentStageRolloutOpenShiftDeploymentSpec extends PipelineSpockTestB IContext context = new Context(null, c, logger) OpenShiftService openShiftService = Mock(OpenShiftService.class) - openShiftService.helmStatus('guardians-test', 'standalone-app') >> HelmStatusSimpleData.fromJsonObject(new JsonSlurperClassic().parseText(helmJsonText)) + openShiftService.helmStatus('guardians-test', 'standalone-app') >> HelmStatus.fromJsonObject(new JsonSlurperClassic().parseText(helmJsonText)) // todo: verify that we did not want to ensure that build images are tagged here. // - the org.ods.component.Context.artifactUriStore is not initialized with c when created above! // - as a consequence the build artifacts are empty so no retagging happens here. From 9eda979acd7fe307ddce7fcd554e691762abcfdb Mon Sep 17 00:00:00 2001 From: "x2odsantelo@boehringer-ingelheim.com" Date: Fri, 18 Oct 2024 13:16:03 +0200 Subject: [PATCH 27/36] fix: fixed bad merge on CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc1d871cf..3ada850d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## Unreleased +* Fix branch calculation for re-deploy ([#1162](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1162)) * Aqua log readability update and whitelisting mechanism fix ([#1161](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1161)) * Aqua remotely exploitable critical vulnerabilities improvements ([#1157](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1157)) * Fail builds when aqua scan detects remotely exploitable security vulnerabilities with solutions ([#1147](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1147)) @@ -13,6 +14,7 @@ * Enhance SSDS Document Generation Performance using New Atlassian APIs ([#1084](https://github.com/opendevstack/ods-jenkins-shared-library/issues/1084)) * Deprecation of vuln-type and scanners config in Trivy ([#1150](https://github.com/opendevstack/ods-jenkins-shared-library/issues/1150)) * Use riskPriority value as number instead of its value as text in RA ([#1156](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1156)) +* Preserve quickstarter files in case a repository already contain files with same names ([#1165](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1165)) * TIR - remove dynamic pod data, surface helm status and helm report tables reformatting ([#1143](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1143)) ### Fixed From aab54445ddf9e10504449a3972eeb190f5de405c Mon Sep 17 00:00:00 2001 From: "Farre,Juan_Antonio (IT EDS) BI-ES-S" Date: Mon, 21 Oct 2024 15:36:29 +0200 Subject: [PATCH 28/36] Improve the organization of the data for the TIR --- .../usecase/LeVADocumentUseCase.groovy | 107 ++++++------------ .../usecase/LeVADocumentUseCaseSpec.groovy | 4 +- 2 files changed, 39 insertions(+), 72 deletions(-) diff --git a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy index 8cdc90478..db733d86a 100644 --- a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy +++ b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy @@ -1090,13 +1090,13 @@ class LeVADocumentUseCase extends DocGenUseCase { def keysInDoc = ['Technology-' + repo.id] def docHistory = this.getAndStoreDocumentHistory(documentType + '-' + repo.id, keysInDoc) - + def deployments = repo.data.openshift.deployments ?: [:] as Map> def data_ = [ metadata : this.getDocumentMetadata(this.DOCUMENT_TYPE_NAMES[documentType], repo), deployNote : deploynoteData, openShiftData: [ builds : repo.data.openshift.builds ?: '', - deployments: getNonHelmDeployments(repo.data.openshift.deployments ?: [:]), + deployments: prepareDeploymentInfo(deployments), ], testResults: [ installation: installationTestData?.testResults @@ -1106,15 +1106,10 @@ class LeVADocumentUseCase extends DocGenUseCase { sections: sections, documentHistory: docHistory?.getDocGenFormat() ?: [], documentHistoryLatestVersionId: docHistory?.latestVersionId ?: 1, - ] + ], + deploymentMean: prepareDeploymentMeanInfo(deployments) ] - def helmStatusAndMean = getHelmStatusAndMean(repo.data.openshift.deployments ?: [:]) - - if (helmStatusAndMean) { - data_ << [deployment: helmStatusAndMean] - } - logger.jsonDebug(data_, "createTIR - assembled data:") // Code review report - in the special case of NO jira .. @@ -1193,88 +1188,60 @@ class LeVADocumentUseCase extends DocGenUseCase { status?.remove("resourcesByKind") } - /** - * Retrieves first helm release in deployments. - * - * @return An empty map if there is no helm release in deployments. - * Otherwise keys 'status' and 'means' each contain a - * map suited to format the information in a template. + /* + * Retrieves the deployment mean and fills empty values with proper defaults */ - Map> getHelmStatusAndMean(Map> deployments) { - // collect first helm release - def deploymentMeanHelm = deployments.find { - it.key.endsWith('-deploymentMean') && it.value.type == "helm" - } - - def meanHelm = deploymentMeanHelm?.value - - if (!meanHelm) { - return [:] - } - - String releaseName = meanHelm?.helmReleaseName - if (!releaseName) { - logger.warn("No helmReleaseName name in ${meanHelm}: skipping") - return [:] - } - def helmStatus = (meanHelm?.helmStatus ?: [:]) as Map - def meanHelmWithoutStatus = meanHelm.findAll { k, v -> k != 'helmStatus' } - - return [ - status: helmStatus, - mean : meanHelmWithoutStatus - ] + @NonCPS + private static Map prepareDeploymentMeanInfo(Map> deployments) { + def deploymentMean = + deployments.find { it.key.endsWith('-deploymentMean') }.value + return setEmptyValuesDefaults(deploymentMean) } /** - * Retrieves all non-helm deployments. + * Retrieves all deployments. * * The processed map is suited to format the resource information in the TIR. + * This method doesn't return deployment means. * - * @return An empty map if no such deployments exist. - * Otherwise keys indicate the deployment resource or deployment mean if they - * have a suffix of -deploymentMean. + * @return A Map with all the deployments. */ - Map getNonHelmDeployments(Map> deployments) { - // collect non helm deployments - def deploymentsNotHelm = deployments.findAll { - !it.key.endsWith('-deploymentMean') || it.value?.type != "helm" - } + @NonCPS + protected static Map> prepareDeploymentInfo(Map> deployments) { Map> deploymentsForTir = [:] - deploymentsNotHelm.each { String deploymentName, Map deployment -> - if (deploymentName.endsWith('-deploymentMean')) { - deploymentsForTir.put(deploymentName, setEmptyValuesDefaults(deployment)) - } else { - deploymentsForTir.put(deploymentName, deployment.findAll { k, v -> k != 'podName' }) + deployments.each { String deploymentName, Map deployment -> + if (!deploymentName.endsWith('-deploymentMean')) { + def filteredFields = deployment.findAll { k, v -> k != 'podName' } + deploymentsForTir.put(deploymentName, filteredFields) } } return deploymentsForTir } - Map setEmptyValuesDefaults(Map deployment) { + @NonCPS + private static Map setEmptyValuesDefaults(Map deployment) { + def defaultValues = [ + helmAdditionalFlags : 'None', + helmEnvBasedValuesFiles: 'None', + helmValues : 'None', + ] if (deployment?.type == 'tailor') { - def tailorEmptyValues = [ + defaultValues = [ tailorParamFile: 'None', tailorParams : 'None', tailorPreserve : 'No extra resources specified to be preserved' ] - return deployment.collectEntries { k, v -> - def newValue = (tailorEmptyValues.containsKey(k) && !v) ? tailorEmptyValues[k] : v - [(k): newValue] - } } - if (deployment?.type == 'helm') { - def helmEmptyValues = [ - helmAdditionalFlags : 'None', - helmEnvBasedValuesFiles: 'None', - helmValues : 'None', - ] - return deployment.collectEntries { k, v -> - def newValue = (helmEmptyValues.containsKey(k) && !v) ? helmEmptyValues[k] : v - [(k): newValue] + def newDeployment = deployment.collectEntries { k, v -> + if (!v) { + def defaultValue = defaultValues[k] + if (defaultValue) { + return [(k): defaultValue] + } } - } - return deployment + return [(k): v] + } as Map + return newDeployment } String createOverallTIR(Map repo = null, Map data = null) { diff --git a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy index 8e8170bcf..05204c3fc 100644 --- a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy +++ b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy @@ -1309,7 +1309,7 @@ class LeVADocumentUseCaseSpec extends SpecHelper { when: def deploymentsData = new JsonSlurperClassic().parseText(file.text) def helmStatusAndMean = usecase.getHelmStatusAndMean(deploymentsData.deployments) - def nonHelmDeployments = usecase.getNonHelmDeployments(deploymentsData.deployments) + def nonHelmDeployments = usecase.prepareDeploymentInfo(deploymentsData.deployments) then: @@ -1371,7 +1371,7 @@ class LeVADocumentUseCaseSpec extends SpecHelper { when: def deploymentsData = new JsonSlurperClassic().parseText(file.text) def helmStatusAndMean = usecase.getHelmStatusAndMean(deploymentsData.deployments) - def nonHelmDeployments = usecase.getNonHelmDeployments(deploymentsData.deployments) + def nonHelmDeployments = usecase.prepareDeploymentInfo(deploymentsData.deployments) def data = [:] if (helmStatusAndMean) { // must be falsy data << [deployment: helmStatusAndMean] From 9160bf2905e96d6b7da4d2b52540e4f58dca2d63 Mon Sep 17 00:00:00 2001 From: Carlos Antelo <180194797+anteloro-boeh@users.noreply.github.com> Date: Fri, 15 Nov 2024 17:12:11 +0100 Subject: [PATCH 29/36] chore: deployment strategies unit tests, test data anonymization, some refactoring --- .../component/HelmDeploymentStrategy.groovy | 144 +- .../RolloutOpenShiftDeploymentStage.groovy | 7 +- .../component/TailorDeploymentStrategy.groovy | 10 +- .../phases/DeployOdsComponent.groovy | 6 +- .../usecase/LeVADocumentUseCase.groovy | 219 +- src/org/ods/util/HelmStatus.groovy | 107 +- src/org/ods/util/ILogger.groovy | 6 - src/org/ods/util/JsonLogUtil.groovy | 26 - src/org/ods/util/Logger.groovy | 31 - .../HelmDeploymentStrategySpec.groovy | 232 +- .../usecase/LeVADocumentUseCaseSpec.groovy | 276 +- .../ods/services/OpenShiftServiceSpec.groovy | 42 +- test/groovy/util/FixtureHelper.groovy | 1576 ++++++++- test/groovy/util/HelmStatusSpec.groovy | 24 +- ...StageRolloutOpenShiftDeploymentSpec.groovy | 20 +- test/resources/deployments-data-helm.json | 102 - test/resources/deployments-data-tailor.json | 21 - test/resources/helmstatus.json | 2810 +++++++++-------- 18 files changed, 3583 insertions(+), 2076 deletions(-) delete mode 100644 src/org/ods/util/JsonLogUtil.groovy delete mode 100644 test/resources/deployments-data-helm.json delete mode 100644 test/resources/deployments-data-tailor.json diff --git a/src/org/ods/component/HelmDeploymentStrategy.groovy b/src/org/ods/component/HelmDeploymentStrategy.groovy index 60f168ffb..25faa298d 100644 --- a/src/org/ods/component/HelmDeploymentStrategy.groovy +++ b/src/org/ods/component/HelmDeploymentStrategy.groovy @@ -4,34 +4,29 @@ import groovy.transform.TypeChecked import groovy.transform.TypeCheckingMode import org.ods.services.JenkinsService import org.ods.services.OpenShiftService -import org.ods.util.HelmStatus -import org.ods.util.ILogger -import org.ods.util.PipelineSteps -import org.ods.util.PodData +import org.ods.util.* class HelmDeploymentStrategy extends AbstractDeploymentStrategy { // Constructor arguments - private final Script script private final IContext context private final OpenShiftService openShift private final JenkinsService jenkins private final ILogger logger // assigned in constructor - private def steps + private IPipelineSteps steps private final RolloutOpenShiftDeploymentOptions options @SuppressWarnings(['AbcMetric', 'CyclomaticComplexity', 'ParameterCount']) HelmDeploymentStrategy( - def script, + IPipelineSteps steps, IContext context, Map config, OpenShiftService openShift, JenkinsService jenkins, ILogger logger ) { - if (!config.selector) { config.selector = context.selector } @@ -72,10 +67,9 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { if (!config.helmPrivateKeyCredentialsId) { config.helmPrivateKeyCredentialsId = "${context.cdProject}-helm-private-key" } - this.script = script this.context = context this.logger = logger - this.steps = new PipelineSteps(script) + this.steps = steps this.options = new RolloutOpenShiftDeploymentOptions(config) this.openShift = openShift @@ -92,12 +86,12 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { // Tag images which have been built in this pipeline from cd project into target project retagImages(context.targetProject, getBuiltImages()) - logger.info "Rolling out ${context.componentId} with HELM, selector: ${options.selector}" + logger.info("Rolling out ${context.componentId} with HELM, selector: ${options.selector}") helmUpgrade(context.targetProject) HelmStatus helmStatus = openShift.helmStatus(context.targetProject, options.helmReleaseName) if (logger.debugMode) { def helmStatusMap = helmStatus.toMap() - logger.jsonDebug(helmStatusMap, "${this.class.name} -- HELM STATUS") + logger.debug("${this.class.name} -- HELM STATUS: ${helmStatusMap}") } // // FIXME: pauseRollouts is non trivial to determine! @@ -105,7 +99,6 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { // // cases since they don't have triggers. // metadataSvc.updateMetadata(false, deploymentResources) def rolloutData = getRolloutData(helmStatus) - logger.jsonDebug(rolloutData) return rolloutData } @@ -121,7 +114,7 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { options.helmValues['componentId'] = context.componentId // we persist the original ones set from outside - here we just add ours - def mergedHelmValues = [:] + Map mergedHelmValues = [:] mergedHelmValues << options.helmValues // we add the global ones - this allows usage in subcharts @@ -138,18 +131,19 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { // deal with dynamic value files - which are env dependent def mergedHelmValuesFiles = [] - mergedHelmValuesFiles.addAll(options.helmValuesFiles) - options.helmEnvBasedValuesFiles.each { envValueFile -> - mergedHelmValuesFiles << envValueFile.replace('.env', - ".${context.environment}") + options.helmEnvBasedValuesFiles = options.helmEnvBasedValuesFiles.collect { + it.replace('.env.',".${context.environment}.") } + mergedHelmValuesFiles.addAll(options.helmValuesFiles) + mergedHelmValuesFiles.addAll(options.helmEnvBasedValuesFiles) + openShift.helmUpgrade( targetProject, options.helmReleaseName, mergedHelmValuesFiles, - mergedHelmValues as Map, + mergedHelmValues, options.helmDefaultFlags, options.helmAdditionalFlags, options.helmDiff @@ -162,77 +156,59 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { private Map> getRolloutData( HelmStatus helmStatus ) { - // Why do we need podData when helm should take care of the install - // 1. FinalizeOdsComponent#verifyDeploymentsBuiltByODS() will fail at: - // commit: 7aab67dad73298b1388eca3517a1a2ea856a2b8e - // ... - // def message = "DeploymentConfigs (component: '${repo.id}') found that are not ODS managed: " + - // ... - // unless it can associate all pods with a deployment resource. - // 2. DeploymentDescriptor requires to have for each deployment resource - // a deployment mean with postfix -deploymentMean - // 3. Image importing in DeployOdsComponent at - // commit: 97463eebc986f1558c37d5ff5a84ec188f9e92d0 - // requires to have a deployment resource for each image. - // ... - // if (!openShiftDir.startsWith('openshift')) { - // deploymentDescriptor.deployments.each { String deploymentName, Map deployment -> - // ... - // currently is driven by images in PodData#containers. - // - // If possible this should be redesigned so that the shared library does not have to - // concern itself with pods anymore. - def rolloutData = [:] - - def deploymentKinds = helmStatus.resourcesByKind - .findAll { it.key in DEPLOYMENT_KINDS } - .collectEntries { it } + // TODO This is still needed for several reasons, still pending refactoring in order to + // remove the need for pod data + Map> rolloutData = [:] + + Map> deploymentKinds = helmStatus.resourcesByKind + .findAll { kind, _ -> kind in DEPLOYMENT_KINDS } deploymentKinds.each { kind, names -> - names.each { name -> - context.addDeploymentToArtifactURIs("${name}-deploymentMean", - [ - type: 'helm', - selector: options.selector, - chartDir: options.chartDir, - helmReleaseName: options.helmReleaseName, - helmEnvBasedValuesFiles: options.helmEnvBasedValuesFiles, - helmValuesFiles: options.helmValuesFiles, - helmValues: options.helmValues, - helmDefaultFlags: options.helmDefaultFlags, - helmAdditionalFlags: options.helmAdditionalFlags, - helmStatus: helmStatus.toMap(), + names.each { name -> + context.addDeploymentToArtifactURIs("${name}-deploymentMean", + [ + type: 'helm', + selector: options.selector, + namespace: context.targetProject, + chartDir: options.chartDir, + helmReleaseName: options.helmReleaseName, + helmEnvBasedValuesFiles: options.helmEnvBasedValuesFiles, + helmValuesFiles: options.helmValuesFiles, + helmValues: options.helmValues, + helmDefaultFlags: options.helmDefaultFlags, + helmAdditionalFlags: options.helmAdditionalFlags, + helmStatus: helmStatus.toMap(), + ] + ) + def podDataContext = [ + "targetProject=${context.targetProject}", + "selector=${options.selector}", + "name=${name}", ] - ) - def podDataContext = [ - "targetProject=${context.targetProject}", - "selector=${options.selector}", - "name=${name}", - ] - def msgPodsNotFound = "Could not find 'running' pod(s) for '${podDataContext.join(', ')}'" - List podData = null - for (def i = 0; i < options.deployTimeoutRetries; i++) { - podData = openShift.checkForPodData(context.targetProject, options.selector, name) - if (podData) { - break + def msgPodsNotFound = "Could not find 'running' pod(s) for '${podDataContext.join(', ')}'" + List podData = null + for (def i = 0; i < options.deployTimeoutRetries; i++) { + podData = openShift.checkForPodData(context.targetProject, options.selector, name) + if (podData) { + break + } + steps.echo("${msgPodsNotFound} - waiting") + steps.sleep(12) } - steps.echo("${msgPodsNotFound} - waiting") - steps.sleep(12) - } - if (!podData) { - throw new RuntimeException(msgPodsNotFound) + if (!podData) { + throw new RuntimeException(msgPodsNotFound) + } + logger.debug("Helm podData for ${podDataContext.join(', ')}: ${podData}") + + rolloutData["${kind}/${name}"] = podData + // TODO: Once the orchestration pipeline can deal with multiple replicas, + // update this to store multiple pod artifacts. + // TODO: Potential conflict if resourceName is duplicated between + // Deployment and DeploymentConfig resource. + context.addDeploymentToArtifactURIs(name, podData[0]?.toMap()) } - logger.jsonDebug(podData, "Helm podData for ${podDataContext.join(', ')}:") - - rolloutData["${kind}/${name}"] = podData - // TODO: Once the orchestration pipeline can deal with multiple replicas, - // update this to store multiple pod artifacts. - // TODO: Potential conflict if resourceName is duplicated between - // Deployment and DeploymentConfig resource. - context.addDeploymentToArtifactURIs(name, podData[0]?.toMap()) } - } - rolloutData + return rolloutData } } diff --git a/src/org/ods/component/RolloutOpenShiftDeploymentStage.groovy b/src/org/ods/component/RolloutOpenShiftDeploymentStage.groovy index 06e373281..fae546629 100644 --- a/src/org/ods/component/RolloutOpenShiftDeploymentStage.groovy +++ b/src/org/ods/component/RolloutOpenShiftDeploymentStage.groovy @@ -2,9 +2,10 @@ package org.ods.component import groovy.transform.TypeChecked import groovy.transform.TypeCheckingMode -import org.ods.services.OpenShiftService import org.ods.services.JenkinsService +import org.ods.services.OpenShiftService import org.ods.util.ILogger +import org.ods.util.PipelineSteps @SuppressWarnings('ParameterCount') @TypeChecked @@ -123,13 +124,13 @@ class RolloutOpenShiftDeploymentStage extends Stage { // (1) We have an openshiftDir // (2) We do not have an openshiftDir but neither do we have an indication that it is Helm if (isTailorDeployment || (!isHelmDeployment && !isTailorDeployment)) { - deploymentStrategy = new TailorDeploymentStrategy(script, context, config, openShift, jenkins, logger) + deploymentStrategy = new TailorDeploymentStrategy(new PipelineSteps(script), context, config, openShift, jenkins, logger) String resourcePath = 'org/ods/component/RolloutOpenShiftDeploymentStage.deprecate-tailor.GString.txt' def msg =steps.libraryResource(resourcePath) logger.warn(msg) } if (isHelmDeployment) { - deploymentStrategy = new HelmDeploymentStrategy(script, context, config, openShift, jenkins, logger) + deploymentStrategy = new HelmDeploymentStrategy(new PipelineSteps(script), context, config, openShift, jenkins, logger) } logger.info("deploymentStrategy: ${deploymentStrategy} -- ${deploymentStrategy.class.name}") return deploymentStrategy.deploy() diff --git a/src/org/ods/component/TailorDeploymentStrategy.groovy b/src/org/ods/component/TailorDeploymentStrategy.groovy index bb0d18175..600af3dc4 100644 --- a/src/org/ods/component/TailorDeploymentStrategy.groovy +++ b/src/org/ods/component/TailorDeploymentStrategy.groovy @@ -5,25 +5,24 @@ import groovy.transform.TypeCheckingMode import org.ods.services.JenkinsService import org.ods.services.OpenShiftService import org.ods.util.ILogger -import org.ods.util.PipelineSteps +import org.ods.util.IPipelineSteps import org.ods.util.PodData class TailorDeploymentStrategy extends AbstractDeploymentStrategy { // Constructor arguments - private final Script script private final IContext context private final OpenShiftService openShift private final JenkinsService jenkins private final ILogger logger // assigned in constructor - private def steps + private IPipelineSteps steps private final RolloutOpenShiftDeploymentOptions options @SuppressWarnings(['AbcMetric', 'CyclomaticComplexity', 'ParameterCount']) TailorDeploymentStrategy( - def script, + IPipelineSteps steps, IContext context, Map config, OpenShiftService openShift, @@ -67,10 +66,9 @@ class TailorDeploymentStrategy extends AbstractDeploymentStrategy { if (!config.containsKey('tailorParams')) { config.tailorParams = [] } - this.script = script this.context = context this.logger = logger - this.steps = new PipelineSteps(script) + this.steps = steps this.options = new RolloutOpenShiftDeploymentOptions(config) this.openShift = openShift diff --git a/src/org/ods/orchestration/phases/DeployOdsComponent.groovy b/src/org/ods/orchestration/phases/DeployOdsComponent.groovy index a054be7f1..2972861d6 100644 --- a/src/org/ods/orchestration/phases/DeployOdsComponent.groovy +++ b/src/org/ods/orchestration/phases/DeployOdsComponent.groovy @@ -41,7 +41,7 @@ class DeployOdsComponent { DeploymentDescriptor deploymentDescriptor steps.dir(openShiftDir) { deploymentDescriptor = DeploymentDescriptor.readFromFile(steps) - logger.jsonDebug(deploymentDescriptor.deployments, "DeploymentDescriptor '${openShiftDir}': ") + logger.debug("DeploymentDescriptor '${openShiftDir}': ${deploymentDescriptor.deployments}") } if (!repo.data.openshift.deployments) { repo.data.openshift.deployments = [:] @@ -79,7 +79,7 @@ class DeployOdsComponent { if (!podData) { throw new RuntimeException(msgPodsNotFound) } - logger.jsonDebug(podData, "Helm podData for '${podDataContext.join(', ')}': ") + logger.debug("Helm podData for '${podDataContext.join(', ')}': ${podData}") // TODO: Once the orchestration pipeline can deal with multiple replicas, // update this to deal with multiple pods. @@ -239,7 +239,7 @@ class DeployOdsComponent { def helmStatus = os.helmStatus(project.targetProject, deploymentMean.helmReleaseName) def helmStatusMap = helmStatus.toMap() deploymentMean.helmStatus = helmStatusMap - logger.jsonDebug(helmStatusMap, "${this.class.name} -- HELM STATUS") + logger.debug("${this.class.name} -- HELM STATUS: ${helmStatusMap}") } } jenkins.maybeWithPrivateKeyCredentials(secretName) { String pkeyFile -> diff --git a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy index db733d86a..cc0702698 100644 --- a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy +++ b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy @@ -1,5 +1,6 @@ package org.ods.orchestration.usecase + import org.apache.commons.lang.StringUtils import org.ods.orchestration.util.HtmlFormatterUtil @@ -22,6 +23,7 @@ import org.ods.services.NexusService import org.ods.services.OpenShiftService import org.ods.util.ILogger import org.ods.util.IPipelineSteps +import org.ods.orchestration.util.MROPipelineUtil.PipelineConfig import java.time.LocalDateTime @@ -83,12 +85,12 @@ class LeVADocumentUseCase extends DocGenUseCase { ] static Map INTERNAL_TO_EXT_COMPONENT_TYPES = [ - (MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_SAAS_SERVICE as String) : 'SAAS Component', - (MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_TEST as String) : 'Automated tests', - (MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_SERVICE as String) : '3rd Party Service Component', - (MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_CODE as String) : 'ODS Software Component', - (MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_INFRA as String) : 'Infrastructure as Code Component', - (MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_LIB as String) : 'ODS library component' + (PipelineConfig.REPO_TYPE_ODS_SAAS_SERVICE as String) : 'SAAS Component', + (PipelineConfig.REPO_TYPE_ODS_TEST as String) : 'Automated tests', + (PipelineConfig.REPO_TYPE_ODS_SERVICE as String) : '3rd Party Service Component', + (PipelineConfig.REPO_TYPE_ODS_CODE as String) : 'ODS Software Component', + (PipelineConfig.REPO_TYPE_ODS_INFRA as String) : 'Infrastructure as Code Component', + (PipelineConfig.REPO_TYPE_ODS_LIB as String) : 'ODS library component' ] public static String DEVELOPER_PREVIEW_WATERMARK = 'Developer Preview' @@ -680,11 +682,11 @@ class LeVADocumentUseCase extends DocGenUseCase { def testsOfRepoTypeOdsCode = [] def testsOfRepoTypeOdsService = [] testsGroupedByRepoType.each { repoTypes, tests -> - if (repoTypes.contains(MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_CODE)) { + if (repoTypes.contains(PipelineConfig.REPO_TYPE_ODS_CODE)) { testsOfRepoTypeOdsCode.addAll(tests) } - if (repoTypes.contains(MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_SERVICE)) { + if (repoTypes.contains(PipelineConfig.REPO_TYPE_ODS_SERVICE)) { testsOfRepoTypeOdsService.addAll(tests) } } @@ -693,7 +695,7 @@ class LeVADocumentUseCase extends DocGenUseCase { def docHistory = this.getAndStoreDocumentHistory(documentType, keysInDoc) def installedRepos = this.project.repositories.findAll { it -> - MROPipelineUtil.PipelineConfig.INSTALLABLE_REPO_TYPES.contains(it.type) + PipelineConfig.INSTALLABLE_REPO_TYPES.contains(it.type) } def data_ = [ @@ -744,11 +746,11 @@ class LeVADocumentUseCase extends DocGenUseCase { def testsOfRepoTypeOdsService = [] def testsGroupedByRepoType = groupTestsByRepoType(installationTestIssues) testsGroupedByRepoType.each { repoTypes, tests -> - if (repoTypes.contains(MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_CODE)) { + if (repoTypes.contains(PipelineConfig.REPO_TYPE_ODS_CODE)) { testsOfRepoTypeOdsCode.addAll(tests) } - if (repoTypes.contains(MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_SERVICE)) { + if (repoTypes.contains(PipelineConfig.REPO_TYPE_ODS_SERVICE)) { testsOfRepoTypeOdsService.addAll(tests) } } @@ -757,7 +759,7 @@ class LeVADocumentUseCase extends DocGenUseCase { def docHistory = this.getAndStoreDocumentHistory(documentType, keysInDoc) def installedRepos = this.project.repositories.findAll { it -> - MROPipelineUtil.PipelineConfig.INSTALLABLE_REPO_TYPES.contains(it.type) + PipelineConfig.INSTALLABLE_REPO_TYPES.contains(it.type) } def data_ = [ @@ -986,7 +988,7 @@ class LeVADocumentUseCase extends DocGenUseCase { // Get the components that we consider modules in SSDS (the ones you have to code) def modules = componentsMetadata - .findAll { it.odsRepoType.toLowerCase() == MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_CODE.toLowerCase() } + .findAll { it.odsRepoType.toLowerCase() == PipelineConfig.REPO_TYPE_ODS_CODE.toLowerCase() } .collect { component -> // We will set-up a double loop in the template. For moustache limitations we need to have lists component.requirements = component.requirements.findAll { it != null }.collect { r -> @@ -1090,12 +1092,14 @@ class LeVADocumentUseCase extends DocGenUseCase { def keysInDoc = ['Technology-' + repo.id] def docHistory = this.getAndStoreDocumentHistory(documentType + '-' + repo.id, keysInDoc) - def deployments = repo.data.openshift.deployments ?: [:] as Map> - def data_ = [ + Map> deployments = repo.data.openshift.deployments ?: [:] + Map deploymentMean = prepareDeploymentMeanInfo(deployments) + + def documentData = [ metadata : this.getDocumentMetadata(this.DOCUMENT_TYPE_NAMES[documentType], repo), deployNote : deploynoteData, openShiftData: [ - builds : repo.data.openshift.builds ?: '', + builds : formatTIRBuilds(repo.data.openshift.builds), deployments: prepareDeploymentInfo(deployments), ], testResults: [ @@ -1107,95 +1111,38 @@ class LeVADocumentUseCase extends DocGenUseCase { documentHistory: docHistory?.getDocGenFormat() ?: [], documentHistoryLatestVersionId: docHistory?.latestVersionId ?: 1, ], - deploymentMean: prepareDeploymentMeanInfo(deployments) + deploymentMean: deploymentMean, + legacy: deploymentMean?.type == 'tailor' ] - logger.jsonDebug(data_, "createTIR - assembled data:") - // Code review report - in the special case of NO jira .. - def codeReviewReport - if (this.project.isAssembleMode && !this.jiraUseCase.jira && - repo.type?.toLowerCase() == MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_CODE.toLowerCase()) { - def currentRepoAsList = [repo] - codeReviewReport = obtainCodeReviewReport(currentRepoAsList) - } - - def modifier = { document -> - if (codeReviewReport) { - List documents = [document] - documents += codeReviewReport - // Merge the current document with the code review report - return this.pdf.merge(this.steps.env.WORKSPACE, documents) - } - return document - } - - // This will alter the data_ map, due to documentData being a reference to data_, not a deep copy - def documentData = data_ + def isOdsCodeRepo = repo.type?.toLowerCase() == PipelineConfig.REPO_TYPE_ODS_CODE.toLowerCase() - formatDocumentTIRData(documentData) - - return this.createDocument(documentType, repo, documentData, [:], modifier, getDocumentTemplateName(documentType, repo), watermarkText) - } + List codeReviewReport = (this.project.isAssembleMode && !this.jiraUseCase.jira && isOdsCodeRepo) + ? obtainCodeReviewReport([repo]) + : null - private def formatDocumentTIRData(Map data) { - if (data.openShiftData?.builds) { - formatTIRBuilds(data) + def modifier = { byte[] document -> + codeReviewReport + ? this.pdf.merge(this.steps.env.WORKSPACE as String, [document] + codeReviewReport) + : document } - if (data.deployment?.mean?.type == "helm") { - formatTIRHelmDeployment(data) - } - } - - private def formatTIRBuilds(Map data) { - def builds = data.openShiftData.builds as Map - - def capitalizeKey = { entry -> - [(StringUtils.capitalize(entry.key)): entry.value ] - } - - def formattedBuilds = builds.collectEntries { res, resValue -> - def newResValue = resValue.collectEntries { capitalizeKey(it) } - - [(res): newResValue] - } - - data.openShiftData.builds = formattedBuilds - } - - private def formatTIRHelmDeployment(Map data) { - // Move values to its final place and format them accordingly - def mean = data.deployment.mean - def status = data.deployment.status - - mean.namespace = status?.namespace ?: "None" - - mean.selector = mean.selector.replaceAll("=", ": ") - mean.helmDefaultFlags = mean.helmDefaultFlags.join(" ") ?: 'None' - mean.helmAdditionalFlags = mean.helmAdditionalFlags.join(" ") ?: 'None' - mean.helmValues = HtmlFormatterUtil.toUl(mean.helmValues as List, 'None') - mean.helmValuesFiles = HtmlFormatterUtil.toUl(mean.helmValuesFiles as List, 'None') - mean.helmEnvBasedValuesFiles = HtmlFormatterUtil.toUl(mean.helmEnvBasedValuesFiles as List, 'None') - - status.deployStatus = (status.status == "deployed") ? "Successfully deployed" : status.status - status?.resources = HtmlFormatterUtil.toUl(status.resourcesByKind as Map, 'None') - - // These fields are not needed anymore due to their values - // being moved to their final place - status?.remove("namespace") - status?.remove("status") - status?.remove("resourcesByKind") + return this.createDocument(documentType, repo, documentData, [:], modifier, getDocumentTemplateName(documentType, repo), watermarkText) } /* * Retrieves the deployment mean and fills empty values with proper defaults */ - @NonCPS - private static Map prepareDeploymentMeanInfo(Map> deployments) { - def deploymentMean = + protected static Map prepareDeploymentMeanInfo(Map> deployments) { + Map deploymentMean = deployments.find { it.key.endsWith('-deploymentMean') }.value - return setEmptyValuesDefaults(deploymentMean) + + if(deploymentMean.type == 'tailor') { + return formatTIRTailorDeploymentMean(deploymentMean) + } + + return formatTIRHelmDeploymentMean(deploymentMean) } /** @@ -1206,42 +1153,64 @@ class LeVADocumentUseCase extends DocGenUseCase { * * @return A Map with all the deployments. */ - @NonCPS protected static Map> prepareDeploymentInfo(Map> deployments) { - Map> deploymentsForTir = [:] - deployments.each { String deploymentName, Map deployment -> - if (!deploymentName.endsWith('-deploymentMean')) { + return deployments + .findAll { ! it.key.endsWith('-deploymentMean') } + .collectEntries { String deploymentName, Map deployment -> def filteredFields = deployment.findAll { k, v -> k != 'podName' } - deploymentsForTir.put(deploymentName, filteredFields) - } - } - return deploymentsForTir + return [(deploymentName): filteredFields] + } as Map> } - @NonCPS - private static Map setEmptyValuesDefaults(Map deployment) { - def defaultValues = [ - helmAdditionalFlags : 'None', - helmEnvBasedValuesFiles: 'None', - helmValues : 'None', - ] - if (deployment?.type == 'tailor') { - defaultValues = [ - tailorParamFile: 'None', - tailorParams : 'None', - tailorPreserve : 'No extra resources specified to be preserved' - ] + private static Map> formatTIRBuilds(Map> builds) { + + if (!builds) { + return [:] as Map> } - def newDeployment = deployment.collectEntries { k, v -> - if (!v) { - def defaultValue = defaultValues[k] - if (defaultValue) { - return [(k): defaultValue] - } - } - return [(k): v] + + return builds.collectEntries { String buildKey, Map build -> + Map formattedBuild = build + .collectEntries { String key, Object value -> [(StringUtils.capitalize(key)): value] } + return [(buildKey): formattedBuild] + } as Map> + } + + protected static Map formatTIRHelmDeploymentMean(Map mean) { + Map formattedMean = [:] + + formattedMean.namespace = mean.namespace ?: 'None' + formattedMean.type = mean.type + formattedMean.descriptorPath = mean.chartDir ?: '.' + formattedMean.defaultCmdLineArgs = mean.helmDefaultFlags.join(' ') ?: 'None' + formattedMean.additionalCmdLineArgs = mean.helmAdditionalFlags.join(' ') ?: 'None' + formattedMean.configParams = HtmlFormatterUtil.toUl(mean.helmValues as Map, 'None') + formattedMean.configFiles = HtmlFormatterUtil.toUl(mean.helmValuesFiles as List, 'None') + formattedMean.envConfigFiles = HtmlFormatterUtil.toUl(mean.helmEnvBasedValuesFiles as List, 'None') + + Map formattedStatus = [:] + + formattedStatus.deployStatus = (mean.helmStatus.status == "deployed") + ? "Successfully deployed" + : mean.helmStatus.status + formattedStatus.resultMessage = mean.helmStatus.description + formattedStatus.lastDeployed = mean.helmStatus.lastDeployed + formattedStatus.resources = HtmlFormatterUtil.toUl(mean.helmStatus.resourcesByKind as Map, 'None') + + formattedMean.deploymentStatus = formattedStatus + + return formattedMean + } + + protected static Map formatTIRTailorDeploymentMean(Map mean) { + Map defaultValues = [ + tailorParamFile: 'None', + tailorParams : 'None', + tailorPreserve : 'No extra resources specified to be preserved' + ].withDefault {'N/A'} + + return mean.collectEntries { k, v -> + [(k): v ?: defaultValues[k]] } as Map - return newDeployment } String createOverallTIR(Map repo = null, Map data = null) { @@ -1343,7 +1312,7 @@ class LeVADocumentUseCase extends DocGenUseCase { def suffix = "" // compute suffix based on repository type if (repo != null) { - if (repo.type.toLowerCase() == MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_INFRA) { + if (repo.type.toLowerCase() == PipelineConfig.REPO_TYPE_ODS_INFRA) { if (documentType == DocumentType.TIR as String) { suffix += "-infra" } @@ -1594,13 +1563,13 @@ class LeVADocumentUseCase extends DocGenUseCase { componentName : component.name, componentId : metadata.id ?: 'N/A - part of this application', componentType : INTERNAL_TO_EXT_COMPONENT_TYPES.get(repo_.type?.toLowerCase()), - doInstall : MROPipelineUtil.PipelineConfig.INSTALLABLE_REPO_TYPES.contains(repo_.type), + doInstall : PipelineConfig.INSTALLABLE_REPO_TYPES.contains(repo_.type), odsRepoType : repo_.type?.toLowerCase(), description : metadata.description, nameOfSoftware : normComponentName ?: metadata.name, references : metadata.references ?: 'N/A', supplier : metadata.supplier, - version : (repo_.type?.toLowerCase() == MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_CODE) ? + version : (repo_.type?.toLowerCase() == PipelineConfig.REPO_TYPE_ODS_CODE) ? this.project.buildParams.version : metadata.version, requirements : component.getResolvedSystemRequirements(), diff --git a/src/org/ods/util/HelmStatus.groovy b/src/org/ods/util/HelmStatus.groovy index 9b4f8c356..3975b89e3 100644 --- a/src/org/ods/util/HelmStatus.groovy +++ b/src/org/ods/util/HelmStatus.groovy @@ -4,22 +4,21 @@ import com.cloudbees.groovy.cps.NonCPS class HelmStatus { - String name - String version - String namespace - String status - String description - String lastDeployed + private String name + private String version + private String namespace + private String status + private String description + private String lastDeployed /** * Resources names by kind */ - Map > resourcesByKind + private Map > resourcesByKind @NonCPS - static HelmStatus fromJsonObject(Object object) { + static HelmStatus fromJsonObject(Object object) { try { def rootObject = ensureMap(object, "") - def infoObject = ensureMap(rootObject.info, "info") // validation of missing keys or types @@ -57,14 +56,14 @@ class HelmStatus { } } def hs = new HelmStatus() - hs.with { - name = rootObject.name - version = rootObject.version as String - namespace = rootObject.namespace - status = infoObject.status - description = infoObject.description - lastDeployed = infoObject["last_deployed"] - } + + hs.name = rootObject.name + hs.version = rootObject.version as String + hs.namespace = rootObject.namespace + hs.status = infoObject.status + hs.description = infoObject.description + hs.lastDeployed = infoObject["last_deployed"] + hs.resourcesByKind = resourcesByKind.collectEntries { kind, names -> [ kind, names.collect() ] } as Map > @@ -83,6 +82,76 @@ class HelmStatus { } } + @NonCPS + String getName() { + return name + } + + @NonCPS + void setName(String name) { + this.name = name + } + + @NonCPS + String getVersion() { + return version + } + + @NonCPS + void setVersion(String version) { + this.version = version + } + + @NonCPS + String getNamespace() { + return namespace + } + + @NonCPS + void setNamespace(String namespace) { + this.namespace = namespace + } + + @NonCPS + String getStatus() { + return status + } + + @NonCPS + void setStatus(String status) { + this.status = status + } + + @NonCPS + String getDescription() { + return description + } + + @NonCPS + void setDescription(String description) { + this.description = description + } + + @NonCPS + String getLastDeployed() { + return lastDeployed + } + + @NonCPS + void setLastDeployed(String lastDeployed) { + this.lastDeployed = lastDeployed + } + + @NonCPS + Map> getResourcesByKind() { + return resourcesByKind + } + + @NonCPS + void setResourcesByKind(Map> resourcesByKind) { + this.resourcesByKind = resourcesByKind + } + @NonCPS Map toMap() { def result = [ @@ -98,12 +167,12 @@ class HelmStatus { } - @NonCPS + @NonCPS String toString() { return toMap().toMapString() } - @NonCPS + @NonCPS private static Tuple2 extractResource( resourceJsonObject, String context) { def resourceObject = ensureMap(resourceJsonObject, context) diff --git a/src/org/ods/util/ILogger.groovy b/src/org/ods/util/ILogger.groovy index e0622bf25..fec0800fe 100644 --- a/src/org/ods/util/ILogger.groovy +++ b/src/org/ods/util/ILogger.groovy @@ -7,15 +7,9 @@ interface ILogger { String info(String message) String infoClocked(String component, String message) - String jsonInfo(Object jsonObject, String message, boolean pretty) - String jsonInfoClocked(String component, Object jsonObject, String message, boolean pretty) - String debug(String message) String debugClocked(String component, String message) - String jsonDebug(Object jsonObject, String message, boolean pretty) - String jsonDebugClocked(String component, Object jsonObject, String message, boolean pretty) - String warn(String message) String warnClocked(String component, String message) diff --git a/src/org/ods/util/JsonLogUtil.groovy b/src/org/ods/util/JsonLogUtil.groovy deleted file mode 100644 index 2b510cb21..000000000 --- a/src/org/ods/util/JsonLogUtil.groovy +++ /dev/null @@ -1,26 +0,0 @@ -package org.ods.util - -import com.cloudbees.groovy.cps.NonCPS -import groovy.json.JsonOutput -import groovy.transform.TypeChecked - -@TypeChecked -class JsonLogUtil { - - static String debug(ILogger logger, String msg, Object jsonObject) { - if (logger.debugMode) { - if (msg) { - logger.debug(msg) - } - if (jsonObject) { - logger.debug(jsonToString(jsonObject)) - } - } - } - - @NonCPS - static String jsonToString(Object jsonObject) { - return JsonOutput.prettyPrint(JsonOutput.toJson(jsonObject)) - } - -} diff --git a/src/org/ods/util/Logger.groovy b/src/org/ods/util/Logger.groovy index dc1f97284..25ac14d21 100644 --- a/src/org/ods/util/Logger.groovy +++ b/src/org/ods/util/Logger.groovy @@ -23,10 +23,6 @@ class Logger implements ILogger, Serializable { } } - String jsonDebug(def jsonObject, String message = null, boolean pretty = true) { - debug(jsonMessage(jsonObject, message, pretty)) - } - String logWithThrow(String message) { this.script.echo("About to throw: ${message}") this.script.currentBuild.result = 'FAILURE' @@ -38,10 +34,6 @@ class Logger implements ILogger, Serializable { message } - String jsonInfo(Object jsonObject, String message = null, boolean pretty = true) { - info(jsonMessage(jsonObject, message, pretty)) - } - String warn(String message) { message = "WARN: ${message}" info(message) @@ -51,18 +43,10 @@ class Logger implements ILogger, Serializable { debug(timedCall(component, message)) } - String jsonDebugClocked(String component, Object jsonObject, String message = null, boolean pretty = true) { - debug(timedCall(component, jsonMessage(jsonObject, message, pretty))) - } - String infoClocked(String component, String message = null) { info(timedCall(component, message)) } - String jsonInfoClocked(String component, Object jsonObject, String message = null, boolean pretty = true) { - info(timedCall(component, jsonMessage(jsonObject, message, pretty))) - } - String warnClocked(String component, String message = null) { warn(timedCall(component, message)) } @@ -83,21 +67,6 @@ class Logger implements ILogger, Serializable { timedCall(component) } - private def toJson(Object jsonObject, boolean pretty = true) { - def json = JsonOutput.toJson(jsonObject) - json = pretty ? JsonOutput.prettyPrint(json) : json - return json - } - - private def jsonMessage(Object jsonObject, String message, boolean pretty) { - def json = toJson(jsonObject, pretty) - def prefix = message ? "${message}, json" : 'json' - - def msg = "${prefix}: ${json}" - - return msg - } - @SuppressWarnings(['GStringAsMapKey', 'UnnecessaryElseStatement']) private def timedCall(String component, String message = null) { if (!component) { diff --git a/test/groovy/org/ods/component/HelmDeploymentStrategySpec.groovy b/test/groovy/org/ods/component/HelmDeploymentStrategySpec.groovy index 9b3075db6..036133f08 100644 --- a/test/groovy/org/ods/component/HelmDeploymentStrategySpec.groovy +++ b/test/groovy/org/ods/component/HelmDeploymentStrategySpec.groovy @@ -1,114 +1,172 @@ package org.ods.component -import groovy.json.JsonSlurperClassic import org.ods.services.JenkinsService import org.ods.services.OpenShiftService -import org.ods.services.ServiceRegistry import org.ods.util.HelmStatus -import org.ods.util.Logger +import org.ods.util.ILogger +import org.ods.util.IPipelineSteps import org.ods.util.PodData import spock.lang.Shared import util.FixtureHelper import vars.test_helper.PipelineSpockTestBase class HelmDeploymentStrategySpec extends PipelineSpockTestBase { - private Logger logger = Mock(Logger) + private ILogger logger = Stub() + @Shared - def contextData = [ - gitUrl: 'https://example.com/scm/foo/bar.git', - gitCommit: 'cd3e9082d7466942e1de86902bb9e663751dae8e', - gitCommitMessage: 'Foo', - gitCommitAuthor: 'John Doe', - gitCommitTime: '2020-03-23 12:27:08 +0100', - gitBranch: 'master', - buildUrl: 'https://jenkins.example.com/job/foo-cd/job/foo-cd-bar-master/11/console', - buildTime: '2020-03-23 12:27:08 +0100', - odsSharedLibVersion: '2.x', - projectId: 'guardians', - componentId: 'core', - cdProject: 'guardians-cd', - artifactUriStore: [builds: [bar: [:]]] + static def contextData = [ + 'artifactUriStore' : [ + 'builds': [ + 'bar': [:], + ], + ], + 'buildTime' : '2020-03-23 12:27:08 +0100', + 'buildUrl' : 'https://jenkins.example.com/job/foo-cd/job/foo-cd-bar-master/11/console', + 'cdProject' : 'myproject-cd', + 'chartDir' : 'chart', + 'clusterRegistryAddress' : 'image-registry.openshift.svc:1000', + 'componentId' : 'core', + 'environment' : 'test', + 'gitBranch' : 'master', + 'gitCommit' : 'cdefab12345', + 'gitCommitAuthor' : 'John Doe', + 'gitCommitMessage' : 'Foo', + 'gitCommitTime' : '2020-03-23 12:27:08 +0100', + 'gitUrl' : 'https://example.com/scm/foo/bar.git', + 'odsSharedLibVersion' : '2.x', + 'openshiftRolloutTimeoutRetries': 5, + 'projectId' : 'myproject', + 'targetProject' : 'myproject-test', ] - def "rollout: check deploymentMean"() { - given: - - def expectedDeploymentMean = [ - type : "helm", - selector : "app=guardians-core", - chartDir : "chart", - helmReleaseName : "core", - helmEnvBasedValuesFiles: [], - helmValuesFiles : ["values.yaml"], - helmValues : [:], - helmDefaultFlags : ["--install", "--atomic"], - helmAdditionalFlags : [], - helmStatus : [ - name : "standalone-app", - version : "43", - namespace : "guardians-test", - status : "deployed", - description: "Upgrade complete", - lastDeployed : "2024-03-04T15:21:09.34520527Z", - resourcesByKind : [ - ConfigMap: ['core-appconfig-configmap'], - Deployment: ['core', 'standalone-gateway'], - Service: ['core', 'standalone-gateway'], - Cluster: ['edb-cluster'], - Secret: ['core-rsa-key-secret', 'core-security-exandradev-secret', 'core-security-unify-secret'] - ] - ] - ] - def expectedDeploymentMeans = [ - builds : [:], - deployments: [ - "core-deploymentMean" : expectedDeploymentMean, - "core" : [ - podName : null, - podNamespace : null, - podMetaDataCreationTimestamp: null, - deploymentId : "core-124", - podStatus : null, - containers : null - ], - "standalone-gateway-deploymentMean": expectedDeploymentMean, - "standalone-gateway" : [ - podName : null, - podNamespace : null, - podMetaDataCreationTimestamp: null, - deploymentId : "core-124", - podStatus : null, - containers : null, - ], - ] + static Map> expectedRolloutData = [ + 'Deployment/core': [ + new PodData( + [ + 'containers' : [ + 'chart-component-a': "${contextData.clusterRegistryAddress}/myproject-dev/helm-component-a@sha256:12345abcdef", + ], + 'deploymentId' : 'backend-helm-monorepo-chart-component-a-789abcde', + 'podMetaDataCreationTimestamp': '2024-11-11T16:01:04Z', + 'podName' : 'backend-helm-monorepo-chart-component-a-789abcde-asdf', + 'podNamespace' : "${contextData.targetProject}", + 'podStatus' : 'Running', + ]) + ], + 'Deployment/standalone-gateway': [ + new PodData( + [ + 'containers' : [ + 'chart-component-b': "${contextData.clusterRegistryAddress}/myproject-dev/helm-component-b@sha256:98765fedcba", + ], + 'deploymentId' : 'backend-helm-monorepo-chart-component-b-01234abc', + 'podMetaDataCreationTimestamp': '2024-11-11T16:01:04Z', + 'podName' : 'backend-helm-monorepo-chart-component-b-01234abc-qwerty', + 'podNamespace' : "${contextData.targetProject}", + 'podStatus' : 'Running', + ]) ] + ] - def config = [:] + static def config = [ + 'helmAdditionalFlags' : ['--additional-flag-1', '--additional-flag-2'], + 'helmEnvBasedValuesFiles': ['values.env.yaml', 'secrets.env.yaml'], + 'helmValuesFiles' : ['values.yaml', 'secrets.yaml'], + 'selector' : "app.kubernetes.io/instance=${contextData.componentId}", + ] - def helmStatusFile = new FixtureHelper().getResource("helmstatus.json") - def helmStatus = HelmStatus.fromJsonObject(new JsonSlurperClassic().parseText(helmStatusFile.text)) + static def corePodData = new PodData([ + 'containers' : [ + 'chart-component-a': "${contextData.clusterRegistryAddress}/myproject-dev/helm-component-a@sha256:12345abcdef", + ], + 'deploymentId' : 'backend-helm-monorepo-chart-component-a-789abcde', + 'podMetaDataCreationTimestamp': '2024-11-11T16:01:04Z', + 'podName' : 'backend-helm-monorepo-chart-component-a-789abcde-asdf', + 'podNamespace' : "${contextData.targetProject}", + 'podStatus' : 'Running', + ]) - def ctxData = contextData + [environment: 'test', targetProject: 'guardians-test', openshiftRolloutTimeoutRetries: 5, chartDir: 'chart'] - IContext context = new Context(null, ctxData, logger) - OpenShiftService openShiftService = Mock(OpenShiftService.class) - openShiftService.checkForPodData(*_) >> [new PodData([deploymentId: "${contextData.componentId}-124"])] - ServiceRegistry.instance.add(OpenShiftService, openShiftService) + static def standaloneGatewayPodData = new PodData([ + 'containers' : [ + 'chart-component-b': "${contextData.clusterRegistryAddress}/myproject-dev/helm-component-b@sha256:98765fedcba", + ], + 'deploymentId' : 'backend-helm-monorepo-chart-component-b-01234abc', + 'podMetaDataCreationTimestamp': '2024-11-11T16:01:04Z', + 'podName' : 'backend-helm-monorepo-chart-component-b-01234abc-qwerty', + 'podNamespace' : "${contextData.targetProject}", + 'podStatus' : 'Running', + ]) - JenkinsService jenkinsService = Stub(JenkinsService.class) - jenkinsService.maybeWithPrivateKeyCredentials(*_) >> { args -> args[1]('/tmp/file') } - ServiceRegistry.instance.add(JenkinsService, jenkinsService) + def "rollout: check rolloutData"() { + given: + def helmStatus = HelmStatus.fromJsonObject(FixtureHelper.createHelmCmdStatusMap()) - HelmDeploymentStrategy strategy = Spy(HelmDeploymentStrategy, constructorArgs: [null, context, config, openShiftService, jenkinsService, logger]) + OpenShiftService openShift = Mock() + JenkinsService jenkins = Stub() + IPipelineSteps steps = Stub() - when: - strategy.getRolloutData(helmStatus as HelmStatus) - def actualDeploymentMeans = context.getBuildArtifactURIs() + IContext context = Stub { + getTargetProject() >> contextData.targetProject + getEnvironment() >> contextData.environment + getBuildArtifactURIs() >> contextData.artifactUriStore + getComponentId() >> contextData.componentId + getClusterRegistryAddress() >> contextData.clusterRegistryAddress + getCdProject() >> contextData.cdProject + } + // Invoke the closures passed to the methods, given that those are part + // of HelmDeploymentStrategy and should also be tested + steps.dir(contextData.chartDir, _ as Closure) >> { args -> args[1]() } + jenkins.maybeWithPrivateKeyCredentials("${contextData.cdProject}-helm-private-key", _ as Closure) >> { args -> args[1]() } + + Map> rolloutData + HelmDeploymentStrategy strategy = new HelmDeploymentStrategy(steps, context, config, openShift, jenkins, logger) + + when: + rolloutData = strategy.deploy() then: - printCallStack() - assertJobStatusSuccess() + 1 * openShift.helmStatus(contextData.targetProject, contextData.componentId) >> { helmStatus } + + 1 * openShift.helmUpgrade( + contextData.targetProject, + contextData.componentId, + ['values.yaml', 'secrets.yaml', 'values.test.yaml','secrets.test.yaml'], + [ + registry: contextData.clusterRegistryAddress, + componentId: contextData.componentId, + 'global.registry': contextData.clusterRegistryAddress, + 'global.componentId': contextData.componentId, + imageNamespace: contextData.targetProject, + imageTag: '', + 'global.imageNamespace': contextData.targetProject, + 'global.imageTag': '', + ], + ['--install', '--atomic'], + ['--additional-flag-1', '--additional-flag-2'], + true, + ) + + 2 * openShift.checkForPodData(contextData.targetProject, config.selector, _) >> { args -> + switch (args[2]) { + case 'core': + return [corePodData] + case 'standalone-gateway': + return [standaloneGatewayPodData] + default: + return [] + } + } + + assert expectedRolloutData.keySet() == rolloutData.keySet() + + expectedRolloutData.each { key, expectedPodData -> + def actualPodData = rolloutData[key] + + def expectedMaps = expectedPodData*.toMap() + def actualMaps = actualPodData*.toMap() - assert expectedDeploymentMeans == actualDeploymentMeans + assert expectedMaps == actualMaps + } } } diff --git a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy index 05204c3fc..8edd8bd1b 100644 --- a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy +++ b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy @@ -1,7 +1,6 @@ package org.ods.orchestration.usecase import groovy.json.JsonSlurper -import groovy.json.JsonSlurperClassic import groovy.util.logging.Slf4j import org.apache.commons.io.FileUtils import org.junit.Rule @@ -54,6 +53,8 @@ import static util.FixtureHelper.createSockShopJUnitXmlTestResults @Slf4j class LeVADocumentUseCaseSpec extends SpecHelper { + private static final String FIXTURES_PATH = "org/ods/component/LeVADocumentUseCase" + @Rule public TemporaryFolder tempFolder @@ -111,7 +112,7 @@ class LeVADocumentUseCaseSpec extends SpecHelper { docHistory.load(project.data.jira, []) usecase.getAndStoreDocumentHistory(*_) >> docHistory jenkins.unstashFilesIntoPath(_, _, "SonarQube Report") >> true - steps.getEnv() >> ['RELEASE_PARAM_VERSION': 'WIP'] + steps.getEnv() >> ['RELEASE_PARAM_VERSION': 'WIP', 'BUILD_NUMBER': '10', 'BUILD_URL': 'http://jenkins-project-cd/job/10', 'JOB_NAME': 'project-cd/project-cd-releasemanager-master'] stepsNoWip.getEnv() >> ['RELEASE_PARAM_VERSION': 'CHG00001'] } @@ -1220,22 +1221,6 @@ class LeVADocumentUseCaseSpec extends SpecHelper { def "create TIR"() { given: - // Test Parameters - def repo = project.repositories.first() - def data = [ - openshift: [ - pod: [ - podName: 'N/A', - podNamespace: 'N/A', - podCreationTimestamp: 'N/A', - podEnvironment: 'N/A', - podNode: 'N/A', - podIp: 'N/A', - podStatus: 'N/A' - ] - ] - ] - // Argument Constraints def documentType = TIR as String @@ -1248,36 +1233,27 @@ class LeVADocumentUseCaseSpec extends SpecHelper { usecase.createTIR(repo, data) then: + // Jira enabled + 2 * jiraUseCase.jira >> Mock(JiraService) 1 * usecase.getDocumentSectionsFileOptional(documentType) >> chapterData 0 * levaFiles.getDocumentChapterData(documentType) 1 * usecase.getWatermarkText(documentType, _) >> watermarkText - - then: 1 * usecase.getDocumentMetadata(LeVADocumentUseCase.DOCUMENT_TYPE_NAMES[documentType], repo) 1 * usecase.getDocumentTemplateName(documentType, repo) >> documentTemplate - 1 * usecase.createDocument(documentType, repo, _, [:], _, documentTemplate, watermarkText) + 0 * usecase.obtainCodeReviewReport(_) >> [] + 1 * usecase.createDocument(documentType, repo, _, [:], _, documentTemplate, watermarkText) >> { + assert it[2]."deploymentMean" + assert it[2]."legacy" == legacy + } + + where: + legacy | data | repo + false | FixtureHelper.createTIRDataHelm() | FixtureHelper.createTIRRepoHelm() + true | FixtureHelper.createTIRDataTailor() | FixtureHelper.createTIRRepoTailor() } def "create TIR without Jira"() { given: - project.services.jira = null - def data = [ - openshift: [ - pod: [ - podName: 'N/A', - podNamespace: 'N/A', - podCreationTimestamp: 'N/A', - podEnvironment: 'N/A', - podNode: 'N/A', - podIp: 'N/A', - podStatus: 'N/A' - ] - ] - ] - - // Test Parameters - def repo = project.repositories.first() - // Argument Constraints def documentType = TIR as String @@ -1290,116 +1266,170 @@ class LeVADocumentUseCaseSpec extends SpecHelper { usecase.createTIR(repo, data) then: + // No Jira enabled + 2 * jiraUseCase.jira >> null 1 * project.getDocumentChaptersForDocument(documentType) >> [] 1 * usecase.getDocumentSectionsFileOptional(documentType) 1 * levaFiles.getDocumentChapterData(documentType) >> chapterData 1 * usecase.getWatermarkText(documentType, _) >> watermarkText - - then: 1 * usecase.getDocumentMetadata(LeVADocumentUseCase.DOCUMENT_TYPE_NAMES[documentType], repo) 1 * usecase.getDocumentTemplateName(documentType, repo) >> documentTemplate - 1 * usecase.createDocument(documentType, repo, _, [:], _, documentTemplate, watermarkText) + 1 * usecase.obtainCodeReviewReport(_) >> [] + 1 * usecase.createDocument(documentType, repo, _, [:], _, documentTemplate, watermarkText) >> { + assert it[2]."deploymentMean" + assert it[2]."legacy" == legacy + } + + where: + legacy | data | repo + false | FixtureHelper.createTIRDataHelm() | FixtureHelper.createTIRRepoHelm() + true | FixtureHelper.createTIRDataTailor() | FixtureHelper.createTIRRepoTailor() } - def "assemble deploymentInfo for TIR with helm"() { + def "assemble deploymentMean and deploymentInfo for TIR with helm"() { given: - def file = new FixtureHelper().getResource("deployments-data-helm.json") + def deployments = FixtureHelper.createTIRRepoHelm().data.openshift.deployments OpenShiftHelper.isDeploymentKind(*_) >> true + def expectedMeanInfo = [ + 'namespace': 'myodsproject-dev', + 'type': 'helm', + 'descriptorPath': 'chart', + 'defaultCmdLineArgs': '--install --atomic', + 'additionalCmdLineArgs': '--additional-flag-1 --additional-flag-2', + 'configParams': '''
                  • registry: image-registry.openshift.svc:1000
                  • componentId: backend-helm-monorepo
                  ''', + 'configFiles': '''
                  • values.yaml
                  ''', + 'envConfigFiles': '''
                  • values1.env.yaml
                  • values2.env.yaml
                  ''', + 'deploymentStatus': [ + 'deployStatus': 'Successfully deployed', + 'resultMessage': 'Upgrade complete', + 'lastDeployed': '2024-10-31T11:10:27.478860933Z', + 'resources': '''
                  • Deployment:
                    • backend-helm-monorepo-chart-component-a
                    • backend-helm-monorepo-chart-component-b
                  • Service:
                    • backend-helm-monorepo-chart
                  ''', + ] , + ] + + def expectedDeploymentInfo = [ + 'backend-helm-monorepo-chart-component-b': [ + 'podNamespace': 'myodsproject-dev', + 'podStatus': 'Running', + 'deploymentId': 'backend-helm-monorepo-chart-component-b-567ff4f8f6', + 'podMetaDataCreationTimestamp': '2024-10-31T11:10:28Z', + 'containers': [ + 'chart-component-b': 'image-registry.openshift.svc:1000/myodsproject-dev/backend-helm-monorepo-component-b@sha256:10002345abcde', + ] , + ] , + 'backend-helm-monorepo-chart-component-a': [ + 'podNamespace': 'myodsproject-dev', + 'podStatus': 'Running', + 'deploymentId': 'backend-helm-monorepo-chart-component-a-5ffd9c7cbd', + 'podMetaDataCreationTimestamp': '2024-10-31T11:10:28Z', + 'containers': [ + 'chart-component-a': 'image-registry.openshift.svc:1000/myodsproject-dev/backend-helm-monorepo-component-a@sha256:10002345abcde', + ] , + ] , + ] + when: - def deploymentsData = new JsonSlurperClassic().parseText(file.text) - def helmStatusAndMean = usecase.getHelmStatusAndMean(deploymentsData.deployments) - def nonHelmDeployments = usecase.prepareDeploymentInfo(deploymentsData.deployments) + def deploymentMeanInfo = usecase.prepareDeploymentMeanInfo(deployments) + def deploymentInfo = usecase.prepareDeploymentInfo(deployments) then: + deploymentMeanInfo == expectedMeanInfo + deploymentInfo == expectedDeploymentInfo + } - helmStatusAndMean["mean"] == [ - chartDir : "chart", - repoId : "backend-helm-monorepo", - helmAdditionalFlags: [], - helmEnvBasedValuesFiles : [], - helmValues :[ - registry :"image-registry.openshift-image-registry.svc:5000", - componentId :"backend-helm-monorepo" - ], - helmDefaultFlags :["--install", "--atomic"], - helmReleaseName :"backend-helm-monorepo", - selector :"app.kubernetes.io/instance=backend-helm-monorepo", - helmValuesFiles :["values.yaml"], - type :"helm", ] - - helmStatusAndMean["status"] == [ - "version": "2", - "name": "backend-helm-monorepo", - "namespace": "kraemerh-dev", - "description": "Upgrade complete", - "resourcesByKind": [ - "Deployment": [ - "backend-helm-monorepo-chart-component-a", - "backend-helm-monorepo-chart-component-b" - ], - "Service": ["backend-helm-monorepo-chart"] - ], - "status" :"deployed", - "lastDeployed" :"2024-06-26T12:59:51.270713404Z" - ] - nonHelmDeployments["backend-helm-monorepo-chart-component-a"] == [ - podNamespace: "kraemerh-test", - podMetaDataCreationTimestamp: "2024-06-26T13:05:33Z", - deploymentId: "backend-helm-monorepo-chart-component-a-7d6659884", - podStatus: "Running", - containers: [ - "chart-component-a": "image-registry.openshift-image-registry.svc:5000/kraemerh-test/backend-helm-monorepo-component-a@sha256:5c6440e6179138842d75a9b4a0eb9dd283097839931119e79ee0da43656c8870" - ] - ] - nonHelmDeployments["backend-helm-monorepo-chart-component-b"] == [ - podNamespace: "kraemerh-test", - podMetaDataCreationTimestamp: "2024-06-26T13:05:33Z", - deploymentId: "backend-helm-monorepo-chart-component-b-87c7f548d", - podStatus: "Running", - containers: [ - "chart-component-b": "image-registry.openshift-image-registry.svc:5000/kraemerh-test/backend-helm-monorepo-component-b@sha256:5e9ed6ba8458a9501a9d973398ff27e6e50411d3745cec0dac761e07378185a2" - ] - ] + def "assemble deploymentMean for TIR with helm and default values"() { + given: + def deployments = FixtureHelper.createTIRRepoHelm().data.openshift.deployments + def deploymentMean = deployments.'backend-helm-monorepo-chart-component-b-deploymentMean' + OpenShiftHelper.isDeploymentKind(*_) >> true + + deploymentMean.namespace = '' + deploymentMean.chartDir = '' + deploymentMean.helmDefaultFlags = [] + deploymentMean.helmAdditionalFlags = [] + deploymentMean.helmValues = [:] + deploymentMean.helmValuesFiles = [] + deploymentMean.helmEnvBasedValuesFiles = [] + deploymentMean.helmStatus.status = 'SOME OTHER STATUS' + deploymentMean.helmStatus.resourcesByKind = [:] + + when: + def deploymentMeanInfo = usecase.prepareDeploymentMeanInfo(deployments) + + then: + deploymentMeanInfo.namespace == 'None' + deploymentMeanInfo.descriptorPath == '.' + deploymentMeanInfo.defaultCmdLineArgs == 'None' + deploymentMeanInfo.additionalCmdLineArgs == 'None' + deploymentMeanInfo.configParams =='None' + deploymentMeanInfo.configFiles =='None' + deploymentMeanInfo.envConfigFiles == 'None' + deploymentMeanInfo.deploymentStatus.deployStatus == 'SOME OTHER STATUS' + deploymentMeanInfo.deploymentStatus.resources == 'None' } - def "assemble deploymentInfo for TIR with tailor"() { + def "assemble deploymentMean and deploymentInfo for TIR with tailor"() { given: - def file = new FixtureHelper().getResource("deployments-data-tailor.json") + def deployments = FixtureHelper.createTIRRepoTailor().data.openshift.deployments OpenShiftHelper.isDeploymentKind(*_) >> true + def expectedMeanInfo = [ + 'tailorParamFile': 'a-param-file.yaml', + 'tailorParams': [ 'fake-param1', 'fake-param2' ] , + 'selector': 'app=myodsproject-flask-backend', + 'type': 'tailor', + 'tailorSelectors': [ + 'selector': 'app=myodsproject-flask-backend', + 'exclude': 'bc,is', + ] , + 'tailorPreserve': [ 'fake-preserve1', 'fake-preserve2' ] , + 'tailorVerify': true, + ] + + def expectedDeploymentInfo = [ + 'flask-backend': [ + 'podNamespace': 'myodsproject-dev', + 'podStatus': 'Running', + 'deploymentId': 'flask-backend-2', + 'podMetaDataCreationTimestamp': '2024-10-31T11:09:56Z', + 'containers': [ + 'flask-backend': 'image-registry.openshift.svc:1000/myodsproject-dev/flask-backend@sha256:10002345abcde', + ] , + ] , + ] + when: - def deploymentsData = new JsonSlurperClassic().parseText(file.text) - def helmStatusAndMean = usecase.getHelmStatusAndMean(deploymentsData.deployments) - def nonHelmDeployments = usecase.prepareDeploymentInfo(deploymentsData.deployments) - def data = [:] - if (helmStatusAndMean) { // must be falsy - data << [deployment: helmStatusAndMean] - } - helmStatusAndMean == [:] + def deploymentMeanInfo = usecase.prepareDeploymentMeanInfo(deployments) + def deploymentInfo = usecase.prepareDeploymentInfo(deployments) then: + deploymentMeanInfo == expectedMeanInfo + deploymentInfo == expectedDeploymentInfo + } - data == [:] + def "assemble deploymentMean for TIR with tailor and default values"() { + given: + def deployments = FixtureHelper.createTIRRepoTailor().data.openshift.deployments + def deploymentMean = deployments.'flask-backend-deploymentMean' + OpenShiftHelper.isDeploymentKind(*_) >> true - nonHelmDeployments["backend-first-deploymentMean"] == [ - type: "tailor", - selector: "app=kraemerh-backend-first", - tailorSelectors: [ - selector: "app=kraemerh-backend-first", - exclude: "bc,is", - ], - tailorParamFile: "None", - tailorParams: "None", - tailorPreserve: "No extra resources specified to be preserved", - tailorVerify: true - ] + // These are special cases to be replaced with a custom value when a falsy value is present + deploymentMean.tailorParamFile = '' + deploymentMean.tailorParams = [] + deploymentMean.tailorPreserve = [] - nonHelmDeployments["backend-first"] == [ - containers: [ - "backend-first": "backend-first@sha256:fc5fb63f4ac45e207a4a1ceba37534814489c16e82306cf46aca76627c0f5e1e" - ] - ] + // This one is the general case + deploymentMean.tailorSelectors = [] + + when: + def deploymentMeanInfo = usecase.prepareDeploymentMeanInfo(deployments) + + then: + deploymentMeanInfo.tailorParamFile == 'None' + deploymentMeanInfo.tailorParams == 'None' + deploymentMeanInfo.tailorPreserve == 'No extra resources specified to be preserved' + deploymentMeanInfo.tailorSelectors == 'N/A' } def "create overall DTR"() { diff --git a/test/groovy/org/ods/services/OpenShiftServiceSpec.groovy b/test/groovy/org/ods/services/OpenShiftServiceSpec.groovy index 46abf6596..6eff53520 100644 --- a/test/groovy/org/ods/services/OpenShiftServiceSpec.groovy +++ b/test/groovy/org/ods/services/OpenShiftServiceSpec.groovy @@ -260,16 +260,16 @@ class OpenShiftServiceSpec extends SpecHelper { def helmJsonText = new FixtureHelper().getResource("helmstatus.json").text when: - def helmStatusData = service.helmStatus('guardians-test', 'standalone-app') -// OpenShiftService.DEPLOYMENT_KIND, OpenShiftService.DEPLOYMENTCONFIG_KIND,]) + def helmStatusData = service.helmStatus('myproject-dev', 'backend-helm-monorepo') + then: 1 * steps.sh( - script: 'helm -n guardians-test status standalone-app --show-resources -o json', - label: 'Gather Helm status for release standalone-app in guardians-test', + script: 'helm -n myproject-dev status backend-helm-monorepo --show-resources -o json', + label: 'Gather Helm status for release backend-helm-monorepo in myproject-dev', returnStdout: true, ) >> helmJsonText - helmStatusData.name == 'standalone-app' - helmStatusData.namespace == 'guardians-test' + helmStatusData.name == 'backend-helm-monorepo' + helmStatusData.namespace == 'myproject-dev' } def "helm status data extraction bad content"() { @@ -277,27 +277,27 @@ class OpenShiftServiceSpec extends SpecHelper { def steps = Spy(util.PipelineSteps) def service = new OpenShiftService(steps, new Logger(steps, false)) def helmJsonText = """ -{ - "name": "standalone-app", - "info": { - "first_deployed": "2022-12-19T09:44:32.164490076Z", - "last_deployed": "2024-03-04T15:21:09.34520527Z", - "deleted": "", - "description": "Upgrade complete", - "status": "deployed", - "resources" : {} - } -} + { + "name": "backend-helm-monorepo", + "info": { + "first_deployed": "2022-12-19T09:44:32.164490076Z", + "last_deployed": "2024-03-04T15:21:09.34520527Z", + "deleted": "", + "description": "Upgrade complete", + "status": "deployed", + "resources" : {} + } + } """ when: - def helmStatusData = service.helmStatus('guardians-test', 'standalone-app') + service.helmStatus('myproject-dev ', 'backend-helm-monorepo') // OpenShiftService.DEPLOYMENT_KIND, OpenShiftService.DEPLOYMENTCONFIG_KIND,]) then: 1 * steps.sh( - script: 'helm -n guardians-test status standalone-app --show-resources -o json', - label: 'Gather Helm status for release standalone-app in guardians-test', + script: 'helm -n myproject-dev status backend-helm-monorepo --show-resources -o json', + label: 'Gather Helm status for release backend-helm-monorepo in myproject-dev ', returnStdout: true, - ) + ) >> helmJsonText thrown RuntimeException } diff --git a/test/groovy/util/FixtureHelper.groovy b/test/groovy/util/FixtureHelper.groovy index 0a109ab56..caa5568b4 100644 --- a/test/groovy/util/FixtureHelper.groovy +++ b/test/groovy/util/FixtureHelper.groovy @@ -2,13 +2,12 @@ package util import groovy.json.JsonSlurperClassic import groovy.transform.InheritConstructors - -import org.ods.services.GitService import org.apache.http.client.utils.URIBuilder import org.junit.contrib.java.lang.system.EnvironmentVariables -import org.ods.orchestration.parser.* -import org.ods.orchestration.usecase.* -import org.ods.orchestration.util.* +import org.ods.orchestration.parser.JUnitParser +import org.ods.orchestration.usecase.JiraUseCase +import org.ods.orchestration.util.Project +import org.ods.services.GitService import org.ods.util.IPipelineSteps import org.ods.util.Logger import org.yaml.snakeyaml.Yaml @@ -601,6 +600,1573 @@ class FixtureHelper { ] } + static Map createTIRDataHelm() { + [ + 'git' : [ + 'previousSucessfulCommit': 'b00012345bcdef', + 'baseTag' : '', + 'commit' : 'a00012345bcdef', + 'previousCommit' : 'b00012345bcdef', + 'targetTag' : '', + 'branch' : 'master', + 'url' : 'https://bitbucket-myodsproject-cd.ocp.mycompany.com/scm/myodsproject/myodsproject-backend-helm-monorepo.git', + ], + 'previousSucessfulCommit': 'c00012345bcdef', + 'documents' : [ + ], + 'openshift' : [ + 'testResults' : 1, + 'deployments' : [ + 'backend-helm-monorepo-chart-component-deploymentMean': [ + 'chartDir' : 'chart', + 'helmAdditionalFlags' : ['--additional-flag-1', '--additional-flag-2'], + 'helmEnvBasedValuesFiles': ['values1.env.yaml', 'values2.env.yaml'], + 'helmValues' : [ + 'registry' : 'image-registry.openshift.svc:1000', + 'componentId': 'backend-helm-monorepo', + ], + 'helmDefaultFlags' : ['--install', '--atomic'], + 'namespace' : 'myodsproject-dev', + 'helmReleaseName' : 'backend-helm-monorepo', + 'selector' : 'app.kubernetes.io/instance=backend-helm-monorepo', + 'helmValuesFiles' : ['values.yaml'], + 'type' : 'helm', + 'helmStatus' : [ + 'name' : 'backend-helm-monorepo', + 'namespace' : 'myodsproject-dev', + 'description' : 'Upgrade complete', + 'resourcesByKind': [ + 'Deployment': ['backend-helm-monorepo-chart-component-a', 'backend-helm-monorepo-chart-component-b'], + 'Service' : ['backend-helm-monorepo-chart'], + ], + 'version' : '14', + 'status' : 'deployed', + 'lastDeployed' : '2024-10-31T11:10:27.478860933Z', + ], + ], + 'backend-helm-monorepo-chart-component-b' : [ + 'podNamespace' : 'myodsproject-dev', + 'podStatus' : 'Running', + 'deploymentId' : 'backend-helm-monorepo-chart-component-b-567ff4f8f6', + 'podName' : 'backend-helm-monorepo-chart-component-b-567ff4f8f6-kvmqm', + 'podMetaDataCreationTimestamp': '2024-10-31T11:10:28Z', + 'containers' : [ + 'chart-component-b': 'image-registry.openshift.svc:1000/myodsproject-dev/backend-helm-monorepo-component-b@sha256:10002345abcde', + ], + ], + 'backend-helm-monorepo-chart-component-a' : [ + 'podNamespace' : 'myodsproject-dev', + 'podStatus' : 'Running', + 'deploymentId' : 'backend-helm-monorepo-chart-component-a-5ffd9c7cbd', + 'podName' : 'backend-helm-monorepo-chart-component-a-5ffd9c7cbd-h4wsb', + 'podMetaDataCreationTimestamp': '2024-10-31T11:10:28Z', + 'containers' : [ + 'chart-component-a': 'image-registry.openshift.svc:1000/myodsproject-dev/backend-helm-monorepo-component-a@sha256:10002345abcde', + ], + ], + ], + 'testResultsFolder' : 'build/test-results/test', + 'xunitTestResultsStashPath': 'test-reports-junit-xml-backend-helm-monorepo-19', + 'SCRR' : 'SCRR-myodsproject-backend-helm-monorepo.docx', + 'SCRR-MD' : 'SCRR-myodsproject-backend-helm-monorepo.md', + 'builds' : [ + 'backend-helm-monorepo-component-b': [ + 'image' : 'image-registry.openshift.svc:1000/myodsproject-cd/backend-helm-monorepo-component-b@sha256:10002345abcde', + 'buildId': 'backend-helm-monorepo-component-b-26', + ], + 'backend-helm-monorepo-component-a': [ + 'image' : 'image-registry.openshift.svc:1000/myodsproject-cd/backend-helm-monorepo-component-a@sha256:10002345abcde', + 'buildId': 'backend-helm-monorepo-component-a-26', + ], + ], + 'CREATED_BY_BUILD' : 'WIP/19', + 'sonarqubeScanStashPath' : 'scrr-report-backend-helm-monorepo-19', + ], + ] + } + + static Map createTIRRepoHelm() { + [ + 'include' : true, + 'metadata' : [ + 'supplier' : 'IT INF IAS', + 'name' : 'PostgreSQL', + 'description': 'A fully functional PostgreSQL Cluster with Patroni', + 'type' : 'ods', + 'version' : '4.x', + ], + 'data' : [ + 'git' : [ + 'previousSucessfulCommit': 'b00012345bcdef', + 'baseTag' : '', + 'commit' : 'a00012345bcdef', + 'previousCommit' : 'b00012345bcdef', + 'targetTag' : '', + 'branch' : 'master', + 'url' : 'https://bitbucket-myodsproject-cd.ocp.mycompany.com/scm/myodsproject/myodsproject-backend-helm-monorepo.git', + ], + 'previousSucessfulCommit': 'c00012345bcdef', + 'documents' : [ + ], + 'openshift' : [ + 'testResults' : 1, + 'deployments' : [ + 'backend-helm-monorepo-chart-component-b-deploymentMean': [ + 'chartDir' : 'chart', + 'helmAdditionalFlags' : ['--additional-flag-1', '--additional-flag-2'], + 'helmEnvBasedValuesFiles': ['values1.env.yaml', 'values2.env.yaml'], + 'helmValues' : [ + 'registry' : 'image-registry.openshift.svc:1000', + 'componentId': 'backend-helm-monorepo', + ], + 'helmDefaultFlags' : ['--install', '--atomic'], + 'namespace' : 'myodsproject-dev', + 'helmReleaseName' : 'backend-helm-monorepo', + 'selector' : 'app.kubernetes.io/instance=backend-helm-monorepo', + 'helmValuesFiles' : ['values.yaml'], + 'type' : 'helm', + 'helmStatus' : [ + 'name' : 'backend-helm-monorepo', + 'namespace' : 'myodsproject-dev', + 'description' : 'Upgrade complete', + 'resourcesByKind': [ + 'Deployment': ['backend-helm-monorepo-chart-component-a', 'backend-helm-monorepo-chart-component-b'], + 'Service' : ['backend-helm-monorepo-chart'], + ], + 'version' : '14', + 'status' : 'deployed', + 'lastDeployed' : '2024-10-31T11:10:27.478860933Z', + ], + ], + 'backend-helm-monorepo-chart-component-b' : [ + 'podNamespace' : 'myodsproject-dev', + 'podStatus' : 'Running', + 'deploymentId' : 'backend-helm-monorepo-chart-component-b-567ff4f8f6', + 'podName' : 'backend-helm-monorepo-chart-component-b-567ff4f8f6-kvmqm', + 'podMetaDataCreationTimestamp': '2024-10-31T11:10:28Z', + 'containers' : [ + 'chart-component-b': 'image-registry.openshift.svc:1000/myodsproject-dev/backend-helm-monorepo-component-b@sha256:10002345abcde', + ], + ], + 'backend-helm-monorepo-chart-component-a' : [ + 'podNamespace' : 'myodsproject-dev', + 'podStatus' : 'Running', + 'deploymentId' : 'backend-helm-monorepo-chart-component-a-5ffd9c7cbd', + 'podName' : 'backend-helm-monorepo-chart-component-a-5ffd9c7cbd-h4wsb', + 'podMetaDataCreationTimestamp': '2024-10-31T11:10:28Z', + 'containers' : [ + 'chart-component-a': 'image-registry.openshift.svc:1000/myodsproject-dev/backend-helm-monorepo-component-a@sha256:10002345abcde', + ], + ], + ], + 'testResultsFolder' : 'build/test-results/test', + 'xunitTestResultsStashPath': 'test-reports-junit-xml-backend-helm-monorepo-19', + 'SCRR' : 'SCRR-myodsproject-backend-helm-monorepo.docx', + 'SCRR-MD' : 'SCRR-myodsproject-backend-helm-monorepo.md', + 'builds' : [ + 'backend-helm-monorepo-component-b': [ + 'image' : 'image-registry.openshift.svc:1000/myodsproject-cd/backend-helm-monorepo-component-b@sha256:10002345abcde', + 'buildId': 'backend-helm-monorepo-component-b-26', + ], + 'backend-helm-monorepo-component-a': [ + 'image' : 'image-registry.openshift.svc:1000/myodsproject-cd/backend-helm-monorepo-component-a@sha256:10002345abcde', + 'buildId': 'backend-helm-monorepo-component-a-26', + ], + ], + 'CREATED_BY_BUILD' : 'WIP/19', + 'sonarqubeScanStashPath' : 'scrr-report-backend-helm-monorepo-19', + ], + ], + 'doInstall' : true, + 'pipelineConfig': [ + 'dependencies': [], + ], + 'defaultBranch' : 'master', + 'id' : 'backend-helm-monorepo', + 'type' : 'ods', + 'url' : 'https://bitbucket-myodsproject-cd.ocp.mycompany.com/scm/myodsproject/myodsproject-backend-helm-monorepo.git', + ] + } + + static Map createTIRDataTailor() { + [ + 'git' : [ + 'previousSucessfulCommit': null, + 'baseTag' : '', + 'commit' : 'a00012345bcdef', + 'previousCommit' : null, + 'targetTag' : '', + 'branch' : 'master', + 'url' : 'https://bitbucket-myodsproject-cd.ocp.mycompany.com/scm/myodsproject/myodsproject-flask-backend.git', + ], + 'previousSucessfulCommit': 'c00012345bcdef', + 'documents' : [ + ], + 'openshift' : [ + 'testResults' : 1, + 'deployments' : [ + 'flask-backend' : [ + 'podNamespace' : 'myodsproject-dev', + 'podStatus' : 'Running', + 'deploymentId' : 'flask-backend-2', + 'podName' : 'flask-backend-2-plcgr', + 'podMetaDataCreationTimestamp': '2024-10-31T11:09:56Z', + 'containers' : [ + 'flask-backend': 'image-registry.openshift.svc:1000/myodsproject-dev/flask-backend@sha256:10002345abcde', + ], + ], + 'flask-backend-deploymentMean': [ + 'tailorParamFile': '', + 'tailorParams' : [], + 'selector' : 'app=myodsproject-flask-backend', + 'type' : 'tailor', + 'tailorSelectors': [ + 'selector': 'app=myodsproject-flask-backend', + 'exclude' : 'bc,is', + ], + 'tailorPreserve' : [], + 'tailorVerify' : true, + ], + ], + 'testResultsFolder' : 'build/test-results/test', + 'xunitTestResultsStashPath': 'test-reports-junit-xml-flask-backend-19', + 'SCRR' : 'SCRR-myodsproject-flask-backend.docx', + 'SCRR-MD' : 'SCRR-myodsproject-flask-backend.md', + 'builds' : [ + 'flask-backend': [ + 'image' : 'image-registry.openshift.svc:1000/myodsproject-cd/flask-backend@sha256:10002345abcde', + 'buildId': 'flask-backend-2', + ], + ], + 'CREATED_BY_BUILD' : 'WIP/19', + 'sonarqubeScanStashPath' : 'scrr-report-flask-backend-19', + ], + ] + } + + static Map createTIRRepoTailor() { + [ + 'include' : true, + 'metadata' : [ + 'supplier' : 'https://www.palletsprojects.com/p/flask/', + 'name' : 'Flask', + 'description': 'Flask is a micro web framework written in Python. Technologies: Flask 3.0.0, Python 3.11', + 'type' : 'ods', + 'version' : '4.x', + ], + 'data' : [ + 'git' : [ + 'previousSucessfulCommit': null, + 'baseTag' : '', + 'commit' : 'a00012345bcdef', + 'previousCommit' : null, + 'targetTag' : '', + 'branch' : 'master', + 'url' : 'https://bitbucket-myodsproject-cd.ocp.mycompany.com/scm/myodsproject/myodsproject-flask-backend.git', + ], + 'previousSucessfulCommit': 'c00012345bcdef', + 'documents' : [ + ], + 'openshift' : [ + 'testResults' : 1, + 'deployments' : [ + 'flask-backend' : [ + 'podNamespace' : 'myodsproject-dev', + 'podStatus' : 'Running', + 'deploymentId' : 'flask-backend-2', + 'podName' : 'flask-backend-2-plcgr', + 'podMetaDataCreationTimestamp': '2024-10-31T11:09:56Z', + 'containers' : [ + 'flask-backend': 'image-registry.openshift.svc:1000/myodsproject-dev/flask-backend@sha256:10002345abcde', + ], + ], + 'flask-backend-deploymentMean': [ + 'tailorParamFile': 'a-param-file.yaml', + 'tailorParams' : ['fake-param1', 'fake-param2'], + 'selector' : 'app=myodsproject-flask-backend', + 'type' : 'tailor', + 'tailorSelectors': [ + 'selector': 'app=myodsproject-flask-backend', + 'exclude' : 'bc,is', + ], + 'tailorPreserve' : ['fake-preserve1', 'fake-preserve2'], + 'tailorVerify' : true, + ], + ], + 'testResultsFolder' : 'build/test-results/test', + 'xunitTestResultsStashPath': 'test-reports-junit-xml-flask-backend-19', + 'SCRR' : 'SCRR-myodsproject-flask-backend.docx', + 'SCRR-MD' : 'SCRR-myodsproject-flask-backend.md', + 'builds' : [ + 'flask-backend': [ + 'image' : 'image-registry.openshift.svc:1000/myodsproject-cd/flask-backend@sha256:10002345abcde', + 'buildId': 'flask-backend-2', + ], + ], + 'CREATED_BY_BUILD' : 'WIP/19', + 'sonarqubeScanStashPath' : 'scrr-report-flask-backend-19', + ], + ], + 'doInstall' : true, + 'pipelineConfig': [ + 'dependencies': [], + ], + 'defaultBranch' : 'master', + 'id' : 'flask-backend', + 'type' : 'ods', + 'url' : 'https://bitbucket-myodsproject-cd.ocp.mycompany.com/scm/myodsproject/myodsproject-flask-backend.git', + ] + } + + static Map createHelmCmdStatusMap() { + [ + 'info' : [ + 'deleted' : '', + 'description' : 'Upgrade complete', + 'first_deployed': '2022-12-19T09:44:32.164490076Z', + 'last_deployed' : '2024-03-04T15:21:09.34520527Z', + 'resources' : [ + 'v1/Cluster' : [[ + 'apiVersion': 'postgresql.k8s.k8db.io/v1', + 'kind' : 'Cluster', + 'metadata' : [ + 'annotations' : [ + 'meta.helm.sh/release-name' : 'standalone-app', + 'meta.helm.sh/release-namespace': 'myproject-test', + ], + 'creationTimestamp': '2023-07-04T13:18:28Z', + 'generation' : 3, + 'labels' : [ + 'app.kubernetes.io/instance' : 'standalone-app', + 'app.kubernetes.io/managed-by': 'Helm', + 'app.kubernetes.io/name' : 'some-cluster', + 'app.kubernetes.io/version' : 'aaaabbbbcccc', + 'helm.sh/chart' : 'some-cluster-0.1.0_aaaabbbbcccc', + ], + 'name' : 'some-cluster', + 'namespace' : 'myproject-test', + 'resourceVersion' : '2880969905', + 'uid' : '12345678-1234-1234-1234-200000000abcde', + ], + 'spec' : [ + 'affinity' : [ + 'podAntiAffinityType': 'preferred', + ], + 'bootstrap' : [ + 'initdb': [ + 'database' : 'app', + 'encoding' : 'UTF8', + 'localeCType' : 'C', + 'localeCollate': 'C', + 'owner' : 'app', + ], + ], + 'enableSuperuserAccess': true, + 'failoverDelay' : 0, + 'imageName' : 'quay.io/k8db/postgresql:x.x', + 'instances' : 1, + 'logLevel' : 'info', + 'maxSyncReplicas' : 0, + 'minSyncReplicas' : 0, + 'monitoring' : [ + 'customQueriesConfigMap': [[ + 'key' : 'queries', + 'name': 'postgresql-operator-default-monitoring', + ]], + 'disableDefaultQueries' : false, + 'enablePodMonitor' : false, + ], + 'postgresGID' : 26, + 'postgresUID' : 26, + 'postgresql' : [ + 'parameters' : [ + 'archive_mode' : 'on', + 'archive_timeout' : '5min', + 'dynamic_shared_memory_type': 'posix', + 'log_destination' : 'csvlog', + 'log_directory' : '/controller/log', + 'log_filename' : 'postgres', + 'log_rotation_age' : '0', + 'log_rotation_size' : '0', + 'log_truncate_on_rotation' : 'false', + 'logging_collector' : 'on', + 'max_parallel_workers' : '32', + 'max_replication_slots' : '32', + 'max_worker_processes' : '32', + 'shared_memory_type' : 'mmap', + 'shared_preload_libraries' : '', + 'ssl_max_protocol_version' : 'TLSvx.x', + 'ssl_min_protocol_version' : 'TLSvx.x', + 'wal_keep_size' : '512MB', + 'wal_receiver_timeout' : '5s', + 'wal_sender_timeout' : '5s', + ], + 'syncReplicaElectionConstraint': [ + 'enabled': false, + ], + ], + 'primaryUpdateMethod' : 'restart', + 'primaryUpdateStrategy': 'unsupervised', + 'replicationSlots' : [ + 'highAvailability': [ + 'enabled' : true, + 'slotPrefix': '_cnp_', + ], + 'updateInterval' : 30, + ], + 'resources' : [], + 'smartShutdownTimeout' : 180, + 'startDelay' : 30, + 'stopDelay' : 30, + 'storage' : [ + 'resizeInUseVolumes': true, + 'size' : '20Gi', + ], + 'switchoverDelay' : 40000000, + ], + 'status' : [ + 'certificates' : [ + 'clientCASecret' : 'some-cluster-ca', + 'expirations' : [ + 'some-cluster-ca' : '2024-08-29 14:02:22 +0000 UTC', + 'some-cluster-replication': '2024-08-29 14:02:22 +0000 UTC', + 'some-cluster-server' : '2024-08-29 14:02:22 +0000 UTC', + ], + 'replicationTLSSecret': 'some-cluster-replication', + 'serverAltDNSNames' : ['some-cluster-rw', 'some-cluster-rw.myproject-test', 'some-cluster-rw.myproject-test.svc', 'some-cluster-r', 'some-cluster-r.myproject-test', 'some-cluster-r.myproject-test.svc', 'some-cluster-ro', 'some-cluster-ro.myproject-test', 'some-cluster-ro.myproject-test.svc'], + 'serverCASecret' : 'some-cluster-ca', + 'serverTLSSecret' : 'some-cluster-server', + ], + 'cloudNativePostgresqlCommitHash' : '900010000', + 'cloudNativePostgresqlOperatorHash': '12345abcdef', + 'conditions' : [[ + 'lastTransitionTime': '2024-05-25T14:42:08Z', + 'message' : 'Cluster is Ready', + 'reason' : 'ClusterIsReady', + 'status' : 'True', + 'type' : 'Ready', + ], [ + 'lastTransitionTime': '2023-07-04T13:19:38Z', + 'message' : 'vlr addon is disabled', + 'reason' : 'Disabled', + 'status' : 'False', + 'type' : 'k8s.k8db.io/vlr', + ], [ + 'lastTransitionTime': '2023-07-04T13:19:38Z', + 'message' : 'external-backup-adapter addon is disabled', + 'reason' : 'Disabled', + 'status' : 'False', + 'type' : 'k8s.k8db.io/extBackpAdapt', + ], [ + 'lastTransitionTime': '2023-07-04T13:19:38Z', + 'message' : 'external-backup-adapter-cluster addon is disabled', + 'reason' : 'Disabled', + 'status' : 'False', + 'type' : 'k8s.k8db.io/extBackpAdaptCluster', + ], [ + 'lastTransitionTime': '2023-07-04T13:19:40Z', + 'message' : 'kstn addon is disabled', + 'reason' : 'Disabled', + 'status' : 'False', + 'type' : 'k8s.k8db.io/kstn', + ], [ + 'lastTransitionTime': '2023-11-30T15:26:14Z', + 'message' : 'Continuous archiving is working', + 'reason' : 'ContinuousArchivingSuccess', + 'status' : 'True', + 'type' : 'ContinuousArchiving', + ]], + 'configMapResourceVersion' : [ + 'metrics': [ + 'postgresql-operator-default-monitoring': '2880955105', + ], + ], + 'currentPrimary' : 'some-cluster-1', + 'currentPrimaryTimestamp' : '2023-07-04T13:19:27.039619Z', + 'healthyPVC' : ['some-cluster-1'], + 'instanceNames' : ['some-cluster-1'], + 'instances' : 1, + 'instancesReportedState' : [ + 'some-cluster-1': [ + 'isPrimary' : true, + 'timeLineID': 1, + ], + ], + 'instancesStatus' : [ + 'healthy': ['some-cluster-1'], + ], + 'latestGeneratedNode' : 1, + 'licenseStatus' : [ + 'licenseExpiration': '2999-12-31T00:00:00Z', + 'licenseStatus' : 'Valid license (My Company (my_company))', + 'repositoryAccess' : false, + 'valid' : true, + ], + 'managedRolesStatus' : [], + 'phase' : 'Cluster in healthy state', + 'poolerIntegrations' : [ + 'pgBouncerIntegration': [], + ], + 'pvcCount' : 1, + 'readService' : 'some-cluster-r', + 'readyInstances' : 1, + 'secretsResourceVersion' : [ + 'applicationSecretVersion': '2880969810', + 'clientCaSecretVersion' : '2880969811', + 'replicationSecretVersion': '2880969813', + 'serverCaSecretVersion' : '2880969811', + 'serverSecretVersion' : '2880969815', + 'superuserSecretVersion' : '2880969816', + ], + 'targetPrimary' : 'some-cluster-1', + 'targetPrimaryTimestamp' : '2023-07-04T13:18:29.516149Z', + 'timelineID' : 1, + 'topology' : [ + 'instances' : [ + 'some-cluster-1': [], + ], + 'nodesUsed' : 1, + 'successfullyExtracted': true, + ], + 'writeService' : 'some-cluster-rw', + ], + ]], + 'v1/ConfigMap' : [[ + 'apiVersion': 'v1', + 'data' : [ + 'application.yaml': 'REDACTED\n', + ], + 'kind' : 'ConfigMap', + 'metadata' : [ + 'annotations' : [ + 'meta.helm.sh/release-name' : 'standalone-app', + 'meta.helm.sh/release-namespace': 'myproject-test', + ], + 'creationTimestamp': '2023-05-16T15:41:54Z', + 'labels' : [ + 'app.kubernetes.io/instance' : 'standalone-app', + 'app.kubernetes.io/managed-by': 'Helm', + 'app.kubernetes.io/name' : 'core', + 'app.kubernetes.io/version' : 'ea01234567', + 'helm.sh/chart' : 'core-0.1.0_ea01234567', + ], + 'name' : 'core-appconfig-configmap', + 'namespace' : 'myproject-test', + 'resourceVersion' : '2880955101', + 'uid' : '12345678-1234-1234-1234-600000000abcde', + ], + ]], + 'v1/Deployment' : [[ + 'apiVersion': 'apps/v1', + 'kind' : 'Deployment', + 'metadata' : [ + 'annotations' : [ + 'deployment.kubernetes.io/revision': '36', + 'meta.helm.sh/release-name' : 'standalone-app', + 'meta.helm.sh/release-namespace' : 'myproject-test', + ], + 'creationTimestamp': '2022-12-19T09:44:33Z', + 'generation' : 42, + 'labels' : [ + 'app.kubernetes.io/instance' : 'standalone-app', + 'app.kubernetes.io/managed-by': 'Helm', + 'app.kubernetes.io/name' : 'core', + 'app.kubernetes.io/version' : 'ea01234567', + 'helm.sh/chart' : 'core-0.1.0_ea01234567', + ], + 'name' : 'core', + 'namespace' : 'myproject-test', + 'resourceVersion' : '2865328801', + 'uid' : '12345678-1234-1234-1234-100000000abcde', + ], + 'spec' : [ + 'progressDeadlineSeconds': 600, + 'replicas' : 1, + 'revisionHistoryLimit' : 10, + 'selector' : [ + 'matchLabels': [ + 'app.kubernetes.io/instance': 'standalone-app', + 'app.kubernetes.io/name' : 'core', + ], + ], + 'strategy' : [ + 'type': 'Recreate', + ], + 'template' : [ + 'metadata': [ + 'annotations' : [ + 'checksum/appconfig-configmap' : 'cf012345cf', + 'checksum/rsa-key-secret' : '57a57a57a57a', + 'checksum/security-exandradev-secret': 'abcdef12345', + 'checksum/security-unify-secret' : '1a2b3c4d', + ], + 'creationTimestamp': null, + 'labels' : [ + 'app.kubernetes.io/instance': 'standalone-app', + 'app.kubernetes.io/name' : 'core', + ], + ], + 'spec' : [ + 'containers' : [[ + 'env' : [[ + 'name' : 'EXANDRADEV_CLIENT_ID', + 'valueFrom': [ + 'secretKeyRef': [ + 'key' : 'clientId', + 'name': 'core-security-exandradev-secret', + ], + ], + ], [ + 'name' : 'EXANDRADEV_CLIENT_SECRET', + 'valueFrom': [ + 'secretKeyRef': [ + 'key' : 'clientSecret', + 'name': 'core-security-exandradev-secret', + ], + ], + ], [ + 'name' : 'UNIFY_CLIENT_ID', + 'valueFrom': [ + 'secretKeyRef': [ + 'key' : 'clientId', + 'name': 'core-security-unify-secret', + ], + ], + ], [ + 'name' : 'UNIFY_CLIENT_SECRET', + 'valueFrom': [ + 'secretKeyRef': [ + 'key' : 'clientSecret', + 'name': 'core-security-unify-secret', + ], + ], + ], [ + 'name' : 'DB_USERNAME', + 'valueFrom': [ + 'secretKeyRef': [ + 'key' : 'username', + 'name': 'some-cluster-app', + ], + ], + ], [ + 'name' : 'DB_PASSWORD', + 'valueFrom': [ + 'secretKeyRef': [ + 'key' : 'password', + 'name': 'some-cluster-app', + ], + ], + ]], + 'image' : 'image-registry.openshift.svc:1000/myproject-test/core-standalone:ea01234567', + 'imagePullPolicy' : 'IfNotPresent', + 'livenessProbe' : [ + 'failureThreshold': 3, + 'httpGet' : [ + 'path' : '/q/health/live', + 'port' : 'http', + 'scheme': 'HTTP', + ], + 'periodSeconds' : 5, + 'successThreshold': 1, + 'timeoutSeconds' : 1, + ], + 'name' : 'core', + 'ports' : [[ + 'containerPort': 8081, + 'name' : 'http', + 'protocol' : 'TCP', + ]], + 'resources' : [ + 'limits' : [ + 'cpu' : '1', + 'memory': '512Mi', + ], + 'requests': [ + 'cpu' : '1', + 'memory': '512Mi', + ], + ], + 'securityContext' : [], + 'startupProbe' : [ + 'failureThreshold': 20, + 'httpGet' : [ + 'path' : '/q/health/started', + 'port' : 'http', + 'scheme': 'HTTP', + ], + 'periodSeconds' : 3, + 'successThreshold': 1, + 'timeoutSeconds' : 1, + ], + 'terminationMessagePath' : '/dev/termination-log', + 'terminationMessagePolicy': 'File', + 'volumeMounts' : [[ + 'mountPath': '/deployments/core/rsa', + 'name' : 'exandra-rsa-key-volume', + 'readOnly' : true, + ], [ + 'mountPath': '/deployments/config', + 'name' : 'exandra-config-volume', + 'readOnly' : true, + ]], + ]], + 'dnsPolicy' : 'ClusterFirst', + 'restartPolicy' : 'Always', + 'schedulerName' : 'default-scheduler', + 'securityContext' : [], + 'terminationGracePeriodSeconds': 30, + 'volumes' : [[ + 'name' : 'exandra-rsa-key-volume', + 'secret': [ + 'defaultMode': 420, + 'items' : [[ + 'key' : 'rsaKey', + 'path': 'jwk.json', + ]], + 'secretName' : 'core-rsa-key-secret', + ], + ], [ + 'configMap': [ + 'defaultMode': 420, + 'name' : 'core-appconfig-configmap', + ], + 'name' : 'exandra-config-volume', + ]], + ], + ], + ], + 'status' : [ + 'availableReplicas' : 1, + 'conditions' : [[ + 'lastTransitionTime': '2023-05-16T15:53:18Z', + 'lastUpdateTime' : '2024-03-04T15:21:26Z', + 'message' : 'ReplicaSet \"core-f7f7f7f7\" has successfully progressed.', + 'reason' : 'NewReplicaSetAvailable', + 'status' : 'True', + 'type' : 'Progressing', + ], [ + 'lastTransitionTime': '2024-05-25T13:43:04Z', + 'lastUpdateTime' : '2024-05-25T13:43:04Z', + 'message' : 'Deployment has minimum availability.', + 'reason' : 'MinimumReplicasAvailable', + 'status' : 'True', + 'type' : 'Available', + ]], + 'observedGeneration': 42, + 'readyReplicas' : 1, + 'replicas' : 1, + 'updatedReplicas' : 1, + ], + ], [ + 'apiVersion': 'apps/v1', + 'kind' : 'Deployment', + 'metadata' : [ + 'annotations' : [ + 'deployment.kubernetes.io/revision': '18', + 'meta.helm.sh/release-name' : 'standalone-app', + 'meta.helm.sh/release-namespace' : 'myproject-test', + ], + 'creationTimestamp': '2023-05-08T09:40:33Z', + 'generation' : 18, + 'labels' : [ + 'app.kubernetes.io/instance' : 'standalone-app', + 'app.kubernetes.io/managed-by': 'Helm', + 'app.kubernetes.io/name' : 'standalone-gateway', + 'app.kubernetes.io/version' : '7b5e50e13fd78502967881f4970484ae08b76dc4', + 'helm.sh/chart' : 'standalone-gateway-0.1.0_7b5e50e13fd78502967881f4970484ae08b76d', + ], + 'name' : 'standalone-gateway', + 'namespace' : 'myproject-test', + 'resourceVersion' : '2865332166', + 'uid' : '12345678-1234-1234-1234-220000000abcde', + ], + 'spec' : [ + 'progressDeadlineSeconds': 600, + 'replicas' : 1, + 'revisionHistoryLimit' : 10, + 'selector' : [ + 'matchLabels': [ + 'app.kubernetes.io/instance': 'standalone-app', + 'app.kubernetes.io/name' : 'standalone-gateway', + ], + ], + 'strategy' : [ + 'type': 'Recreate', + ], + 'template' : [ + 'metadata': [ + 'creationTimestamp': null, + 'labels' : [ + 'app.kubernetes.io/instance': 'standalone-app', + 'app.kubernetes.io/name' : 'standalone-gateway', + ], + ], + 'spec' : [ + 'containers' : [[ + 'image' : 'image-registry.openshift.svc:1000/myproject-test/standalone-gateway:7b5e50e13fd78502967881f4970484ae08b76dc4', + 'imagePullPolicy' : 'IfNotPresent', + 'livenessProbe' : [ + 'failureThreshold': 3, + 'httpGet' : [ + 'path' : '/ready', + 'port' : 9901, + 'scheme': 'HTTP', + ], + 'periodSeconds' : 5, + 'successThreshold': 1, + 'timeoutSeconds' : 1, + ], + 'name' : 'standalone-gateway', + 'ports' : [[ + 'containerPort': 8000, + 'name' : 'http', + 'protocol' : 'TCP', + ]], + 'resources' : [ + 'limits' : [ + 'cpu' : '1', + 'memory': '512Mi', + ], + 'requests': [ + 'cpu' : '100m', + 'memory': '256Mi', + ], + ], + 'securityContext' : [], + 'startupProbe' : [ + 'failureThreshold' : 30, + 'httpGet' : [ + 'path' : '/ready', + 'port' : 9901, + 'scheme': 'HTTP', + ], + 'initialDelaySeconds': 1, + 'periodSeconds' : 1, + 'successThreshold' : 1, + 'timeoutSeconds' : 1, + ], + 'terminationMessagePath' : '/dev/termination-log', + 'terminationMessagePolicy': 'File', + ]], + 'dnsPolicy' : 'ClusterFirst', + 'restartPolicy' : 'Always', + 'schedulerName' : 'default-scheduler', + 'securityContext' : [], + 'terminationGracePeriodSeconds': 30, + ], + ], + ], + 'status' : [ + 'availableReplicas' : 1, + 'conditions' : [[ + 'lastTransitionTime': '2023-05-08T09:40:33Z', + 'lastUpdateTime' : '2023-12-20T16:48:17Z', + 'message' : 'ReplicaSet \"standalone-gateway-500000000c\" has successfully progressed.', + 'reason' : 'NewReplicaSetAvailable', + 'status' : 'True', + 'type' : 'Progressing', + ], [ + 'lastTransitionTime': '2024-05-25T13:43:54Z', + 'lastUpdateTime' : '2024-05-25T13:43:54Z', + 'message' : 'Deployment has minimum availability.', + 'reason' : 'MinimumReplicasAvailable', + 'status' : 'True', + 'type' : 'Available', + ]], + 'observedGeneration': 18, + 'readyReplicas' : 1, + 'replicas' : 1, + 'updatedReplicas' : 1, + ], + ]], + 'v1/Pod(related)': [[ + 'apiVersion': 'v1', + 'items' : [[ + 'apiVersion': 'v1', + 'kind' : 'Pod', + 'metadata' : [ + 'annotations' : [ + 'checksum/appconfig-configmap' : 'cf012345cf', + 'checksum/rsa-key-secret' : '57a57a57a57a', + 'checksum/security-exandradev-secret' : 'abcdef12345', + 'checksum/security-unify-secret' : '1a2b3c4d', + 'k8s.ovn.org/pod-networks' : '{\"default\":{\"ip_addresses\":[\"10.200.10.50/24\"],\"mac_address\":\"0a:00:00:00:00:0a\",\"gateway_ips\":[\"10.200.10.1\"],\"routes\":[{\"dest\":\"10.200.0.0/16\",\"nextHop\":\"10.200.10.1\"},{\"dest\":\"170.30.0.0/16\",\"nextHop\":\"10.200.10.1\"},{\"dest\":\"100.64.0.0/16\",\"nextHop\":\"10.200.10.1\"}],\"ip_address\":\"10.200.10.50/24\",\"gateway_ip\":\"10.200.10.1\"}}', + 'k8s.v1.cni.cncf.io/network-status' : '[{\n \"name\": \"ovn-kubernetes\",\n \"interface\": \"eth0\",\n \"ips\": [\n \"10.200.10.50\"\n ],\n \"mac\": \"0a:00:00:00:00:0a\",\n \"default\": true,\n \"dns\": {}\n}]', + 'openshift.io/scc' : 'restricted-v2', + 'seccomp.security.alpha.kubernetes.io/pod': 'runtime/default', + ], + 'creationTimestamp': '2024-05-25T13:41:18Z', + 'generateName' : 'core-f7f7f7f7-', + 'labels' : [ + 'app.kubernetes.io/instance': 'standalone-app', + 'app.kubernetes.io/name' : 'core', + 'pod-template-hash' : 'f7f7f7f7', + ], + 'name' : 'core-f7f7f7f7-8abcx', + 'namespace' : 'myproject-test', + 'ownerReferences' : [[ + 'apiVersion' : 'apps/v1', + 'blockOwnerDeletion': true, + 'controller' : true, + 'kind' : 'ReplicaSet', + 'name' : 'core-f7f7f7f7', + 'uid' : '12345678-1234-1234-1234-900000000abcde', + ]], + 'resourceVersion' : '2865328796', + 'uid' : '12345678-1234-1234-1234-400000000abcde', + ], + 'spec' : [ + 'containers' : [[ + 'env' : [[ + 'name' : 'EXANDRADEV_CLIENT_ID', + 'valueFrom': [ + 'secretKeyRef': [ + 'key' : 'clientId', + 'name': 'core-security-exandradev-secret', + ], + ], + ], [ + 'name' : 'EXANDRADEV_CLIENT_SECRET', + 'valueFrom': [ + 'secretKeyRef': [ + 'key' : 'clientSecret', + 'name': 'core-security-exandradev-secret', + ], + ], + ], [ + 'name' : 'UNIFY_CLIENT_ID', + 'valueFrom': [ + 'secretKeyRef': [ + 'key' : 'clientId', + 'name': 'core-security-unify-secret', + ], + ], + ], [ + 'name' : 'UNIFY_CLIENT_SECRET', + 'valueFrom': [ + 'secretKeyRef': [ + 'key' : 'clientSecret', + 'name': 'core-security-unify-secret', + ], + ], + ], [ + 'name' : 'DB_USERNAME', + 'valueFrom': [ + 'secretKeyRef': [ + 'key' : 'username', + 'name': 'some-cluster-app', + ], + ], + ], [ + 'name' : 'DB_PASSWORD', + 'valueFrom': [ + 'secretKeyRef': [ + 'key' : 'password', + 'name': 'some-cluster-app', + ], + ], + ]], + 'image' : 'image-registry.openshift.svc:1000/myproject-test/core-standalone:ea01234567', + 'imagePullPolicy' : 'IfNotPresent', + 'livenessProbe' : [ + 'failureThreshold': 3, + 'httpGet' : [ + 'path' : '/q/health/live', + 'port' : 'http', + 'scheme': 'HTTP', + ], + 'periodSeconds' : 5, + 'successThreshold': 1, + 'timeoutSeconds' : 1, + ], + 'name' : 'core', + 'ports' : [[ + 'containerPort': 8081, + 'name' : 'http', + 'protocol' : 'TCP', + ]], + 'resources' : [ + 'limits' : [ + 'cpu' : '1', + 'memory': '512Mi', + ], + 'requests': [ + 'cpu' : '1', + 'memory': '512Mi', + ], + ], + 'securityContext' : [ + 'allowPrivilegeEscalation': false, + 'capabilities' : [ + 'drop': ['ALL'], + ], + 'runAsNonRoot' : true, + 'runAsUser' : 1001270000, + ], + 'startupProbe' : [ + 'failureThreshold': 20, + 'httpGet' : [ + 'path' : '/q/health/started', + 'port' : 'http', + 'scheme': 'HTTP', + ], + 'periodSeconds' : 3, + 'successThreshold': 1, + 'timeoutSeconds' : 1, + ], + 'terminationMessagePath' : '/dev/termination-log', + 'terminationMessagePolicy': 'File', + 'volumeMounts' : [[ + 'mountPath': '/deployments/core/rsa', + 'name' : 'exandra-rsa-key-volume', + 'readOnly' : true, + ], [ + 'mountPath': '/deployments/config', + 'name' : 'exandra-config-volume', + 'readOnly' : true, + ], [ + 'mountPath': '/var/run/secrets/kubernetes.io/secretaccount', + 'name' : 'kube-api-access-lkjhg', + 'readOnly' : true, + ]], + ]], + 'dnsPolicy' : 'ClusterFirst', + 'enableServiceLinks' : true, + 'imagePullSecrets' : [[ + 'name': 'default-dockercfg-xasdf', + ]], + 'nodeName' : 'ip-10.8.30.200.ec2.internal', + 'preemptionPolicy' : 'PreemptLowerPriority', + 'priority' : 0, + 'restartPolicy' : 'Always', + 'schedulerName' : 'default-scheduler', + 'securityContext' : [ + 'fsGroup' : 1001270000, + 'seLinuxOptions': [ + 'level': 's0:c36,c5', + ], + 'seccompProfile': [ + 'type': 'RuntimeDefault', + ], + ], + 'serviceAccount' : 'default', + 'serviceAccountName' : 'default', + 'terminationGracePeriodSeconds': 30, + 'tolerations' : [[ + 'effect' : 'NoExecute', + 'key' : 'node.kubernetes.io/not-ready', + 'operator' : 'Exists', + 'tolerationSeconds': 300, + ], [ + 'effect' : 'NoExecute', + 'key' : 'node.kubernetes.io/unreachable', + 'operator' : 'Exists', + 'tolerationSeconds': 300, + ], [ + 'effect' : 'NoSchedule', + 'key' : 'node.kubernetes.io/memory-pressure', + 'operator': 'Exists', + ]], + 'volumes' : [[ + 'name' : 'exandra-rsa-key-volume', + 'secret': [ + 'defaultMode': 420, + 'items' : [[ + 'key' : 'rsaKey', + 'path': 'jwk.json', + ]], + 'secretName' : 'core-rsa-key-secret', + ], + ], [ + 'configMap': [ + 'defaultMode': 420, + 'name' : 'core-appconfig-configmap', + ], + 'name' : 'exandra-config-volume', + ], [ + 'name' : 'kube-api-access-lkjhg', + 'projected': [ + 'defaultMode': 420, + 'sources' : [[ + 'serviceAccountToken': [ + 'expirationSeconds': 3607, + 'path' : 'token', + ], + ], [ + 'configMap': [ + 'items': [[ + 'key' : 'ca.crt', + 'path': 'ca.crt', + ]], + 'name' : 'kube-some-ca.crt', + ], + ], [ + 'downwardAPI': [ + 'items': [[ + 'fieldRef': [ + 'apiVersion': 'v1', + 'fieldPath' : 'metadata.namespace', + ], + 'path' : 'namespace', + ]], + ], + ], [ + 'configMap': [ + 'items': [[ + 'key' : 'service-ca.crt', + 'path': 'service-ca.crt', + ]], + 'name' : 'openshift-some-ca.crt', + ], + ]], + ], + ]], + ], + 'status' : [ + 'conditions' : [[ + 'lastProbeTime' : null, + 'lastTransitionTime': '2024-05-25T13:41:18Z', + 'status' : 'True', + 'type' : 'Initialized', + ], [ + 'lastProbeTime' : null, + 'lastTransitionTime': '2024-05-25T13:43:03Z', + 'status' : 'True', + 'type' : 'Ready', + ], [ + 'lastProbeTime' : null, + 'lastTransitionTime': '2024-05-25T13:43:03Z', + 'status' : 'True', + 'type' : 'ContainersReady', + ], [ + 'lastProbeTime' : null, + 'lastTransitionTime': '2024-05-25T13:41:18Z', + 'status' : 'True', + 'type' : 'PodScheduled', + ]], + 'containerStatuses': [[ + 'containerID' : 'cri-o://475000574', + 'image' : 'image-registry.openshift.svc:1000/myproject-test/core-standalone:ea01234567', + 'imageID' : 'image-registry.openshift.svc:1000/myproject-test/core-standalone@sha256:6a000a6', + 'lastState' : [], + 'name' : 'core', + 'ready' : true, + 'restartCount': 0, + 'started' : true, + 'state' : [ + 'running': [ + 'startedAt': '2024-05-25T13:42:52Z', + ], + ], + ]], + 'hostIP' : '10.8.30.200', + 'phase' : 'Running', + 'podIP' : '10.200.10.50', + 'podIPs' : [[ + 'ip': '10.200.10.50', + ]], + 'qosClass' : 'Guaranteed', + 'startTime' : '2024-05-25T13:41:18Z', + ], + ]], + 'kind' : 'PodList', + 'metadata' : [ + 'resourceVersion': '2886974735', + ], + ], [ + 'apiVersion': 'v1', + 'items' : [[ + 'apiVersion': 'v1', + 'kind' : 'Pod', + 'metadata' : [ + 'annotations' : [ + 'k8s.ovn.org/pod-networks' : '{\"default\":{\"ip_addresses\":[\"10.251.18.51/24\"],\"mac_address\":\"0c:00:00:00:00:0c\",\"gateway_ips\":[\"10.200.10.1\"],\"routes\":[{\"dest\":\"10.200.0.0/16\",\"nextHop\":\"10.200.10.1\"},{\"dest\":\"170.30.0.0/16\",\"nextHop\":\"10.200.10.1\"},{\"dest\":\"100.64.0.0/16\",\"nextHop\":\"10.200.10.1\"}],\"ip_address\":\"10.251.18.51/24\",\"gateway_ip\":\"10.200.10.1\"}}', + 'k8s.v1.cni.cncf.io/network-status' : '[{\n \"name\": \"ovn-kubernetes\",\n \"interface\": \"eth0\",\n \"ips\": [\n \"10.251.18.51\"\n ],\n \"mac\": \"0c:00:00:00:00:0c\",\n \"default\": true,\n \"dns\": {}\n}]', + 'openshift.io/scc' : 'restricted-v2', + 'seccomp.security.alpha.kubernetes.io/pod': 'runtime/default', + ], + 'creationTimestamp': '2024-05-25T13:41:18Z', + 'generateName' : 'standalone-gateway-500000000c-', + 'labels' : [ + 'app.kubernetes.io/instance': 'standalone-app', + 'app.kubernetes.io/name' : 'standalone-gateway', + 'pod-template-hash' : '500000000c', + ], + 'name' : 'standalone-gateway-500000000c-6h0h6', + 'namespace' : 'myproject-test', + 'ownerReferences' : [[ + 'apiVersion' : 'apps/v1', + 'blockOwnerDeletion': true, + 'controller' : true, + 'kind' : 'ReplicaSet', + 'name' : 'standalone-gateway-500000000c', + 'uid' : '12345678-1234-1234-1234-700000000abcde', + ]], + 'resourceVersion' : '2865332161', + 'uid' : '12345678-1234-1234-1234-110000000abcde', + ], + 'spec' : [ + 'containers' : [[ + 'image' : 'image-registry.openshift.svc:1000/myproject-test/standalone-gateway:7b5e50e13fd78502967881f4970484ae08b76dc4', + 'imagePullPolicy' : 'IfNotPresent', + 'livenessProbe' : [ + 'failureThreshold': 3, + 'httpGet' : [ + 'path' : '/ready', + 'port' : 9901, + 'scheme': 'HTTP', + ], + 'periodSeconds' : 5, + 'successThreshold': 1, + 'timeoutSeconds' : 1, + ], + 'name' : 'standalone-gateway', + 'ports' : [[ + 'containerPort': 8000, + 'name' : 'http', + 'protocol' : 'TCP', + ]], + 'resources' : [ + 'limits' : [ + 'cpu' : '1', + 'memory': '512Mi', + ], + 'requests': [ + 'cpu' : '100m', + 'memory': '256Mi', + ], + ], + 'securityContext' : [ + 'allowPrivilegeEscalation': false, + 'capabilities' : [ + 'drop': ['ALL'], + ], + 'runAsNonRoot' : true, + 'runAsUser' : 1001270000, + ], + 'startupProbe' : [ + 'failureThreshold' : 30, + 'httpGet' : [ + 'path' : '/ready', + 'port' : 9901, + 'scheme': 'HTTP', + ], + 'initialDelaySeconds': 1, + 'periodSeconds' : 1, + 'successThreshold' : 1, + 'timeoutSeconds' : 1, + ], + 'terminationMessagePath' : '/dev/termination-log', + 'terminationMessagePolicy': 'File', + 'volumeMounts' : [[ + 'mountPath': '/var/run/secrets/kubernetes.io/secretaccount', + 'name' : 'kube-api-access-zxcvb', + 'readOnly' : true, + ]], + ]], + 'dnsPolicy' : 'ClusterFirst', + 'enableServiceLinks' : true, + 'imagePullSecrets' : [[ + 'name': 'default-dockercfg-xasdf', + ]], + 'nodeName' : 'ip-10.8.30.200.ec2.internal', + 'preemptionPolicy' : 'PreemptLowerPriority', + 'priority' : 0, + 'restartPolicy' : 'Always', + 'schedulerName' : 'default-scheduler', + 'securityContext' : [ + 'fsGroup' : 1001270000, + 'seLinuxOptions': [ + 'level': 's0:c36,c5', + ], + 'seccompProfile': [ + 'type': 'RuntimeDefault', + ], + ], + 'serviceAccount' : 'default', + 'serviceAccountName' : 'default', + 'terminationGracePeriodSeconds': 30, + 'tolerations' : [[ + 'effect' : 'NoExecute', + 'key' : 'node.kubernetes.io/not-ready', + 'operator' : 'Exists', + 'tolerationSeconds': 300, + ], [ + 'effect' : 'NoExecute', + 'key' : 'node.kubernetes.io/unreachable', + 'operator' : 'Exists', + 'tolerationSeconds': 300, + ], [ + 'effect' : 'NoSchedule', + 'key' : 'node.kubernetes.io/memory-pressure', + 'operator': 'Exists', + ]], + 'volumes' : [[ + 'name' : 'kube-api-access-zxcvb', + 'projected': [ + 'defaultMode': 420, + 'sources' : [[ + 'serviceAccountToken': [ + 'expirationSeconds': 3607, + 'path' : 'token', + ], + ], [ + 'configMap': [ + 'items': [[ + 'key' : 'ca.crt', + 'path': 'ca.crt', + ]], + 'name' : 'kube-some-ca.crt', + ], + ], [ + 'downwardAPI': [ + 'items': [[ + 'fieldRef': [ + 'apiVersion': 'v1', + 'fieldPath' : 'metadata.namespace', + ], + 'path' : 'namespace', + ]], + ], + ], [ + 'configMap': [ + 'items': [[ + 'key' : 'service-ca.crt', + 'path': 'service-ca.crt', + ]], + 'name' : 'openshift-some-ca.crt', + ], + ]], + ], + ]], + ], + 'status' : [ + 'conditions' : [[ + 'lastProbeTime' : null, + 'lastTransitionTime': '2024-05-25T13:41:18Z', + 'status' : 'True', + 'type' : 'Initialized', + ], [ + 'lastProbeTime' : null, + 'lastTransitionTime': '2024-05-25T13:43:54Z', + 'status' : 'True', + 'type' : 'Ready', + ], [ + 'lastProbeTime' : null, + 'lastTransitionTime': '2024-05-25T13:43:54Z', + 'status' : 'True', + 'type' : 'ContainersReady', + ], [ + 'lastProbeTime' : null, + 'lastTransitionTime': '2024-05-25T13:41:18Z', + 'status' : 'True', + 'type' : 'PodScheduled', + ]], + 'containerStatuses': [[ + 'containerID' : 'cri-o://14b000b41', + 'image' : 'image-registry.openshift.svc:1000/myproject-test/standalone-gateway:7b5e50e13fd78502967881f4970484ae08b76dc4', + 'imageID' : 'image-registry.openshift.svc:1000/myproject-test/standalone-gateway@sha256:c30003c', + 'lastState' : [], + 'name' : 'standalone-gateway', + 'ready' : true, + 'restartCount': 0, + 'started' : true, + 'state' : [ + 'running': [ + 'startedAt': '2024-05-25T13:43:50Z', + ], + ], + ]], + 'hostIP' : '10.8.30.200', + 'phase' : 'Running', + 'podIP' : '10.251.18.51', + 'podIPs' : [[ + 'ip': '10.251.18.51', + ]], + 'qosClass' : 'Burstable', + 'startTime' : '2024-05-25T13:41:18Z', + ], + ]], + 'kind' : 'PodList', + 'metadata' : [ + 'resourceVersion': '2886974735', + ], + ]], + 'v1/Secret' : [[ + 'apiVersion': 'v1', + 'data' : [ + 'rsaKey': 'REDACTED', + ], + 'kind' : 'Secret', + 'metadata' : [ + 'annotations' : [ + 'meta.helm.sh/release-name' : 'standalone-app', + 'meta.helm.sh/release-namespace': 'myproject-test', + ], + 'creationTimestamp': '2023-08-25T08:54:46Z', + 'labels' : [ + 'app.kubernetes.io/instance' : 'standalone-app', + 'app.kubernetes.io/managed-by': 'Helm', + 'app.kubernetes.io/name' : 'core', + 'app.kubernetes.io/version' : 'ea01234567', + 'helm.sh/chart' : 'core-0.1.0_ea01234567', + ], + 'name' : 'core-rsa-key-secret', + 'namespace' : 'myproject-test', + 'resourceVersion' : '2880969794', + 'uid' : '12345678-1234-1234-1234-300000000abcde', + ], + 'type' : 'Opaque', + ], [ + 'apiVersion': 'v1', + 'data' : [ + 'clientId' : 'REDACTED', + 'clientSecret': 'REDACTED', + ], + 'kind' : 'Secret', + 'metadata' : [ + 'annotations' : [ + 'meta.helm.sh/release-name' : 'standalone-app', + 'meta.helm.sh/release-namespace': 'myproject-test', + ], + 'creationTimestamp': '2023-08-25T08:54:46Z', + 'labels' : [ + 'app.kubernetes.io/instance' : 'standalone-app', + 'app.kubernetes.io/managed-by': 'Helm', + 'app.kubernetes.io/name' : 'core', + 'app.kubernetes.io/version' : 'ea01234567', + 'helm.sh/chart' : 'core-0.1.0_ea01234567', + ], + 'name' : 'core-security-exandradev-secret', + 'namespace' : 'myproject-test', + 'resourceVersion' : '2880969795', + 'uid' : '12345678-1234-1234-1234-500000000abcde', + ], + 'type' : 'Opaque', + ], [ + 'apiVersion': 'v1', + 'data' : [ + 'clientId' : 'REDACTED', + 'clientSecret': 'REDACTED', + ], + 'kind' : 'Secret', + 'metadata' : [ + 'annotations' : [ + 'meta.helm.sh/release-name' : 'standalone-app', + 'meta.helm.sh/release-namespace': 'myproject-test', + ], + 'creationTimestamp': '2023-05-16T15:41:54Z', + 'labels' : [ + 'app.kubernetes.io/instance' : 'standalone-app', + 'app.kubernetes.io/managed-by': 'Helm', + 'app.kubernetes.io/name' : 'core', + 'app.kubernetes.io/version' : 'ea01234567', + 'helm.sh/chart' : 'core-0.1.0_ea01234567', + ], + 'name' : 'core-security-unify-secret', + 'namespace' : 'myproject-test', + 'resourceVersion' : '2880969797', + 'uid' : '536ceb38-0457-4186-bd09-efe234b5fca1', + ], + 'type' : 'Opaque', + ]], + 'v1/Service' : [[ + 'apiVersion': 'v1', + 'kind' : 'Service', + 'metadata' : [ + 'annotations' : [ + 'meta.helm.sh/release-name' : 'standalone-app', + 'meta.helm.sh/release-namespace': 'myproject-test', + ], + 'creationTimestamp': '2022-12-19T09:44:33Z', + 'labels' : [ + 'app.kubernetes.io/instance' : 'standalone-app', + 'app.kubernetes.io/managed-by': 'Helm', + 'app.kubernetes.io/name' : 'core', + 'app.kubernetes.io/version' : 'ea01234567', + 'helm.sh/chart' : 'core-0.1.0_ea01234567', + ], + 'name' : 'core', + 'namespace' : 'myproject-test', + 'resourceVersion' : '2687980260', + 'uid' : '12345678-1234-1234-1234-123456789abcde', + ], + 'spec' : [ + 'clusterIP' : '100.30.20.100', + 'clusterIPs' : ['100.30.20.100'], + 'internalTrafficPolicy': 'Cluster', + 'ipFamilies' : ['IPv4'], + 'ipFamilyPolicy' : 'SingleStack', + 'ports' : [[ + 'name' : 'http', + 'port' : 8081, + 'protocol' : 'TCP', + 'targetPort': 8081, + ]], + 'selector' : [ + 'app.kubernetes.io/instance': 'standalone-app', + 'app.kubernetes.io/name' : 'core', + ], + 'sessionAffinity' : 'None', + 'type' : 'ClusterIP', + ], + 'status' : [ + 'loadBalancer': [], + ], + ], [ + 'apiVersion': 'v1', + 'kind' : 'Service', + 'metadata' : [ + 'annotations' : [ + 'meta.helm.sh/release-name' : 'standalone-app', + 'meta.helm.sh/release-namespace': 'myproject-test', + ], + 'creationTimestamp': '2023-05-08T09:40:33Z', + 'labels' : [ + 'app.kubernetes.io/instance' : 'standalone-app', + 'app.kubernetes.io/managed-by': 'Helm', + 'app.kubernetes.io/name' : 'standalone-gateway', + 'app.kubernetes.io/version' : '7b5e50e13fd78502967881f4970484ae08b76dc4', + 'helm.sh/chart' : 'standalone-gateway-0.1.0_7b5e50e13fd78502967881f4970484ae08b76d', + ], + 'name' : 'standalone-gateway', + 'namespace' : 'myproject-test', + 'resourceVersion' : '2497441712', + 'uid' : '12345678-1234-1234-1234-800000000abcde', + ], + 'spec' : [ + 'clusterIP' : '100.30.100.70', + 'clusterIPs' : ['100.30.100.70'], + 'internalTrafficPolicy': 'Cluster', + 'ipFamilies' : ['IPv4'], + 'ipFamilyPolicy' : 'SingleStack', + 'ports' : [[ + 'name' : 'http', + 'port' : 80, + 'protocol' : 'TCP', + 'targetPort': 8000, + ]], + 'selector' : [ + 'app.kubernetes.io/instance': 'standalone-app', + 'app.kubernetes.io/name' : 'standalone-gateway', + ], + 'sessionAffinity' : 'None', + 'type' : 'ClusterIP', + ], + 'status' : [ + 'loadBalancer': [], + ], + ]], + ], + 'status' : 'deployed', + ], + 'manifest' : 'REDACTED\n', + 'name' : 'standalone-app', + 'namespace': 'myproject-test', + 'version' : 43, + ] + } + static Map createProjectMetadata() { def file = new FixtureHelper().getResource("project-metadata.yml") return new Yaml().load(file.text) diff --git a/test/groovy/util/HelmStatusSpec.groovy b/test/groovy/util/HelmStatusSpec.groovy index f4edeffaa..de80f5de5 100644 --- a/test/groovy/util/HelmStatusSpec.groovy +++ b/test/groovy/util/HelmStatusSpec.groovy @@ -1,6 +1,6 @@ package org.ods.util -import groovy.json.JsonSlurperClassic + import org.ods.services.OpenShiftService import util.FixtureHelper import util.SpecHelper @@ -8,36 +8,36 @@ import util.SpecHelper class HelmStatusSpec extends SpecHelper { def "helm status parsing"() { given: - def file = new FixtureHelper().getResource("helmstatus.json") + def helmStatusJsonObj = FixtureHelper.createHelmCmdStatusMap() when: - def jsonObject = new JsonSlurperClassic().parseText(file.text) - def helmStatus = HelmStatus.fromJsonObject(jsonObject) + def helmStatus = HelmStatus.fromJsonObject(helmStatusJsonObj) def simpleStatusMap = helmStatus.toMap() def simpleStatusNoResources = simpleStatusMap.findAll { k,v -> k != "resourcesByKind"} def helmStatusResources = helmStatus.getResources() def deploymentResources = helmStatusResources.subMap([ OpenShiftService.DEPLOYMENT_KIND, OpenShiftService.DEPLOYMENTCONFIG_KIND]) + then: simpleStatusNoResources == [ name: 'standalone-app', version: '43', - namespace: 'guardians-test', + namespace: 'myproject-test', status: 'deployed', description: 'Upgrade complete', lastDeployed: '2024-03-04T15:21:09.34520527Z' ] + simpleStatusMap.resourcesByKind == [ - ConfigMap: ['core-appconfig-configmap'], - Deployment: ['core', 'standalone-gateway'], - Service: ['core', 'standalone-gateway'], - Cluster: ['edb-cluster'], - Secret: ['core-rsa-key-secret', 'core-security-exandradev-secret', 'core-security-unify-secret'] + 'Cluster': ['some-cluster'], + 'ConfigMap': ['core-appconfig-configmap'], + 'Deployment': ['core', 'standalone-gateway'], + 'Secret': ['core-rsa-key-secret', 'core-security-exandradev-secret', 'core-security-unify-secret'], + 'Service': ['core', 'standalone-gateway'], ] + deploymentResources == [ Deployment: [ 'core', 'standalone-gateway'] ] - } - // TODO add tests for fromJsonData() } diff --git a/test/groovy/vars/OdsComponentStageRolloutOpenShiftDeploymentSpec.groovy b/test/groovy/vars/OdsComponentStageRolloutOpenShiftDeploymentSpec.groovy index c974a6b51..d3a39bed2 100644 --- a/test/groovy/vars/OdsComponentStageRolloutOpenShiftDeploymentSpec.groovy +++ b/test/groovy/vars/OdsComponentStageRolloutOpenShiftDeploymentSpec.groovy @@ -1,21 +1,20 @@ package vars -import groovy.json.JsonSlurperClassic import org.codehaus.groovy.runtime.typehandling.GroovyCastException import org.ods.component.Context import org.ods.component.IContext -import org.ods.services.OpenShiftService import org.ods.services.JenkinsService +import org.ods.services.OpenShiftService import org.ods.services.ServiceRegistry - import org.ods.util.HelmStatus import org.ods.util.Logger import org.ods.util.PodData +import spock.lang.Shared +import spock.lang.Unroll import util.FixtureHelper import util.PipelineSteps import vars.test_helper.PipelineSpockTestBase -import spock.lang.* class OdsComponentStageRolloutOpenShiftDeploymentSpec extends PipelineSpockTestBase { @@ -155,17 +154,16 @@ class OdsComponentStageRolloutOpenShiftDeploymentSpec extends PipelineSpockTestB def "run successfully with Helm"() { given: def c = config + [ - projectId: 'guardians', + projectId: 'myproject', componentId: 'core', - environment: 'test', - targetProject: 'guardians-test', + environment: 'dev', + targetProject: 'myproject-dev', openshiftRolloutTimeoutRetries: 5, chartDir: 'chart'] - def helmJsonText = new FixtureHelper().getResource("helmstatus.json").text IContext context = new Context(null, c, logger) OpenShiftService openShiftService = Mock(OpenShiftService.class) - openShiftService.helmStatus('guardians-test', 'standalone-app') >> HelmStatus.fromJsonObject(new JsonSlurperClassic().parseText(helmJsonText)) + openShiftService.helmStatus('myproject-dev', 'backend-helm-monorepo') >> HelmStatus.fromJsonObject(FixtureHelper.createHelmCmdStatusMap()) // todo: verify that we did not want to ensure that build images are tagged here. // - the org.ods.component.Context.artifactUriStore is not initialized with c when created above! // - as a consequence the build artifacts are empty so no retagging happens here. @@ -205,7 +203,7 @@ class OdsComponentStageRolloutOpenShiftDeploymentSpec extends PipelineSpockTestB } } def deploymentInfo = script.call(context, [ - helmReleaseName: "standalone-app", + helmReleaseName: "backend-helm-monorepo", ]) then: @@ -218,7 +216,7 @@ class OdsComponentStageRolloutOpenShiftDeploymentSpec extends PipelineSpockTestB buildArtifacts.size() > 0 buildArtifacts.deployments['core-deploymentMean']['type'] == 'helm' - 1 * openShiftService.helmUpgrade('guardians-test', 'standalone-app', ['values.yaml'], ['registry':null, 'componentId':'core', 'global.registry':null, 'global.componentId':'core', 'imageNamespace':'guardians-test', 'imageTag':'cd3e9082', 'global.imageNamespace':'guardians-test', 'global.imageTag':'cd3e9082'], ['--install', '--atomic'], [], true) + 1 * openShiftService.helmUpgrade('myproject-dev', 'backend-helm-monorepo', ['values.yaml'], ['registry':null, 'componentId':'core', 'global.registry':null, 'global.componentId':'core', 'imageNamespace':'myproject-dev', 'imageTag':'cd3e9082', 'global.imageNamespace':'myproject-dev', 'global.imageTag':'cd3e9082'], ['--install', '--atomic'], [], true) } @Unroll diff --git a/test/resources/deployments-data-helm.json b/test/resources/deployments-data-helm.json deleted file mode 100644 index d90a2b15e..000000000 --- a/test/resources/deployments-data-helm.json +++ /dev/null @@ -1,102 +0,0 @@ -{ - "deployments": { - "backend-helm-monorepo-chart-component-a": { - "podName": "backend-helm-monorepo-chart-component-a-7d6659884-vhl2z", - "podNamespace": "kraemerh-test", - "podMetaDataCreationTimestamp": "2024-06-26T13:05:33Z", - "deploymentId": "backend-helm-monorepo-chart-component-a-7d6659884", - "podStatus": "Running", - "containers": { - "chart-component-a": "image-registry.openshift-image-registry.svc:5000/kraemerh-test/backend-helm-monorepo-component-a@sha256:5c6440e6179138842d75a9b4a0eb9dd283097839931119e79ee0da43656c8870" - } - }, - "backend-helm-monorepo-chart-component-a-deploymentMean": { - "type": "helm", - "selector": "app.kubernetes.io/instance=backend-helm-monorepo", - "chartDir": "chart", - "helmReleaseName": "backend-helm-monorepo", - "helmEnvBasedValuesFiles": [ - ], - "helmValuesFiles": [ - "values.yaml" - ], - "helmValues": { - "registry": "image-registry.openshift-image-registry.svc:5000", - "componentId": "backend-helm-monorepo" - }, - "helmDefaultFlags": [ - "--install", - "--atomic" - ], - "helmAdditionalFlags": [ - ], - "helmStatus": { - "name": "backend-helm-monorepo", - "version": "2", - "namespace": "kraemerh-dev", - "status": "deployed", - "description": "Upgrade complete", - "lastDeployed": "2024-06-26T12:59:51.270713404Z", - "resourcesByKind": { - "Deployment": [ - "backend-helm-monorepo-chart-component-a", - "backend-helm-monorepo-chart-component-b" - ], - "Service": [ - "backend-helm-monorepo-chart" - ] - } - }, - "repoId": "backend-helm-monorepo" - }, - "backend-helm-monorepo-chart-component-b": { - "podName": "backend-helm-monorepo-chart-component-b-87c7f548d-6hhcz", - "podNamespace": "kraemerh-test", - "podMetaDataCreationTimestamp": "2024-06-26T13:05:33Z", - "deploymentId": "backend-helm-monorepo-chart-component-b-87c7f548d", - "podStatus": "Running", - "containers": { - "chart-component-b": "image-registry.openshift-image-registry.svc:5000/kraemerh-test/backend-helm-monorepo-component-b@sha256:5e9ed6ba8458a9501a9d973398ff27e6e50411d3745cec0dac761e07378185a2" - } - }, - "backend-helm-monorepo-chart-component-b-deploymentMean": { - "type": "helm", - "selector": "app.kubernetes.io/instance=backend-helm-monorepo", - "chartDir": "chart", - "helmReleaseName": "backend-helm-monorepo", - "helmEnvBasedValuesFiles": [ - ], - "helmValuesFiles": [ - "values.yaml" - ], - "helmValues": { - "registry": "image-registry.openshift-image-registry.svc:5000", - "componentId": "backend-helm-monorepo" - }, - "helmDefaultFlags": [ - "--install", - "--atomic" - ], - "helmAdditionalFlags": [ - ], - "helmStatus": { - "name": "backend-helm-monorepo", - "version": "2", - "namespace": "kraemerh-dev", - "status": "deployed", - "description": "Upgrade complete", - "lastDeployed": "2024-06-26T12:59:51.270713404Z", - "resourcesByKind": { - "Deployment": [ - "backend-helm-monorepo-chart-component-a", - "backend-helm-monorepo-chart-component-b" - ], - "Service": [ - "backend-helm-monorepo-chart" - ] - } - }, - "repoId": "backend-helm-monorepo" - } - } -} diff --git a/test/resources/deployments-data-tailor.json b/test/resources/deployments-data-tailor.json deleted file mode 100644 index 3a511365b..000000000 --- a/test/resources/deployments-data-tailor.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "deployments": { - "backend-first": { - "containers": { - "backend-first": "backend-first@sha256:fc5fb63f4ac45e207a4a1ceba37534814489c16e82306cf46aca76627c0f5e1e" - } - }, - "backend-first-deploymentMean": { - "type": "tailor", - "selector": "app=kraemerh-backend-first", - "tailorSelectors": { - "selector": "app=kraemerh-backend-first", - "exclude": "bc,is" - }, - "tailorParamFile": "", - "tailorParams": [], - "tailorPreserve": [], - "tailorVerify": true - } - } -} diff --git a/test/resources/helmstatus.json b/test/resources/helmstatus.json index e81088a83..132a64c81 100644 --- a/test/resources/helmstatus.json +++ b/test/resources/helmstatus.json @@ -1,1422 +1,1450 @@ { - "name": "standalone-app", - "info": { - "first_deployed": "2022-12-19T09:44:32.164490076Z", - "last_deployed": "2024-03-04T15:21:09.34520527Z", - "deleted": "", - "description": "Upgrade complete", - "status": "deployed", - "resources": { - "v1/Cluster": [ - { - "apiVersion": "postgresql.k8s.enterprisedb.io/v1", - "kind": "Cluster", - "metadata": { - "annotations": { - "meta.helm.sh/release-name": "standalone-app", - "meta.helm.sh/release-namespace": "guardians-test" - }, - "creationTimestamp": "2023-07-04T13:18:28Z", - "generation": 3, - "labels": { - "app.kubernetes.io/instance": "standalone-app", - "app.kubernetes.io/managed-by": "Helm", - "app.kubernetes.io/name": "edb-cluster", - "app.kubernetes.io/version": "2f70aaae0e4facb06e6981c542b235e89fe156c3", - "helm.sh/chart": "edb-cluster-0.1.0_2f70aaae0e4facb06e6981c542b235e89fe156c3" - }, - "name": "edb-cluster", - "namespace": "guardians-test", - "resourceVersion": "2880969905", - "uid": "3ee355b9-0ebb-4997-9351-47b96def618f" - }, - "spec": { - "affinity": { - "podAntiAffinityType": "preferred" - }, - "bootstrap": { - "initdb": { - "database": "app", - "encoding": "UTF8", - "localeCType": "C", - "localeCollate": "C", - "owner": "app" - } - }, - "enableSuperuserAccess": true, - "failoverDelay": 0, - "imageName": "quay.io/enterprisedb/postgresql:15.3", - "instances": 1, - "logLevel": "info", - "maxSyncReplicas": 0, - "minSyncReplicas": 0, - "monitoring": { - "customQueriesConfigMap": [ + "name": "backend-helm-monorepo", + "info": { + "first_deployed": "2024-11-08T14:37:41.509186505Z", + "last_deployed": "2024-11-11T16:01:03.800460171Z", + "deleted": "", + "description": "Upgrade complete", + "status": "deployed", + "notes": "1. Get the application URL by running these commands:\n export POD_NAME=$(kubectl get pods --namespace myproject-dev -l \"app.kubernetes.io/name=chart,app.kubernetes.io/instance=backend-helm-monorepo\" -o jsonpath=\"{.items[0].metadata.name}\")\n export CONTAINER_PORT=$(kubectl get pod --namespace myproject-dev $POD_NAME -o jsonpath=\"{.spec.containers[0].ports[0].containerPort}\")\n echo \"Visit http://127.0.0.1:8080 to use your application\"\n kubectl --namespace myproject-dev port-forward $POD_NAME 8080:$CONTAINER_PORT\n", + "resources": { + "v1/Deployment": [ { - "key": "queries", - "name": "postgresql-operator-default-monitoring" - } - ], - "disableDefaultQueries": false, - "enablePodMonitor": false - }, - "postgresGID": 26, - "postgresUID": 26, - "postgresql": { - "parameters": { - "archive_mode": "on", - "archive_timeout": "5min", - "dynamic_shared_memory_type": "posix", - "log_destination": "csvlog", - "log_directory": "/controller/log", - "log_filename": "postgres", - "log_rotation_age": "0", - "log_rotation_size": "0", - "log_truncate_on_rotation": "false", - "logging_collector": "on", - "max_parallel_workers": "32", - "max_replication_slots": "32", - "max_worker_processes": "32", - "shared_memory_type": "mmap", - "shared_preload_libraries": "", - "ssl_max_protocol_version": "TLSv1.3", - "ssl_min_protocol_version": "TLSv1.3", - "wal_keep_size": "512MB", - "wal_receiver_timeout": "5s", - "wal_sender_timeout": "5s" - }, - "syncReplicaElectionConstraint": { - "enabled": false - } - }, - "primaryUpdateMethod": "restart", - "primaryUpdateStrategy": "unsupervised", - "replicationSlots": { - "highAvailability": { - "enabled": true, - "slotPrefix": "_cnp_" - }, - "updateInterval": 30 - }, - "resources": {}, - "smartShutdownTimeout": 180, - "startDelay": 30, - "stopDelay": 30, - "storage": { - "resizeInUseVolumes": true, - "size": "20Gi" - }, - "switchoverDelay": 40000000 - }, - "status": { - "certificates": { - "clientCASecret": "edb-cluster-ca", - "expirations": { - "edb-cluster-ca": "2024-08-29 14:02:22 +0000 UTC", - "edb-cluster-replication": "2024-08-29 14:02:22 +0000 UTC", - "edb-cluster-server": "2024-08-29 14:02:22 +0000 UTC" - }, - "replicationTLSSecret": "edb-cluster-replication", - "serverAltDNSNames": [ - "edb-cluster-rw", - "edb-cluster-rw.guardians-test", - "edb-cluster-rw.guardians-test.svc", - "edb-cluster-r", - "edb-cluster-r.guardians-test", - "edb-cluster-r.guardians-test.svc", - "edb-cluster-ro", - "edb-cluster-ro.guardians-test", - "edb-cluster-ro.guardians-test.svc" - ], - "serverCASecret": "edb-cluster-ca", - "serverTLSSecret": "edb-cluster-server" - }, - "cloudNativePostgresqlCommitHash": "949626034", - "cloudNativePostgresqlOperatorHash": "0737af0747dd2ac7040c7b21655bd8e5fa4e01028fe9d833891be071390e8785", - "conditions": [ - { - "lastTransitionTime": "2024-05-25T14:42:08Z", - "message": "Cluster is Ready", - "reason": "ClusterIsReady", - "status": "True", - "type": "Ready" - }, - { - "lastTransitionTime": "2023-07-04T13:19:38Z", - "message": "velero addon is disabled", - "reason": "Disabled", - "status": "False", - "type": "k8s.enterprisedb.io/velero" - }, - { - "lastTransitionTime": "2023-07-04T13:19:38Z", - "message": "external-backup-adapter addon is disabled", - "reason": "Disabled", - "status": "False", - "type": "k8s.enterprisedb.io/externalBackupAdapter" - }, - { - "lastTransitionTime": "2023-07-04T13:19:38Z", - "message": "external-backup-adapter-cluster addon is disabled", - "reason": "Disabled", - "status": "False", - "type": "k8s.enterprisedb.io/externalBackupAdapterCluster" - }, - { - "lastTransitionTime": "2023-07-04T13:19:40Z", - "message": "kasten addon is disabled", - "reason": "Disabled", - "status": "False", - "type": "k8s.enterprisedb.io/kasten" - }, - { - "lastTransitionTime": "2023-11-30T15:26:14Z", - "message": "Continuous archiving is working", - "reason": "ContinuousArchivingSuccess", - "status": "True", - "type": "ContinuousArchiving" - } - ], - "configMapResourceVersion": { - "metrics": { - "postgresql-operator-default-monitoring": "2880955105" - } - }, - "currentPrimary": "edb-cluster-1", - "currentPrimaryTimestamp": "2023-07-04T13:19:27.039619Z", - "healthyPVC": [ - "edb-cluster-1" - ], - "instanceNames": [ - "edb-cluster-1" - ], - "instances": 1, - "instancesReportedState": { - "edb-cluster-1": { - "isPrimary": true, - "timeLineID": 1 - } - }, - "instancesStatus": { - "healthy": [ - "edb-cluster-1" - ] - }, - "latestGeneratedNode": 1, - "licenseStatus": { - "licenseExpiration": "2999-12-31T00:00:00Z", - "licenseStatus": "Valid license (Boehringer Ingelheim (boehringer_ingelheim))", - "repositoryAccess": false, - "valid": true - }, - "managedRolesStatus": {}, - "phase": "Cluster in healthy state", - "poolerIntegrations": { - "pgBouncerIntegration": {} - }, - "pvcCount": 1, - "readService": "edb-cluster-r", - "readyInstances": 1, - "secretsResourceVersion": { - "applicationSecretVersion": "2880969810", - "clientCaSecretVersion": "2880969811", - "replicationSecretVersion": "2880969813", - "serverCaSecretVersion": "2880969811", - "serverSecretVersion": "2880969815", - "superuserSecretVersion": "2880969816" - }, - "targetPrimary": "edb-cluster-1", - "targetPrimaryTimestamp": "2023-07-04T13:18:29.516149Z", - "timelineID": 1, - "topology": { - "instances": { - "edb-cluster-1": {} - }, - "nodesUsed": 1, - "successfullyExtracted": true - }, - "writeService": "edb-cluster-rw" - } - } - ], - "v1/ConfigMap": [ - { - "apiVersion": "v1", - "data": { - "application.yaml": "REDACTED\n" - }, - "kind": "ConfigMap", - "metadata": { - "annotations": { - "meta.helm.sh/release-name": "standalone-app", - "meta.helm.sh/release-namespace": "guardians-test" - }, - "creationTimestamp": "2023-05-16T15:41:54Z", - "labels": { - "app.kubernetes.io/instance": "standalone-app", - "app.kubernetes.io/managed-by": "Helm", - "app.kubernetes.io/name": "core", - "app.kubernetes.io/version": "ea80478c0052a5d76c505d7e5f56556ac63ea982", - "helm.sh/chart": "core-0.1.0_ea80478c0052a5d76c505d7e5f56556ac63ea982" - }, - "name": "core-appconfig-configmap", - "namespace": "guardians-test", - "resourceVersion": "2880955101", - "uid": "612ad220-26de-44b6-bbf6-31ba57e456cb" - } - } - ], - "v1/Deployment": [ - { - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": { - "annotations": { - "deployment.kubernetes.io/revision": "36", - "meta.helm.sh/release-name": "standalone-app", - "meta.helm.sh/release-namespace": "guardians-test" - }, - "creationTimestamp": "2022-12-19T09:44:33Z", - "generation": 42, - "labels": { - "app.kubernetes.io/instance": "standalone-app", - "app.kubernetes.io/managed-by": "Helm", - "app.kubernetes.io/name": "core", - "app.kubernetes.io/version": "ea80478c0052a5d76c505d7e5f56556ac63ea982", - "helm.sh/chart": "core-0.1.0_ea80478c0052a5d76c505d7e5f56556ac63ea982" - }, - "name": "core", - "namespace": "guardians-test", - "resourceVersion": "2865328801", - "uid": "30d8bb5a-06ff-4705-97d4-51f7737a9bfe" - }, - "spec": { - "progressDeadlineSeconds": 600, - "replicas": 1, - "revisionHistoryLimit": 10, - "selector": { - "matchLabels": { - "app.kubernetes.io/instance": "standalone-app", - "app.kubernetes.io/name": "core" - } - }, - "strategy": { - "type": "Recreate" - }, - "template": { - "metadata": { - "annotations": { - "checksum/appconfig-configmap": "cf3b985c902671383801409e8a965ad17ab4ec10e5b6237b7486bd5d16dcec67", - "checksum/rsa-key-secret": "57a5e7abec60d7d4084c36a3d59aca39df32371cff531337d060401b9655d3e5", - "checksum/security-exandradev-secret": "07f38b38833d3701cfcd128a3429ba4defe5272e99164fbba4352a40ed94f99a", - "checksum/security-unify-secret": "2705b192feffaf260f2d5d524d6dfdefe6348d6faa95662f1485fbfd63af2a95" - }, - "creationTimestamp": null, - "labels": { - "app.kubernetes.io/instance": "standalone-app", - "app.kubernetes.io/name": "core" - } - }, - "spec": { - "containers": [ - { - "env": [ - { - "name": "EXANDRADEV_CLIENT_ID", - "valueFrom": { - "secretKeyRef": { - "key": "clientId", - "name": "core-security-exandradev-secret" - } - } - }, - { - "name": "EXANDRADEV_CLIENT_SECRET", - "valueFrom": { - "secretKeyRef": { - "key": "clientSecret", - "name": "core-security-exandradev-secret" - } - } - }, - { - "name": "UNIFY_CLIENT_ID", - "valueFrom": { - "secretKeyRef": { - "key": "clientId", - "name": "core-security-unify-secret" - } - } - }, - { - "name": "UNIFY_CLIENT_SECRET", - "valueFrom": { - "secretKeyRef": { - "key": "clientSecret", - "name": "core-security-unify-secret" - } - } - }, - { - "name": "DB_USERNAME", - "valueFrom": { - "secretKeyRef": { - "key": "username", - "name": "edb-cluster-app" - } - } - }, - { - "name": "DB_PASSWORD", - "valueFrom": { - "secretKeyRef": { - "key": "password", - "name": "edb-cluster-app" - } - } - } - ], - "image": "image-registry.openshift-image-registry.svc:5000/guardians-test/core-standalone:ea80478c0052a5d76c505d7e5f56556ac63ea982", - "imagePullPolicy": "IfNotPresent", - "livenessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/q/health/live", - "port": "http", - "scheme": "HTTP" - }, - "periodSeconds": 5, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "name": "core", - "ports": [ - { - "containerPort": 8081, - "name": "http", - "protocol": "TCP" - } - ], - "resources": { - "limits": { - "cpu": "1", - "memory": "512Mi" - }, - "requests": { - "cpu": "1", - "memory": "512Mi" - } - }, - "securityContext": {}, - "startupProbe": { - "failureThreshold": 20, - "httpGet": { - "path": "/q/health/started", - "port": "http", - "scheme": "HTTP" - }, - "periodSeconds": 3, - "successThreshold": 1, - "timeoutSeconds": 1 + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "annotations": { + "deployment.kubernetes.io/revision": "3", + "meta.helm.sh/release-name": "backend-helm-monorepo", + "meta.helm.sh/release-namespace": "myproject-dev" + }, + "creationTimestamp": "2024-11-08T14:37:41Z", + "generation": 3, + "labels": { + "app.kubernetes.io/instance": "backend-helm-monorepo", + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "chart", + "app.kubernetes.io/version": "1.16.0", + "example.com/component": "a", + "helm.sh/chart": "chart-0.1.0" + }, + "managedFields": [ + { + "apiVersion": "apps/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:meta.helm.sh/release-name": {}, + "f:meta.helm.sh/release-namespace": {} + }, + "f:labels": { + ".": {}, + "f:app.kubernetes.io/instance": {}, + "f:app.kubernetes.io/managed-by": {}, + "f:app.kubernetes.io/name": {}, + "f:app.kubernetes.io/version": {}, + "f:example.com/component": {}, + "f:helm.sh/chart": {} + } + }, + "f:spec": { + "f:progressDeadlineSeconds": {}, + "f:replicas": {}, + "f:revisionHistoryLimit": {}, + "f:selector": {}, + "f:strategy": { + "f:rollingUpdate": { + ".": {}, + "f:maxSurge": {}, + "f:maxUnavailable": {} + }, + "f:type": {} + }, + "f:template": { + "f:metadata": { + "f:labels": { + ".": {}, + "f:app.kubernetes.io/instance": {}, + "f:app.kubernetes.io/name": {}, + "f:example.com/component": {} + } + }, + "f:spec": { + "f:containers": { + "k:{\"name\":\"chart-component-a\"}": { + ".": {}, + "f:env": { + ".": {}, + "k:{\"name\":\"APP_LISTEN_PORT\"}": { + ".": {}, + "f:name": {}, + "f:value": {} + } + }, + "f:image": {}, + "f:imagePullPolicy": {}, + "f:name": {}, + "f:ports": { + ".": {}, + "k:{\"containerPort\":80,\"protocol\":\"TCP\"}": { + ".": {}, + "f:containerPort": {}, + "f:name": {}, + "f:protocol": {} + } + }, + "f:resources": {}, + "f:securityContext": {}, + "f:terminationMessagePath": {}, + "f:terminationMessagePolicy": {} + } + }, + "f:dnsPolicy": {}, + "f:restartPolicy": {}, + "f:schedulerName": {}, + "f:securityContext": {}, + "f:serviceAccount": {}, + "f:serviceAccountName": {}, + "f:terminationGracePeriodSeconds": {} + } + } + } + }, + "manager": "helm", + "operation": "Update", + "time": "2024-11-11T16:01:04Z" + }, + { + "apiVersion": "apps/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + "f:deployment.kubernetes.io/revision": {} + } + }, + "f:status": { + "f:availableReplicas": {}, + "f:conditions": { + ".": {}, + "k:{\"type\":\"Available\"}": { + ".": {}, + "f:lastTransitionTime": {}, + "f:lastUpdateTime": {}, + "f:message": {}, + "f:reason": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Progressing\"}": { + ".": {}, + "f:lastTransitionTime": {}, + "f:lastUpdateTime": {}, + "f:message": {}, + "f:reason": {}, + "f:status": {}, + "f:type": {} + } + }, + "f:observedGeneration": {}, + "f:readyReplicas": {}, + "f:replicas": {}, + "f:updatedReplicas": {} + } + }, + "manager": "kube-controller-manager", + "operation": "Update", + "subresource": "status", + "time": "2024-11-11T16:01:06Z" + } + ], + "name": "backend-helm-monorepo-chart-component-a", + "namespace": "myproject-dev", + "resourceVersion": "4223533956", + "uid": "5a3aa4dc-e990-4ef8-81f4-947262af7617" }, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/deployments/core/rsa", - "name": "exandra-rsa-key-volume", - "readOnly": true - }, - { - "mountPath": "/deployments/config", - "name": "exandra-config-volume", - "readOnly": true - } - ] - } - ], - "dnsPolicy": "ClusterFirst", - "restartPolicy": "Always", - "schedulerName": "default-scheduler", - "securityContext": {}, - "terminationGracePeriodSeconds": 30, - "volumes": [ - { - "name": "exandra-rsa-key-volume", - "secret": { - "defaultMode": 420, - "items": [ - { - "key": "rsaKey", - "path": "jwk.json" + "spec": { + "progressDeadlineSeconds": 600, + "replicas": 1, + "revisionHistoryLimit": 10, + "selector": { + "matchLabels": { + "app.kubernetes.io/instance": "backend-helm-monorepo", + "app.kubernetes.io/name": "chart", + "example.com/component": "a" + } + }, + "strategy": { + "rollingUpdate": { + "maxSurge": "25%", + "maxUnavailable": "25%" + }, + "type": "RollingUpdate" + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "app.kubernetes.io/instance": "backend-helm-monorepo", + "app.kubernetes.io/name": "chart", + "example.com/component": "a" + } + }, + "spec": { + "containers": [ + { + "env": [ + { + "name": "APP_LISTEN_PORT", + "value": "8080" + } + ], + "image": "image-registry.openshift.svc:1000/myproject-dev/helm-component-a:f6db0fd2", + "imagePullPolicy": "IfNotPresent", + "name": "chart-component-a", + "ports": [ + { + "containerPort": 80, + "name": "http", + "protocol": "TCP" + } + ], + "resources": {}, + "securityContext": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File" + } + ], + "dnsPolicy": "ClusterFirst", + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "serviceAccount": "default", + "serviceAccountName": "default", + "terminationGracePeriodSeconds": 30 + } } - ], - "secretName": "core-rsa-key-secret" - } - }, - { - "configMap": { - "defaultMode": 420, - "name": "core-appconfig-configmap" }, - "name": "exandra-config-volume" - } - ] - } - } - }, - "status": { - "availableReplicas": 1, - "conditions": [ - { - "lastTransitionTime": "2023-05-16T15:53:18Z", - "lastUpdateTime": "2024-03-04T15:21:26Z", - "message": "ReplicaSet \"core-75c8f865f7\" has successfully progressed.", - "reason": "NewReplicaSetAvailable", - "status": "True", - "type": "Progressing" - }, - { - "lastTransitionTime": "2024-05-25T13:43:04Z", - "lastUpdateTime": "2024-05-25T13:43:04Z", - "message": "Deployment has minimum availability.", - "reason": "MinimumReplicasAvailable", - "status": "True", - "type": "Available" - } - ], - "observedGeneration": 42, - "readyReplicas": 1, - "replicas": 1, - "updatedReplicas": 1 - } - }, - { - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": { - "annotations": { - "deployment.kubernetes.io/revision": "18", - "meta.helm.sh/release-name": "standalone-app", - "meta.helm.sh/release-namespace": "guardians-test" - }, - "creationTimestamp": "2023-05-08T09:40:33Z", - "generation": 18, - "labels": { - "app.kubernetes.io/instance": "standalone-app", - "app.kubernetes.io/managed-by": "Helm", - "app.kubernetes.io/name": "standalone-gateway", - "app.kubernetes.io/version": "7b5e50e13fd78502967881f4970484ae08b76dc4", - "helm.sh/chart": "standalone-gateway-0.1.0_7b5e50e13fd78502967881f4970484ae08b76d" - }, - "name": "standalone-gateway", - "namespace": "guardians-test", - "resourceVersion": "2865332166", - "uid": "e4d081ee-0e07-48f7-873a-50a167513b09" - }, - "spec": { - "progressDeadlineSeconds": 600, - "replicas": 1, - "revisionHistoryLimit": 10, - "selector": { - "matchLabels": { - "app.kubernetes.io/instance": "standalone-app", - "app.kubernetes.io/name": "standalone-gateway" - } - }, - "strategy": { - "type": "Recreate" - }, - "template": { - "metadata": { - "creationTimestamp": null, - "labels": { - "app.kubernetes.io/instance": "standalone-app", - "app.kubernetes.io/name": "standalone-gateway" - } - }, - "spec": { - "containers": [ - { - "image": "image-registry.openshift-image-registry.svc:5000/guardians-test/standalone-gateway:7b5e50e13fd78502967881f4970484ae08b76dc4", - "imagePullPolicy": "IfNotPresent", - "livenessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/ready", - "port": 9901, - "scheme": "HTTP" - }, - "periodSeconds": 5, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "name": "standalone-gateway", - "ports": [ - { - "containerPort": 8000, - "name": "http", - "protocol": "TCP" - } - ], - "resources": { - "limits": { - "cpu": "1", - "memory": "512Mi" - }, - "requests": { - "cpu": "100m", - "memory": "256Mi" - } - }, - "securityContext": {}, - "startupProbe": { - "failureThreshold": 30, - "httpGet": { - "path": "/ready", - "port": 9901, - "scheme": "HTTP" - }, - "initialDelaySeconds": 1, - "periodSeconds": 1, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File" - } - ], - "dnsPolicy": "ClusterFirst", - "restartPolicy": "Always", - "schedulerName": "default-scheduler", - "securityContext": {}, - "terminationGracePeriodSeconds": 30 - } - } - }, - "status": { - "availableReplicas": 1, - "conditions": [ - { - "lastTransitionTime": "2023-05-08T09:40:33Z", - "lastUpdateTime": "2023-12-20T16:48:17Z", - "message": "ReplicaSet \"standalone-gateway-5466b58d7c\" has successfully progressed.", - "reason": "NewReplicaSetAvailable", - "status": "True", - "type": "Progressing" - }, - { - "lastTransitionTime": "2024-05-25T13:43:54Z", - "lastUpdateTime": "2024-05-25T13:43:54Z", - "message": "Deployment has minimum availability.", - "reason": "MinimumReplicasAvailable", - "status": "True", - "type": "Available" - } - ], - "observedGeneration": 18, - "readyReplicas": 1, - "replicas": 1, - "updatedReplicas": 1 - } - } - ], - "v1/Pod(related)": [ - { - "apiVersion": "v1", - "items": [ - { - "apiVersion": "v1", - "kind": "Pod", - "metadata": { - "annotations": { - "checksum/appconfig-configmap": "cf3b985c902671383801409e8a965ad17ab4ec10e5b6237b7486bd5d16dcec67", - "checksum/rsa-key-secret": "57a5e7abec60d7d4084c36a3d59aca39df32371cff531337d060401b9655d3e5", - "checksum/security-exandradev-secret": "07f38b38833d3701cfcd128a3429ba4defe5272e99164fbba4352a40ed94f99a", - "checksum/security-unify-secret": "2705b192feffaf260f2d5d524d6dfdefe6348d6faa95662f1485fbfd63af2a95", - "k8s.ovn.org/pod-networks": "{\"default\":{\"ip_addresses\":[\"10.251.18.50/24\"],\"mac_address\":\"0a:58:0a:fb:12:32\",\"gateway_ips\":[\"10.251.18.1\"],\"routes\":[{\"dest\":\"10.251.0.0/16\",\"nextHop\":\"10.251.18.1\"},{\"dest\":\"172.30.0.0/16\",\"nextHop\":\"10.251.18.1\"},{\"dest\":\"100.64.0.0/16\",\"nextHop\":\"10.251.18.1\"}],\"ip_address\":\"10.251.18.50/24\",\"gateway_ip\":\"10.251.18.1\"}}", - "k8s.v1.cni.cncf.io/network-status": "[{\n \"name\": \"ovn-kubernetes\",\n \"interface\": \"eth0\",\n \"ips\": [\n \"10.251.18.50\"\n ],\n \"mac\": \"0a:58:0a:fb:12:32\",\n \"default\": true,\n \"dns\": {}\n}]", - "openshift.io/scc": "restricted-v2", - "seccomp.security.alpha.kubernetes.io/pod": "runtime/default" - }, - "creationTimestamp": "2024-05-25T13:41:18Z", - "generateName": "core-75c8f865f7-", - "labels": { - "app.kubernetes.io/instance": "standalone-app", - "app.kubernetes.io/name": "core", - "pod-template-hash": "75c8f865f7" + "status": { + "availableReplicas": 1, + "conditions": [ + { + "lastTransitionTime": "2024-11-08T14:37:44Z", + "lastUpdateTime": "2024-11-08T14:37:44Z", + "message": "Deployment has minimum availability.", + "reason": "MinimumReplicasAvailable", + "status": "True", + "type": "Available" + }, + { + "lastTransitionTime": "2024-11-08T14:37:41Z", + "lastUpdateTime": "2024-11-11T16:01:06Z", + "message": "ReplicaSet \"backend-helm-monorepo-chart-component-a-6467d6c55d\" has successfully progressed.", + "reason": "NewReplicaSetAvailable", + "status": "True", + "type": "Progressing" + } + ], + "observedGeneration": 3, + "readyReplicas": 1, + "replicas": 1, + "updatedReplicas": 1 + } }, - "name": "core-75c8f865f7-8tbcw", - "namespace": "guardians-test", - "ownerReferences": [ - { + { "apiVersion": "apps/v1", - "blockOwnerDeletion": true, - "controller": true, - "kind": "ReplicaSet", - "name": "core-75c8f865f7", - "uid": "f98ac8f8-8e79-4511-9124-7514fe409761" - } - ], - "resourceVersion": "2865328796", - "uid": "47adcd3a-b1f4-409a-9647-4e00e3c453cb" - }, - "spec": { - "containers": [ - { - "env": [ - { - "name": "EXANDRADEV_CLIENT_ID", - "valueFrom": { - "secretKeyRef": { - "key": "clientId", - "name": "core-security-exandradev-secret" - } - } - }, - { - "name": "EXANDRADEV_CLIENT_SECRET", - "valueFrom": { - "secretKeyRef": { - "key": "clientSecret", - "name": "core-security-exandradev-secret" - } - } - }, - { - "name": "UNIFY_CLIENT_ID", - "valueFrom": { - "secretKeyRef": { - "key": "clientId", - "name": "core-security-unify-secret" - } - } - }, - { - "name": "UNIFY_CLIENT_SECRET", - "valueFrom": { - "secretKeyRef": { - "key": "clientSecret", - "name": "core-security-unify-secret" - } - } - }, - { - "name": "DB_USERNAME", - "valueFrom": { - "secretKeyRef": { - "key": "username", - "name": "edb-cluster-app" - } - } - }, - { - "name": "DB_PASSWORD", - "valueFrom": { - "secretKeyRef": { - "key": "password", - "name": "edb-cluster-app" - } - } - } - ], - "image": "image-registry.openshift-image-registry.svc:5000/guardians-test/core-standalone:ea80478c0052a5d76c505d7e5f56556ac63ea982", - "imagePullPolicy": "IfNotPresent", - "livenessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/q/health/live", - "port": "http", - "scheme": "HTTP" - }, - "periodSeconds": 5, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "name": "core", - "ports": [ - { - "containerPort": 8081, - "name": "http", - "protocol": "TCP" - } - ], - "resources": { - "limits": { - "cpu": "1", - "memory": "512Mi" - }, - "requests": { - "cpu": "1", - "memory": "512Mi" - } - }, - "securityContext": { - "allowPrivilegeEscalation": false, - "capabilities": { - "drop": [ - "ALL" - ] - }, - "runAsNonRoot": true, - "runAsUser": 1001270000 - }, - "startupProbe": { - "failureThreshold": 20, - "httpGet": { - "path": "/q/health/started", - "port": "http", - "scheme": "HTTP" - }, - "periodSeconds": 3, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/deployments/core/rsa", - "name": "exandra-rsa-key-volume", - "readOnly": true - }, - { - "mountPath": "/deployments/config", - "name": "exandra-config-volume", - "readOnly": true - }, - { - "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", - "name": "kube-api-access-wdbhx", - "readOnly": true - } - ] - } - ], - "dnsPolicy": "ClusterFirst", - "enableServiceLinks": true, - "imagePullSecrets": [ - { - "name": "default-dockercfg-kn5ds" - } - ], - "nodeName": "ip-10-8-33-221.ec2.internal", - "preemptionPolicy": "PreemptLowerPriority", - "priority": 0, - "restartPolicy": "Always", - "schedulerName": "default-scheduler", - "securityContext": { - "fsGroup": 1001270000, - "seLinuxOptions": { - "level": "s0:c36,c5" - }, - "seccompProfile": { - "type": "RuntimeDefault" - } - }, - "serviceAccount": "default", - "serviceAccountName": "default", - "terminationGracePeriodSeconds": 30, - "tolerations": [ - { - "effect": "NoExecute", - "key": "node.kubernetes.io/not-ready", - "operator": "Exists", - "tolerationSeconds": 300 - }, - { - "effect": "NoExecute", - "key": "node.kubernetes.io/unreachable", - "operator": "Exists", - "tolerationSeconds": 300 - }, - { - "effect": "NoSchedule", - "key": "node.kubernetes.io/memory-pressure", - "operator": "Exists" - } - ], - "volumes": [ - { - "name": "exandra-rsa-key-volume", - "secret": { - "defaultMode": 420, - "items": [ - { - "key": "rsaKey", - "path": "jwk.json" - } - ], - "secretName": "core-rsa-key-secret" - } - }, - { - "configMap": { - "defaultMode": 420, - "name": "core-appconfig-configmap" - }, - "name": "exandra-config-volume" - }, - { - "name": "kube-api-access-wdbhx", - "projected": { - "defaultMode": 420, - "sources": [ - { - "serviceAccountToken": { - "expirationSeconds": 3607, - "path": "token" - } + "kind": "Deployment", + "metadata": { + "annotations": { + "deployment.kubernetes.io/revision": "3", + "meta.helm.sh/release-name": "backend-helm-monorepo", + "meta.helm.sh/release-namespace": "myproject-dev" }, - { - "configMap": { - "items": [ - { - "key": "ca.crt", - "path": "ca.crt" - } - ], - "name": "kube-root-ca.crt" - } + "creationTimestamp": "2024-11-08T14:37:41Z", + "generation": 3, + "labels": { + "app.kubernetes.io/instance": "backend-helm-monorepo", + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "chart", + "app.kubernetes.io/version": "1.16.0", + "example.com/component": "b", + "helm.sh/chart": "chart-0.1.0" }, - { - "downwardAPI": { - "items": [ - { - "fieldRef": { - "apiVersion": "v1", - "fieldPath": "metadata.namespace" + "managedFields": [ + { + "apiVersion": "apps/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:meta.helm.sh/release-name": {}, + "f:meta.helm.sh/release-namespace": {} + }, + "f:labels": { + ".": {}, + "f:app.kubernetes.io/instance": {}, + "f:app.kubernetes.io/managed-by": {}, + "f:app.kubernetes.io/name": {}, + "f:app.kubernetes.io/version": {}, + "f:example.com/component": {}, + "f:helm.sh/chart": {} + } + }, + "f:spec": { + "f:progressDeadlineSeconds": {}, + "f:replicas": {}, + "f:revisionHistoryLimit": {}, + "f:selector": {}, + "f:strategy": { + "f:rollingUpdate": { + ".": {}, + "f:maxSurge": {}, + "f:maxUnavailable": {} + }, + "f:type": {} + }, + "f:template": { + "f:metadata": { + "f:labels": { + ".": {}, + "f:app.kubernetes.io/instance": {}, + "f:app.kubernetes.io/name": {}, + "f:example.com/component": {} + } + }, + "f:spec": { + "f:containers": { + "k:{\"name\":\"chart-component-b\"}": { + ".": {}, + "f:env": { + ".": {}, + "k:{\"name\":\"APP_LISTEN_PORT\"}": { + ".": {}, + "f:name": {}, + "f:value": {} + } + }, + "f:image": {}, + "f:imagePullPolicy": {}, + "f:name": {}, + "f:ports": { + ".": {}, + "k:{\"containerPort\":80,\"protocol\":\"TCP\"}": { + ".": {}, + "f:containerPort": {}, + "f:name": {}, + "f:protocol": {} + } + }, + "f:resources": {}, + "f:securityContext": {}, + "f:terminationMessagePath": {}, + "f:terminationMessagePolicy": {} + } + }, + "f:dnsPolicy": {}, + "f:restartPolicy": {}, + "f:schedulerName": {}, + "f:securityContext": {}, + "f:serviceAccount": {}, + "f:serviceAccountName": {}, + "f:terminationGracePeriodSeconds": {} + } + } + } + }, + "manager": "helm", + "operation": "Update", + "time": "2024-11-11T16:01:04Z" + }, + { + "apiVersion": "apps/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + "f:deployment.kubernetes.io/revision": {} + } + }, + "f:status": { + "f:availableReplicas": {}, + "f:conditions": { + ".": {}, + "k:{\"type\":\"Available\"}": { + ".": {}, + "f:lastTransitionTime": {}, + "f:lastUpdateTime": {}, + "f:message": {}, + "f:reason": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Progressing\"}": { + ".": {}, + "f:lastTransitionTime": {}, + "f:lastUpdateTime": {}, + "f:message": {}, + "f:reason": {}, + "f:status": {}, + "f:type": {} + } + }, + "f:observedGeneration": {}, + "f:readyReplicas": {}, + "f:replicas": {}, + "f:updatedReplicas": {} + } }, - "path": "namespace" - } - ] - } + "manager": "kube-controller-manager", + "operation": "Update", + "subresource": "status", + "time": "2024-11-11T16:01:05Z" + } + ], + "name": "backend-helm-monorepo-chart-component-b", + "namespace": "myproject-dev", + "resourceVersion": "4223533904", + "uid": "76db28c3-dbb5-4626-9e1d-30d496662220" + }, + "spec": { + "progressDeadlineSeconds": 600, + "replicas": 1, + "revisionHistoryLimit": 10, + "selector": { + "matchLabels": { + "app.kubernetes.io/instance": "backend-helm-monorepo", + "app.kubernetes.io/name": "chart", + "example.com/component": "b" + } }, - { - "configMap": { - "items": [ - { - "key": "service-ca.crt", - "path": "service-ca.crt" - } - ], - "name": "openshift-service-ca.crt" - } + "strategy": { + "rollingUpdate": { + "maxSurge": "25%", + "maxUnavailable": "25%" + }, + "type": "RollingUpdate" + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "app.kubernetes.io/instance": "backend-helm-monorepo", + "app.kubernetes.io/name": "chart", + "example.com/component": "b" + } + }, + "spec": { + "containers": [ + { + "env": [ + { + "name": "APP_LISTEN_PORT", + "value": "8081" + } + ], + "image": "image-registry.openshift.svc:1000/myproject-dev/helm-component-b:f6db0fd2", + "imagePullPolicy": "IfNotPresent", + "name": "chart-component-b", + "ports": [ + { + "containerPort": 80, + "name": "http", + "protocol": "TCP" + } + ], + "resources": {}, + "securityContext": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File" + } + ], + "dnsPolicy": "ClusterFirst", + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "serviceAccount": "default", + "serviceAccountName": "default", + "terminationGracePeriodSeconds": 30 + } } - ] - } - } - ] - }, - "status": { - "conditions": [ - { - "lastProbeTime": null, - "lastTransitionTime": "2024-05-25T13:41:18Z", - "status": "True", - "type": "Initialized" - }, - { - "lastProbeTime": null, - "lastTransitionTime": "2024-05-25T13:43:03Z", - "status": "True", - "type": "Ready" - }, - { - "lastProbeTime": null, - "lastTransitionTime": "2024-05-25T13:43:03Z", - "status": "True", - "type": "ContainersReady" - }, - { - "lastProbeTime": null, - "lastTransitionTime": "2024-05-25T13:41:18Z", - "status": "True", - "type": "PodScheduled" - } - ], - "containerStatuses": [ - { - "containerID": "cri-o://475026e4b427c773eb5355446e4e9da989d91d7864b0e4f623c3f942885a11a2", - "image": "image-registry.openshift-image-registry.svc:5000/guardians-test/core-standalone:ea80478c0052a5d76c505d7e5f56556ac63ea982", - "imageID": "image-registry.openshift-image-registry.svc:5000/guardians-test/core-standalone@sha256:6a2290e522133866cafc4864c30ea6ba591de4238a865936e3e4f953d60c173a", - "lastState": {}, - "name": "core", - "ready": true, - "restartCount": 0, - "started": true, - "state": { - "running": { - "startedAt": "2024-05-25T13:42:52Z" - } - } - } - ], - "hostIP": "10.8.33.221", - "phase": "Running", - "podIP": "10.251.18.50", - "podIPs": [ - { - "ip": "10.251.18.50" - } - ], - "qosClass": "Guaranteed", - "startTime": "2024-05-25T13:41:18Z" - } - } - ], - "kind": "PodList", - "metadata": { - "resourceVersion": "2886974735" - } - }, - { - "apiVersion": "v1", - "items": [ - { - "apiVersion": "v1", - "kind": "Pod", - "metadata": { - "annotations": { - "k8s.ovn.org/pod-networks": "{\"default\":{\"ip_addresses\":[\"10.251.18.51/24\"],\"mac_address\":\"0a:58:0a:fb:12:33\",\"gateway_ips\":[\"10.251.18.1\"],\"routes\":[{\"dest\":\"10.251.0.0/16\",\"nextHop\":\"10.251.18.1\"},{\"dest\":\"172.30.0.0/16\",\"nextHop\":\"10.251.18.1\"},{\"dest\":\"100.64.0.0/16\",\"nextHop\":\"10.251.18.1\"}],\"ip_address\":\"10.251.18.51/24\",\"gateway_ip\":\"10.251.18.1\"}}", - "k8s.v1.cni.cncf.io/network-status": "[{\n \"name\": \"ovn-kubernetes\",\n \"interface\": \"eth0\",\n \"ips\": [\n \"10.251.18.51\"\n ],\n \"mac\": \"0a:58:0a:fb:12:33\",\n \"default\": true,\n \"dns\": {}\n}]", - "openshift.io/scc": "restricted-v2", - "seccomp.security.alpha.kubernetes.io/pod": "runtime/default" - }, - "creationTimestamp": "2024-05-25T13:41:18Z", - "generateName": "standalone-gateway-5466b58d7c-", - "labels": { - "app.kubernetes.io/instance": "standalone-app", - "app.kubernetes.io/name": "standalone-gateway", - "pod-template-hash": "5466b58d7c" - }, - "name": "standalone-gateway-5466b58d7c-6h87c", - "namespace": "guardians-test", - "ownerReferences": [ - { - "apiVersion": "apps/v1", - "blockOwnerDeletion": true, - "controller": true, - "kind": "ReplicaSet", - "name": "standalone-gateway-5466b58d7c", - "uid": "86bc0197-c804-47fe-97ce-34655e72347e" - } - ], - "resourceVersion": "2865332161", - "uid": "ad9109db-bb3e-4302-8b51-66e8a647b06d" - }, - "spec": { - "containers": [ - { - "image": "image-registry.openshift-image-registry.svc:5000/guardians-test/standalone-gateway:7b5e50e13fd78502967881f4970484ae08b76dc4", - "imagePullPolicy": "IfNotPresent", - "livenessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/ready", - "port": 9901, - "scheme": "HTTP" - }, - "periodSeconds": 5, - "successThreshold": 1, - "timeoutSeconds": 1 }, - "name": "standalone-gateway", - "ports": [ - { - "containerPort": 8000, - "name": "http", - "protocol": "TCP" - } + "status": { + "availableReplicas": 1, + "conditions": [ + { + "lastTransitionTime": "2024-11-08T14:37:43Z", + "lastUpdateTime": "2024-11-08T14:37:43Z", + "message": "Deployment has minimum availability.", + "reason": "MinimumReplicasAvailable", + "status": "True", + "type": "Available" + }, + { + "lastTransitionTime": "2024-11-08T14:37:41Z", + "lastUpdateTime": "2024-11-11T16:01:05Z", + "message": "ReplicaSet \"backend-helm-monorepo-chart-component-b-7d9d7456b\" has successfully progressed.", + "reason": "NewReplicaSetAvailable", + "status": "True", + "type": "Progressing" + } + ], + "observedGeneration": 3, + "readyReplicas": 1, + "replicas": 1, + "updatedReplicas": 1 + } + } + ], + "v1/Pod(related)": [ + { + "apiVersion": "v1", + "items": [ + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "annotations": { + "k8s.ovn.org/pod-networks": "{\"default\":{\"ip_addresses\":[\"10.251.44.130/24\"],\"mac_address\":\"0a:58:0a:fb:2c:82\",\"gateway_ips\":[\"10.251.44.1\"],\"routes\":[{\"dest\":\"10.200.0.0/16\",\"nextHop\":\"10.251.44.1\"},{\"dest\":\"170.30.0.0/16\",\"nextHop\":\"10.251.44.1\"},{\"dest\":\"100.64.0.0/16\",\"nextHop\":\"10.251.44.1\"}],\"ip_address\":\"10.251.44.130/24\",\"gateway_ip\":\"10.251.44.1\"}}", + "k8s.v1.cni.cncf.io/network-status": "[{\n \"name\": \"ovn-kubernetes\",\n \"interface\": \"eth0\",\n \"ips\": [\n \"10.251.44.130\"\n ],\n \"mac\": \"0a:58:0a:fb:2c:82\",\n \"default\": true,\n \"dns\": {}\n}]", + "kubernetes.io/limit-ranger": "LimitRanger plugin set: cpu, memory request for container chart-component-a; cpu, memory limit for container chart-component-a", + "openshift.io/scc": "restricted-v2", + "seccomp.security.alpha.kubernetes.io/pod": "runtime/default" + }, + "creationTimestamp": "2024-11-11T16:01:04Z", + "generateName": "backend-helm-monorepo-chart-component-a-6467d6c55d-", + "labels": { + "app.kubernetes.io/instance": "backend-helm-monorepo", + "app.kubernetes.io/name": "chart", + "example.com/component": "a", + "pod-template-hash": "6467d6c55d" + }, + "managedFields": [ + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + "f:k8s.ovn.org/pod-networks": {} + } + } + }, + "manager": "ip-10-8-32-56", + "operation": "Update", + "subresource": "status", + "time": "2024-11-11T16:01:04Z" + }, + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:generateName": {}, + "f:labels": { + ".": {}, + "f:app.kubernetes.io/instance": {}, + "f:app.kubernetes.io/name": {}, + "f:example.com/component": {}, + "f:pod-template-hash": {} + }, + "f:ownerReferences": { + ".": {}, + "k:{\"uid\":\"458e87be-1cd1-4522-bd68-4b92e0ff261d\"}": {} + } + }, + "f:spec": { + "f:containers": { + "k:{\"name\":\"chart-component-a\"}": { + ".": {}, + "f:env": { + ".": {}, + "k:{\"name\":\"APP_LISTEN_PORT\"}": { + ".": {}, + "f:name": {}, + "f:value": {} + } + }, + "f:image": {}, + "f:imagePullPolicy": {}, + "f:name": {}, + "f:ports": { + ".": {}, + "k:{\"containerPort\":80,\"protocol\":\"TCP\"}": { + ".": {}, + "f:containerPort": {}, + "f:name": {}, + "f:protocol": {} + } + }, + "f:resources": {}, + "f:securityContext": {}, + "f:terminationMessagePath": {}, + "f:terminationMessagePolicy": {} + } + }, + "f:dnsPolicy": {}, + "f:enableServiceLinks": {}, + "f:restartPolicy": {}, + "f:schedulerName": {}, + "f:securityContext": {}, + "f:serviceAccount": {}, + "f:serviceAccountName": {}, + "f:terminationGracePeriodSeconds": {} + } + }, + "manager": "kube-controller-manager", + "operation": "Update", + "time": "2024-11-11T16:01:04Z" + }, + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + "f:k8s.v1.cni.cncf.io/network-status": {} + } + } + }, + "manager": "multus-daemon", + "operation": "Update", + "subresource": "status", + "time": "2024-11-11T16:01:05Z" + }, + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + "f:conditions": { + "k:{\"type\":\"ContainersReady\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Initialized\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Ready\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + } + }, + "f:containerStatuses": {}, + "f:hostIP": {}, + "f:phase": {}, + "f:podIP": {}, + "f:podIPs": { + ".": {}, + "k:{\"ip\":\"10.251.44.130\"}": { + ".": {}, + "f:ip": {} + } + }, + "f:startTime": {} + } + }, + "manager": "kubelet", + "operation": "Update", + "subresource": "status", + "time": "2024-11-11T16:01:06Z" + } + ], + "name": "backend-helm-monorepo-chart-component-a-6467d6c55d-nvdmb", + "namespace": "myproject-dev", + "ownerReferences": [ + { + "apiVersion": "apps/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "ReplicaSet", + "name": "backend-helm-monorepo-chart-component-a-6467d6c55d", + "uid": "458e87be-1cd1-4522-bd68-4b92e0ff261d" + } + ], + "resourceVersion": "4223533939", + "uid": "0a376dc6-6949-4bca-b568-2c0d5033c687" + }, + "spec": { + "containers": [ + { + "env": [ + { + "name": "APP_LISTEN_PORT", + "value": "8080" + } + ], + "image": "image-registry.openshift.svc:1000/myproject-dev/helm-component-a:f6db0fd2", + "imagePullPolicy": "IfNotPresent", + "name": "chart-component-a", + "ports": [ + { + "containerPort": 80, + "name": "http", + "protocol": "TCP" + } + ], + "resources": { + "limits": { + "cpu": "1", + "memory": "1Gi" + }, + "requests": { + "cpu": "10m", + "memory": "10Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "runAsUser": 1005790000 + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/var/run/secrets/kubernetes.io/secretaccount", + "name": "kube-api-access-4xzkq", + "readOnly": true + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "enableServiceLinks": true, + "imagePullSecrets": [ + { + "name": "default-dockercfg-zxnp9" + } + ], + "nodeName": "ip-10-8-32-56.aws.mycompany.com", + "preemptionPolicy": "PreemptLowerPriority", + "priority": 0, + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": { + "fsGroup": 1005790000, + "seLinuxOptions": { + "level": "s0:c76,c45" + }, + "seccompProfile": { + "type": "RuntimeDefault" + } + }, + "serviceAccount": "default", + "serviceAccountName": "default", + "terminationGracePeriodSeconds": 30, + "tolerations": [ + { + "effect": "NoExecute", + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoExecute", + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoSchedule", + "key": "node.kubernetes.io/memory-pressure", + "operator": "Exists" + } + ], + "volumes": [ + { + "name": "kube-api-access-4xzkq", + "projected": { + "defaultMode": 420, + "sources": [ + { + "serviceAccountToken": { + "expirationSeconds": 3607, + "path": "token" + } + }, + { + "configMap": { + "items": [ + { + "key": "ca.crt", + "path": "ca.crt" + } + ], + "name": "kube-some-ca.crt" + } + }, + { + "downwardAPI": { + "items": [ + { + "fieldRef": { + "apiVersion": "v1", + "fieldPath": "metadata.namespace" + }, + "path": "namespace" + } + ] + } + }, + { + "configMap": { + "items": [ + { + "key": "service-ca.crt", + "path": "service-ca.crt" + } + ], + "name": "openshift-some-ca.crt" + } + } + ] + } + } + ] + }, + "status": { + "conditions": [ + { + "lastProbeTime": null, + "lastTransitionTime": "2024-11-11T16:01:04Z", + "status": "True", + "type": "Initialized" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2024-11-11T16:01:06Z", + "status": "True", + "type": "Ready" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2024-11-11T16:01:06Z", + "status": "True", + "type": "ContainersReady" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2024-11-11T16:01:04Z", + "status": "True", + "type": "PodScheduled" + } + ], + "containerStatuses": [ + { + "containerID": "cri-o://c1aa57c3c70ecea0feb2b56aa529a89b136819cee1f9eff69feb0861af729f08", + "image": "image-registry.openshift.svc:1000/myproject-dev/helm-component-a:f6db0fd2", + "imageID": "image-registry.openshift.svc:1000/myproject-dev/helm-component-a@sha256:4cb63016037873c1a7c03482512caebf6567180aaa18b92670ce527b32ac4e5b", + "lastState": {}, + "name": "chart-component-a", + "ready": true, + "restartCount": 0, + "started": true, + "state": { + "running": { + "startedAt": "2024-11-11T16:01:05Z" + } + } + } + ], + "hostIP": "10.8.32.56", + "phase": "Running", + "podIP": "10.251.44.130", + "podIPs": [ + { + "ip": "10.251.44.130" + } + ], + "qosClass": "Burstable", + "startTime": "2024-11-11T16:01:04Z" + } + } ], - "resources": { - "limits": { - "cpu": "1", - "memory": "512Mi" - }, - "requests": { - "cpu": "100m", - "memory": "256Mi" - } - }, - "securityContext": { - "allowPrivilegeEscalation": false, - "capabilities": { - "drop": [ - "ALL" - ] - }, - "runAsNonRoot": true, - "runAsUser": 1001270000 - }, - "startupProbe": { - "failureThreshold": 30, - "httpGet": { - "path": "/ready", - "port": 9901, - "scheme": "HTTP" - }, - "initialDelaySeconds": 1, - "periodSeconds": 1, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", - "name": "kube-api-access-6tc2p", - "readOnly": true - } - ] - } - ], - "dnsPolicy": "ClusterFirst", - "enableServiceLinks": true, - "imagePullSecrets": [ - { - "name": "default-dockercfg-kn5ds" - } - ], - "nodeName": "ip-10-8-33-221.ec2.internal", - "preemptionPolicy": "PreemptLowerPriority", - "priority": 0, - "restartPolicy": "Always", - "schedulerName": "default-scheduler", - "securityContext": { - "fsGroup": 1001270000, - "seLinuxOptions": { - "level": "s0:c36,c5" - }, - "seccompProfile": { - "type": "RuntimeDefault" - } + "kind": "PodList", + "metadata": { + "resourceVersion": "4223534074" + } }, - "serviceAccount": "default", - "serviceAccountName": "default", - "terminationGracePeriodSeconds": 30, - "tolerations": [ - { - "effect": "NoExecute", - "key": "node.kubernetes.io/not-ready", - "operator": "Exists", - "tolerationSeconds": 300 - }, - { - "effect": "NoExecute", - "key": "node.kubernetes.io/unreachable", - "operator": "Exists", - "tolerationSeconds": 300 - }, - { - "effect": "NoSchedule", - "key": "node.kubernetes.io/memory-pressure", - "operator": "Exists" - } - ], - "volumes": [ - { - "name": "kube-api-access-6tc2p", - "projected": { - "defaultMode": 420, - "sources": [ + { + "apiVersion": "v1", + "items": [ { - "serviceAccountToken": { - "expirationSeconds": 3607, - "path": "token" - } + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "annotations": { + "k8s.ovn.org/pod-networks": "{\"default\":{\"ip_addresses\":[\"10.251.46.148/24\"],\"mac_address\":\"0a:58:0a:fb:2e:94\",\"gateway_ips\":[\"10.251.46.1\"],\"routes\":[{\"dest\":\"10.200.0.0/16\",\"nextHop\":\"10.251.46.1\"},{\"dest\":\"170.30.0.0/16\",\"nextHop\":\"10.251.46.1\"},{\"dest\":\"100.64.0.0/16\",\"nextHop\":\"10.251.46.1\"}],\"ip_address\":\"10.251.46.148/24\",\"gateway_ip\":\"10.251.46.1\"}}", + "k8s.v1.cni.cncf.io/network-status": "[{\n \"name\": \"ovn-kubernetes\",\n \"interface\": \"eth0\",\n \"ips\": [\n \"10.251.46.148\"\n ],\n \"mac\": \"0a:58:0a:fb:2e:94\",\n \"default\": true,\n \"dns\": {}\n}]", + "kubernetes.io/limit-ranger": "LimitRanger plugin set: cpu, memory request for container chart-component-b; cpu, memory limit for container chart-component-b", + "openshift.io/scc": "restricted-v2", + "seccomp.security.alpha.kubernetes.io/pod": "runtime/default" + }, + "creationTimestamp": "2024-11-11T16:01:04Z", + "generateName": "backend-helm-monorepo-chart-component-b-7d9d7456b-", + "labels": { + "app.kubernetes.io/instance": "backend-helm-monorepo", + "app.kubernetes.io/name": "chart", + "example.com/component": "b", + "pod-template-hash": "7d9d7456b" + }, + "managedFields": [ + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + "f:k8s.ovn.org/pod-networks": {} + } + } + }, + "manager": "ip-10-8-34-157", + "operation": "Update", + "subresource": "status", + "time": "2024-11-11T16:01:04Z" + }, + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:generateName": {}, + "f:labels": { + ".": {}, + "f:app.kubernetes.io/instance": {}, + "f:app.kubernetes.io/name": {}, + "f:example.com/component": {}, + "f:pod-template-hash": {} + }, + "f:ownerReferences": { + ".": {}, + "k:{\"uid\":\"130fca64-a8b2-4ec1-8c3f-b2e98435813d\"}": {} + } + }, + "f:spec": { + "f:containers": { + "k:{\"name\":\"chart-component-b\"}": { + ".": {}, + "f:env": { + ".": {}, + "k:{\"name\":\"APP_LISTEN_PORT\"}": { + ".": {}, + "f:name": {}, + "f:value": {} + } + }, + "f:image": {}, + "f:imagePullPolicy": {}, + "f:name": {}, + "f:ports": { + ".": {}, + "k:{\"containerPort\":80,\"protocol\":\"TCP\"}": { + ".": {}, + "f:containerPort": {}, + "f:name": {}, + "f:protocol": {} + } + }, + "f:resources": {}, + "f:securityContext": {}, + "f:terminationMessagePath": {}, + "f:terminationMessagePolicy": {} + } + }, + "f:dnsPolicy": {}, + "f:enableServiceLinks": {}, + "f:restartPolicy": {}, + "f:schedulerName": {}, + "f:securityContext": {}, + "f:serviceAccount": {}, + "f:serviceAccountName": {}, + "f:terminationGracePeriodSeconds": {} + } + }, + "manager": "kube-controller-manager", + "operation": "Update", + "time": "2024-11-11T16:01:04Z" + }, + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + "f:conditions": { + "k:{\"type\":\"ContainersReady\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Initialized\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Ready\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + } + }, + "f:containerStatuses": {}, + "f:hostIP": {}, + "f:phase": {}, + "f:podIP": {}, + "f:podIPs": { + ".": {}, + "k:{\"ip\":\"10.251.46.148\"}": { + ".": {}, + "f:ip": {} + } + }, + "f:startTime": {} + } + }, + "manager": "kubelet", + "operation": "Update", + "subresource": "status", + "time": "2024-11-11T16:01:05Z" + }, + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + "f:k8s.v1.cni.cncf.io/network-status": {} + } + } + }, + "manager": "multus-daemon", + "operation": "Update", + "subresource": "status", + "time": "2024-11-11T16:01:05Z" + } + ], + "name": "backend-helm-monorepo-chart-component-b-7d9d7456b-bfqh8", + "namespace": "myproject-dev", + "ownerReferences": [ + { + "apiVersion": "apps/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "ReplicaSet", + "name": "backend-helm-monorepo-chart-component-b-7d9d7456b", + "uid": "130fca64-a8b2-4ec1-8c3f-b2e98435813d" + } + ], + "resourceVersion": "4223533889", + "uid": "fe85cef7-2553-4db9-8ce8-2f36c0243e6f" + }, + "spec": { + "containers": [ + { + "env": [ + { + "name": "APP_LISTEN_PORT", + "value": "8081" + } + ], + "image": "image-registry.openshift.svc:1000/myproject-dev/helm-component-b:f6db0fd2", + "imagePullPolicy": "IfNotPresent", + "name": "chart-component-b", + "ports": [ + { + "containerPort": 80, + "name": "http", + "protocol": "TCP" + } + ], + "resources": { + "limits": { + "cpu": "1", + "memory": "1Gi" + }, + "requests": { + "cpu": "10m", + "memory": "10Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "runAsUser": 1005790000 + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/var/run/secrets/kubernetes.io/secretaccount", + "name": "kube-api-access-2nkvg", + "readOnly": true + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "enableServiceLinks": true, + "imagePullSecrets": [ + { + "name": "default-dockercfg-zxnp9" + } + ], + "nodeName": "ip-10-8-34-157.aws.mycompany.com", + "preemptionPolicy": "PreemptLowerPriority", + "priority": 0, + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": { + "fsGroup": 1005790000, + "seLinuxOptions": { + "level": "s0:c76,c45" + }, + "seccompProfile": { + "type": "RuntimeDefault" + } + }, + "serviceAccount": "default", + "serviceAccountName": "default", + "terminationGracePeriodSeconds": 30, + "tolerations": [ + { + "effect": "NoExecute", + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoExecute", + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoSchedule", + "key": "node.kubernetes.io/memory-pressure", + "operator": "Exists" + } + ], + "volumes": [ + { + "name": "kube-api-access-2nkvg", + "projected": { + "defaultMode": 420, + "sources": [ + { + "serviceAccountToken": { + "expirationSeconds": 3607, + "path": "token" + } + }, + { + "configMap": { + "items": [ + { + "key": "ca.crt", + "path": "ca.crt" + } + ], + "name": "kube-some-ca.crt" + } + }, + { + "downwardAPI": { + "items": [ + { + "fieldRef": { + "apiVersion": "v1", + "fieldPath": "metadata.namespace" + }, + "path": "namespace" + } + ] + } + }, + { + "configMap": { + "items": [ + { + "key": "service-ca.crt", + "path": "service-ca.crt" + } + ], + "name": "openshift-some-ca.crt" + } + } + ] + } + } + ] + }, + "status": { + "conditions": [ + { + "lastProbeTime": null, + "lastTransitionTime": "2024-11-11T16:01:04Z", + "status": "True", + "type": "Initialized" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2024-11-11T16:01:05Z", + "status": "True", + "type": "Ready" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2024-11-11T16:01:05Z", + "status": "True", + "type": "ContainersReady" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2024-11-11T16:01:04Z", + "status": "True", + "type": "PodScheduled" + } + ], + "containerStatuses": [ + { + "containerID": "cri-o://1c4c3c777865130a3d5f6f73d9f8e09de0dcbb2d320895e7caa219cd16674895", + "image": "image-registry.openshift.svc:1000/myproject-dev/helm-component-b:f6db0fd2", + "imageID": "image-registry.openshift.svc:1000/myproject-dev/helm-component-b@sha256:90847a3958af6a7fef1763130f196418704a8e7b8ca44f83527291989111acbc", + "lastState": {}, + "name": "chart-component-b", + "ready": true, + "restartCount": 0, + "started": true, + "state": { + "running": { + "startedAt": "2024-11-11T16:01:05Z" + } + } + } + ], + "hostIP": "10.8.34.157", + "phase": "Running", + "podIP": "10.251.46.148", + "podIPs": [ + { + "ip": "10.251.46.148" + } + ], + "qosClass": "Burstable", + "startTime": "2024-11-11T16:01:04Z" + } + } + ], + "kind": "PodList", + "metadata": { + "resourceVersion": "4223534077" + } + } + ], + "v1/Service": [ + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "annotations": { + "meta.helm.sh/release-name": "backend-helm-monorepo", + "meta.helm.sh/release-namespace": "myproject-dev" }, - { - "configMap": { - "items": [ - { - "key": "ca.crt", - "path": "ca.crt" - } - ], - "name": "kube-root-ca.crt" - } + "creationTimestamp": "2024-11-08T14:37:41Z", + "labels": { + "app.kubernetes.io/instance": "backend-helm-monorepo", + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "chart", + "app.kubernetes.io/version": "1.16.0", + "helm.sh/chart": "chart-0.1.0" }, - { - "downwardAPI": { - "items": [ - { - "fieldRef": { - "apiVersion": "v1", - "fieldPath": "metadata.namespace" + "managedFields": [ + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:meta.helm.sh/release-name": {}, + "f:meta.helm.sh/release-namespace": {} + }, + "f:labels": { + ".": {}, + "f:app.kubernetes.io/instance": {}, + "f:app.kubernetes.io/managed-by": {}, + "f:app.kubernetes.io/name": {}, + "f:app.kubernetes.io/version": {}, + "f:helm.sh/chart": {} + } + }, + "f:spec": { + "f:internalTrafficPolicy": {}, + "f:ports": { + ".": {}, + "k:{\"port\":80,\"protocol\":\"TCP\"}": { + ".": {}, + "f:name": {}, + "f:port": {}, + "f:protocol": {}, + "f:targetPort": {} + } + }, + "f:selector": {}, + "f:sessionAffinity": {}, + "f:type": {} + } }, - "path": "namespace" - } - ] - } + "manager": "helm", + "operation": "Update", + "time": "2024-11-08T14:37:41Z" + } + ], + "name": "backend-helm-monorepo-chart", + "namespace": "myproject-dev", + "resourceVersion": "4213346120", + "uid": "db6377ce-870f-44c4-a96d-93a608e3e4c4" + }, + "spec": { + "clusterIP": "172.30.109.108", + "clusterIPs": [ + "172.30.109.108" + ], + "internalTrafficPolicy": "Cluster", + "ipFamilies": [ + "IPv4" + ], + "ipFamilyPolicy": "SingleStack", + "ports": [ + { + "name": "http", + "port": 80, + "protocol": "TCP", + "targetPort": "http" + } + ], + "selector": { + "app.kubernetes.io/instance": "backend-helm-monorepo", + "app.kubernetes.io/name": "chart" }, - { - "configMap": { - "items": [ - { - "key": "service-ca.crt", - "path": "service-ca.crt" - } - ], - "name": "openshift-service-ca.crt" - } - } - ] - } - } - ] - }, - "status": { - "conditions": [ - { - "lastProbeTime": null, - "lastTransitionTime": "2024-05-25T13:41:18Z", - "status": "True", - "type": "Initialized" - }, - { - "lastProbeTime": null, - "lastTransitionTime": "2024-05-25T13:43:54Z", - "status": "True", - "type": "Ready" - }, - { - "lastProbeTime": null, - "lastTransitionTime": "2024-05-25T13:43:54Z", - "status": "True", - "type": "ContainersReady" - }, - { - "lastProbeTime": null, - "lastTransitionTime": "2024-05-25T13:41:18Z", - "status": "True", - "type": "PodScheduled" - } - ], - "containerStatuses": [ - { - "containerID": "cri-o://14b477cac3d4639fd0dbb9c024ac5274c506f827034783ac5934df6d06da784c", - "image": "image-registry.openshift-image-registry.svc:5000/guardians-test/standalone-gateway:7b5e50e13fd78502967881f4970484ae08b76dc4", - "imageID": "image-registry.openshift-image-registry.svc:5000/guardians-test/standalone-gateway@sha256:c347bebe2497e1e7701bc57b34778e62ac072d223d121e438958e3ffdae4df1a", - "lastState": {}, - "name": "standalone-gateway", - "ready": true, - "restartCount": 0, - "started": true, - "state": { - "running": { - "startedAt": "2024-05-25T13:43:50Z" - } + "sessionAffinity": "None", + "type": "ClusterIP" + }, + "status": { + "loadBalancer": {} } - } - ], - "hostIP": "10.8.33.221", - "phase": "Running", - "podIP": "10.251.18.51", - "podIPs": [ - { - "ip": "10.251.18.51" - } - ], - "qosClass": "Burstable", - "startTime": "2024-05-25T13:41:18Z" - } - } - ], - "kind": "PodList", - "metadata": { - "resourceVersion": "2886974735" - } + } + ] } - ], - "v1/Secret": [ - { - "apiVersion": "v1", - "data": { - "rsaKey": "REDACTED" - }, - "kind": "Secret", - "metadata": { - "annotations": { - "meta.helm.sh/release-name": "standalone-app", - "meta.helm.sh/release-namespace": "guardians-test" - }, - "creationTimestamp": "2023-08-25T08:54:46Z", - "labels": { - "app.kubernetes.io/instance": "standalone-app", - "app.kubernetes.io/managed-by": "Helm", - "app.kubernetes.io/name": "core", - "app.kubernetes.io/version": "ea80478c0052a5d76c505d7e5f56556ac63ea982", - "helm.sh/chart": "core-0.1.0_ea80478c0052a5d76c505d7e5f56556ac63ea982" - }, - "name": "core-rsa-key-secret", - "namespace": "guardians-test", - "resourceVersion": "2880969794", - "uid": "45e94955-0ee4-41c6-ab53-582ceabd3274" - }, - "type": "Opaque" + }, + "config": { + "affinity": {}, + "autoscaling": { + "enabled": false, + "maxReplicas": 100, + "minReplicas": 1, + "targetCPUUtilizationPercentage": 80 }, - { - "apiVersion": "v1", - "data": { - "clientId": "REDACTED", - "clientSecret": "REDACTED" - }, - "kind": "Secret", - "metadata": { - "annotations": { - "meta.helm.sh/release-name": "standalone-app", - "meta.helm.sh/release-namespace": "guardians-test" - }, - "creationTimestamp": "2023-08-25T08:54:46Z", - "labels": { - "app.kubernetes.io/instance": "standalone-app", - "app.kubernetes.io/managed-by": "Helm", - "app.kubernetes.io/name": "core", - "app.kubernetes.io/version": "ea80478c0052a5d76c505d7e5f56556ac63ea982", - "helm.sh/chart": "core-0.1.0_ea80478c0052a5d76c505d7e5f56556ac63ea982" - }, - "name": "core-security-exandradev-secret", - "namespace": "guardians-test", - "resourceVersion": "2880969795", - "uid": "4db28b76-f90f-4431-a19d-73ef7e5d5ae7" - }, - "type": "Opaque" + "componentId": "backend-helm-monorepo", + "fullnameOverride": "", + "global": { + "componentId": "backend-helm-monorepo", + "imageNamespace": "myproject-dev", + "imageTag": "f6db0fd2", + "registry": "image-registry.openshift.svc:1000" }, - { - "apiVersion": "v1", - "data": { - "clientId": "REDACTED", - "clientSecret": "REDACTED" - }, - "kind": "Secret", - "metadata": { - "annotations": { - "meta.helm.sh/release-name": "standalone-app", - "meta.helm.sh/release-namespace": "guardians-test" - }, - "creationTimestamp": "2023-05-16T15:41:54Z", - "labels": { - "app.kubernetes.io/instance": "standalone-app", - "app.kubernetes.io/managed-by": "Helm", - "app.kubernetes.io/name": "core", - "app.kubernetes.io/version": "ea80478c0052a5d76c505d7e5f56556ac63ea982", - "helm.sh/chart": "core-0.1.0_ea80478c0052a5d76c505d7e5f56556ac63ea982" - }, - "name": "core-security-unify-secret", - "namespace": "guardians-test", - "resourceVersion": "2880969797", - "uid": "536ceb38-0457-4186-bd09-efe234b5fca1" - }, - "type": "Opaque" - } - ], - "v1/Service": [ - { - "apiVersion": "v1", - "kind": "Service", - "metadata": { - "annotations": { - "meta.helm.sh/release-name": "standalone-app", - "meta.helm.sh/release-namespace": "guardians-test" - }, - "creationTimestamp": "2022-12-19T09:44:33Z", - "labels": { - "app.kubernetes.io/instance": "standalone-app", - "app.kubernetes.io/managed-by": "Helm", - "app.kubernetes.io/name": "core", - "app.kubernetes.io/version": "ea80478c0052a5d76c505d7e5f56556ac63ea982", - "helm.sh/chart": "core-0.1.0_ea80478c0052a5d76c505d7e5f56556ac63ea982" - }, - "name": "core", - "namespace": "guardians-test", - "resourceVersion": "2687980260", - "uid": "287bf074-73da-4546-a43d-6d1f23c82365" - }, - "spec": { - "clusterIP": "172.30.21.158", - "clusterIPs": [ - "172.30.21.158" - ], - "internalTrafficPolicy": "Cluster", - "ipFamilies": [ - "IPv4" - ], - "ipFamilyPolicy": "SingleStack", - "ports": [ - { - "name": "http", - "port": 8081, - "protocol": "TCP", - "targetPort": 8081 - } + "imageNamespace": "myproject-dev", + "imagePullPolicy": "IfNotPresent", + "imagePullSecrets": [], + "imageTag": "f6db0fd2", + "ingress": { + "annotations": {}, + "className": "", + "enabled": false, + "hosts": [ + { + "host": "chart-example.local", + "paths": [ + { + "path": "/", + "pathType": "ImplementationSpecific" + } + ] + } ], - "selector": { - "app.kubernetes.io/instance": "standalone-app", - "app.kubernetes.io/name": "core" - }, - "sessionAffinity": "None", + "tls": [] + }, + "livenessProbe": { + "enabled": false + }, + "nameOverride": "", + "nodeSelector": {}, + "podAnnotations": {}, + "podSecurityContext": {}, + "readinessProbe": { + "enabled": false + }, + "registry": "image-registry.openshift.svc:1000", + "replicaCount": 1, + "resources": {}, + "securityContext": {}, + "service": { + "port": 80, "type": "ClusterIP" - }, - "status": { - "loadBalancer": {} - } }, + "serviceAccount": { + "annotations": {}, + "create": false, + "name": "" + }, + "tolerations": [] + }, + "manifest": "---\n# Source: chart/templates/service.yaml\napiVersion: v1\nkind: Service\nmetadata:\n name: backend-helm-monorepo-chart\n labels:\n helm.sh/chart: chart-0.1.0\n app.kubernetes.io/name: chart\n app.kubernetes.io/instance: backend-helm-monorepo\n app.kubernetes.io/version: \"1.16.0\"\n app.kubernetes.io/managed-by: Helm\nspec:\n type: ClusterIP\n ports:\n - port: 80\n targetPort: http\n protocol: TCP\n name: http\n selector:\n app.kubernetes.io/name: chart\n app.kubernetes.io/instance: backend-helm-monorepo\n---\n# Source: chart/templates/deployment_component-a.yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: \"backend-helm-monorepo-chart-component-a\"\n labels:\n helm.sh/chart: chart-0.1.0\n app.kubernetes.io/name: chart\n app.kubernetes.io/instance: backend-helm-monorepo\n app.kubernetes.io/version: \"1.16.0\"\n app.kubernetes.io/managed-by: Helm\n example.com/component: a\nspec:\n replicas: 1\n selector:\n matchLabels:\n app.kubernetes.io/name: chart\n app.kubernetes.io/instance: backend-helm-monorepo\n example.com/component: a\n template:\n metadata:\n labels:\n app.kubernetes.io/name: chart\n app.kubernetes.io/instance: backend-helm-monorepo\n example.com/component: a\n spec:\n serviceAccountName: default\n securityContext:\n {}\n containers:\n - name: \"chart-component-a\"\n securityContext:\n {}\n image: \"image-registry.openshift.svc:1000/myproject-dev/helm-component-a:f6db0fd2\"\n imagePullPolicy: IfNotPresent\n env:\n - name: APP_LISTEN_PORT\n value: \"8080\"\n ports:\n - name: http\n containerPort: 80\n protocol: TCP\n resources:\n {}\n---\n# Source: chart/templates/deployment_component-b.yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: \"backend-helm-monorepo-chart-component-b\"\n labels:\n helm.sh/chart: chart-0.1.0\n app.kubernetes.io/name: chart\n app.kubernetes.io/instance: backend-helm-monorepo\n app.kubernetes.io/version: \"1.16.0\"\n app.kubernetes.io/managed-by: Helm\n example.com/component: b\nspec:\n replicas: 1\n selector:\n matchLabels:\n app.kubernetes.io/name: chart\n app.kubernetes.io/instance: backend-helm-monorepo\n example.com/component: b\n template:\n metadata:\n labels:\n app.kubernetes.io/name: chart\n app.kubernetes.io/instance: backend-helm-monorepo\n example.com/component: b\n spec:\n serviceAccountName: default\n securityContext:\n {}\n containers:\n - name: \"chart-component-b\"\n securityContext:\n {}\n image: \"image-registry.openshift.svc:1000/myproject-dev/helm-component-b:f6db0fd2\"\n imagePullPolicy: IfNotPresent\n env:\n - name: APP_LISTEN_PORT\n value: \"8081\"\n ports:\n - name: http\n containerPort: 80\n protocol: TCP\n resources:\n {}\n", + "hooks": [ { - "apiVersion": "v1", - "kind": "Service", - "metadata": { - "annotations": { - "meta.helm.sh/release-name": "standalone-app", - "meta.helm.sh/release-namespace": "guardians-test" - }, - "creationTimestamp": "2023-05-08T09:40:33Z", - "labels": { - "app.kubernetes.io/instance": "standalone-app", - "app.kubernetes.io/managed-by": "Helm", - "app.kubernetes.io/name": "standalone-gateway", - "app.kubernetes.io/version": "7b5e50e13fd78502967881f4970484ae08b76dc4", - "helm.sh/chart": "standalone-gateway-0.1.0_7b5e50e13fd78502967881f4970484ae08b76d" - }, - "name": "standalone-gateway", - "namespace": "guardians-test", - "resourceVersion": "2497441712", - "uid": "87924b81-ff69-4676-b03c-31cb84fcea1e" - }, - "spec": { - "clusterIP": "172.30.187.73", - "clusterIPs": [ - "172.30.187.73" + "name": "backend-helm-monorepo-chart-test-connection", + "kind": "Pod", + "path": "chart/templates/tests/test-connection.yaml", + "manifest": "apiVersion: v1\nkind: Pod\nmetadata:\n name: \"backend-helm-monorepo-chart-test-connection\"\n labels:\n helm.sh/chart: chart-0.1.0\n app.kubernetes.io/name: chart\n app.kubernetes.io/instance: backend-helm-monorepo\n app.kubernetes.io/version: \"1.16.0\"\n app.kubernetes.io/managed-by: Helm\n annotations:\n \"helm.sh/hook\": test\nspec:\n containers:\n - name: wget\n image: busybox\n command: ['wget']\n args: ['backend-helm-monorepo-chart:80']\n restartPolicy: Never", + "events": [ + "test" ], - "internalTrafficPolicy": "Cluster", - "ipFamilies": [ - "IPv4" - ], - "ipFamilyPolicy": "SingleStack", - "ports": [ - { - "name": "http", - "port": 80, - "protocol": "TCP", - "targetPort": 8000 - } - ], - "selector": { - "app.kubernetes.io/instance": "standalone-app", - "app.kubernetes.io/name": "standalone-gateway" - }, - "sessionAffinity": "None", - "type": "ClusterIP" - }, - "status": { - "loadBalancer": {} - } + "last_run": { + "started_at": "", + "completed_at": "", + "phase": "" + } } - ] - } - }, - "manifest": "REDACTED\n", - "version": 43, - "namespace": "guardians-test" + ], + "version": 4, + "namespace": "myproject-dev" } From b8c64c61e1486f28a92344617e2676f52ff29b30 Mon Sep 17 00:00:00 2001 From: Carlos Antelo <180194797+anteloro-boeh@users.noreply.github.com> Date: Mon, 18 Nov 2024 09:26:09 +0100 Subject: [PATCH 30/36] chore: fixed CodeNarc reported blocking issues --- src/org/ods/util/HelmStatus.groovy | 4 ++-- src/org/ods/util/Logger.groovy | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/org/ods/util/HelmStatus.groovy b/src/org/ods/util/HelmStatus.groovy index 3975b89e3..34e7b529e 100644 --- a/src/org/ods/util/HelmStatus.groovy +++ b/src/org/ods/util/HelmStatus.groovy @@ -167,12 +167,12 @@ class HelmStatus { } - @NonCPS + @NonCPS String toString() { return toMap().toMapString() } - @NonCPS + @NonCPS private static Tuple2 extractResource( resourceJsonObject, String context) { def resourceObject = ensureMap(resourceJsonObject, context) diff --git a/src/org/ods/util/Logger.groovy b/src/org/ods/util/Logger.groovy index 25ac14d21..16c21df0c 100644 --- a/src/org/ods/util/Logger.groovy +++ b/src/org/ods/util/Logger.groovy @@ -1,8 +1,5 @@ package org.ods.util - -import groovy.json.JsonOutput - class Logger implements ILogger, Serializable { private final Object script From ff3d38ec122e2b1c56448872f8184f01aef9592b Mon Sep 17 00:00:00 2001 From: Carlos Antelo <180194797+anteloro-boeh@users.noreply.github.com> Date: Mon, 18 Nov 2024 10:59:27 +0100 Subject: [PATCH 31/36] chore: fixed more CodeNarc reported blocking issues --- .../component/HelmDeploymentStrategy.groovy | 93 ++++++++++--------- .../RolloutOpenShiftDeploymentStage.groovy | 13 +-- .../component/TailorDeploymentStrategy.groovy | 2 +- .../usecase/LeVADocumentUseCase.groovy | 18 ++-- .../util/HtmlFormatterUtil.groovy | 2 +- src/org/ods/util/HelmStatus.groovy | 2 - 6 files changed, 65 insertions(+), 65 deletions(-) diff --git a/src/org/ods/component/HelmDeploymentStrategy.groovy b/src/org/ods/component/HelmDeploymentStrategy.groovy index 25faa298d..ee21cb9c9 100644 --- a/src/org/ods/component/HelmDeploymentStrategy.groovy +++ b/src/org/ods/component/HelmDeploymentStrategy.groovy @@ -4,7 +4,10 @@ import groovy.transform.TypeChecked import groovy.transform.TypeCheckingMode import org.ods.services.JenkinsService import org.ods.services.OpenShiftService -import org.ods.util.* +import org.ods.util.HelmStatus +import org.ods.util.ILogger +import org.ods.util.IPipelineSteps +import org.ods.util.PodData class HelmDeploymentStrategy extends AbstractDeploymentStrategy { @@ -15,7 +18,7 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { private final ILogger logger // assigned in constructor - private IPipelineSteps steps + private final IPipelineSteps steps private final RolloutOpenShiftDeploymentOptions options @SuppressWarnings(['AbcMetric', 'CyclomaticComplexity', 'ParameterCount']) @@ -133,7 +136,7 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { def mergedHelmValuesFiles = [] options.helmEnvBasedValuesFiles = options.helmEnvBasedValuesFiles.collect { - it.replace('.env.',".${context.environment}.") + it.replace('.env.', ".${context.environment}.") } mergedHelmValuesFiles.addAll(options.helmValuesFiles) @@ -161,54 +164,54 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { Map> rolloutData = [:] Map> deploymentKinds = helmStatus.resourcesByKind - .findAll { kind, _ -> kind in DEPLOYMENT_KINDS } + .findAll { kind, res -> kind in DEPLOYMENT_KINDS } deploymentKinds.each { kind, names -> - names.each { name -> - context.addDeploymentToArtifactURIs("${name}-deploymentMean", - [ - type: 'helm', - selector: options.selector, - namespace: context.targetProject, - chartDir: options.chartDir, - helmReleaseName: options.helmReleaseName, - helmEnvBasedValuesFiles: options.helmEnvBasedValuesFiles, - helmValuesFiles: options.helmValuesFiles, - helmValues: options.helmValues, - helmDefaultFlags: options.helmDefaultFlags, - helmAdditionalFlags: options.helmAdditionalFlags, - helmStatus: helmStatus.toMap(), - ] - ) - def podDataContext = [ - "targetProject=${context.targetProject}", - "selector=${options.selector}", - "name=${name}", + names.each { name -> + context.addDeploymentToArtifactURIs("${name}-deploymentMean", + [ + type: 'helm', + selector: options.selector, + namespace: context.targetProject, + chartDir: options.chartDir, + helmReleaseName: options.helmReleaseName, + helmEnvBasedValuesFiles: options.helmEnvBasedValuesFiles, + helmValuesFiles: options.helmValuesFiles, + helmValues: options.helmValues, + helmDefaultFlags: options.helmDefaultFlags, + helmAdditionalFlags: options.helmAdditionalFlags, + helmStatus: helmStatus.toMap(), ] - def msgPodsNotFound = "Could not find 'running' pod(s) for '${podDataContext.join(', ')}'" - List podData = null - for (def i = 0; i < options.deployTimeoutRetries; i++) { - podData = openShift.checkForPodData(context.targetProject, options.selector, name) - if (podData) { - break - } - steps.echo("${msgPodsNotFound} - waiting") - steps.sleep(12) - } - if (!podData) { - throw new RuntimeException(msgPodsNotFound) + ) + def podDataContext = [ + "targetProject=${context.targetProject}", + "selector=${options.selector}", + "name=${name}", + ] + def msgPodsNotFound = "Could not find 'running' pod(s) for '${podDataContext.join(', ')}'" + List podData = null + for (def i = 0; i < options.deployTimeoutRetries; i++) { + podData = openShift.checkForPodData(context.targetProject, options.selector, name) + if (podData) { + break } - logger.debug("Helm podData for ${podDataContext.join(', ')}: ${podData}") - - rolloutData["${kind}/${name}"] = podData - // TODO: Once the orchestration pipeline can deal with multiple replicas, - // update this to store multiple pod artifacts. - // TODO: Potential conflict if resourceName is duplicated between - // Deployment and DeploymentConfig resource. - context.addDeploymentToArtifactURIs(name, podData[0]?.toMap()) + steps.echo("${msgPodsNotFound} - waiting") + steps.sleep(12) + } + if (!podData) { + throw new RuntimeException(msgPodsNotFound) } + logger.debug("Helm podData for ${podDataContext.join(', ')}: ${podData}") + + rolloutData["${kind}/${name}"] = podData + // TODO: Once the orchestration pipeline can deal with multiple replicas, + // update this to store multiple pod artifacts. + // TODO: Potential conflict if resourceName is duplicated between + // Deployment and DeploymentConfig resource. + context.addDeploymentToArtifactURIs(name, podData[0]?.toMap()) } - return rolloutData + } + return rolloutData } } diff --git a/src/org/ods/component/RolloutOpenShiftDeploymentStage.groovy b/src/org/ods/component/RolloutOpenShiftDeploymentStage.groovy index fae546629..e123e1cca 100644 --- a/src/org/ods/component/RolloutOpenShiftDeploymentStage.groovy +++ b/src/org/ods/component/RolloutOpenShiftDeploymentStage.groovy @@ -111,26 +111,27 @@ class RolloutOpenShiftDeploymentStage extends Stage { // We have to move everything here, // otherwise Jenkins will complain // about: "hudson.remoting.ProxyException: CpsCallableInvocation{methodName=fileExists, ..." - def isHelmDeployment = steps.fileExists(options.chartDir + '/Chart.yaml') + def isHelmDeployment = this.steps.fileExists(options.chartDir + '/Chart.yaml') logger.info("isHelmDeployment: ${isHelmDeployment}") - def isTailorDeployment = steps.fileExists(options.openshiftDir) + def isTailorDeployment = this.steps.fileExists(options.openshiftDir) if (isTailorDeployment && isHelmDeployment) { - steps.error("Must be either a Tailor based deployment or a Helm based deployment") + this.steps.error("Must be either a Tailor based deployment or a Helm based deployment") throw new IllegalStateException("Must be either a Tailor based deployment or a Helm based deployment") } // Use tailorDeployment in the following cases: // (1) We have an openshiftDir // (2) We do not have an openshiftDir but neither do we have an indication that it is Helm + def steps = new PipelineSteps(script) if (isTailorDeployment || (!isHelmDeployment && !isTailorDeployment)) { - deploymentStrategy = new TailorDeploymentStrategy(new PipelineSteps(script), context, config, openShift, jenkins, logger) + deploymentStrategy = new TailorDeploymentStrategy(steps, context, config, openShift, jenkins, logger) String resourcePath = 'org/ods/component/RolloutOpenShiftDeploymentStage.deprecate-tailor.GString.txt' - def msg =steps.libraryResource(resourcePath) + def msg = this.steps.libraryResource(resourcePath) logger.warn(msg) } if (isHelmDeployment) { - deploymentStrategy = new HelmDeploymentStrategy(new PipelineSteps(script), context, config, openShift, jenkins, logger) + deploymentStrategy = new HelmDeploymentStrategy(steps, context, config, openShift, jenkins, logger) } logger.info("deploymentStrategy: ${deploymentStrategy} -- ${deploymentStrategy.class.name}") return deploymentStrategy.deploy() diff --git a/src/org/ods/component/TailorDeploymentStrategy.groovy b/src/org/ods/component/TailorDeploymentStrategy.groovy index 600af3dc4..23cf47b89 100644 --- a/src/org/ods/component/TailorDeploymentStrategy.groovy +++ b/src/org/ods/component/TailorDeploymentStrategy.groovy @@ -17,7 +17,7 @@ class TailorDeploymentStrategy extends AbstractDeploymentStrategy { private final ILogger logger // assigned in constructor - private IPipelineSteps steps + private final IPipelineSteps steps private final RolloutOpenShiftDeploymentOptions options @SuppressWarnings(['AbcMetric', 'CyclomaticComplexity', 'ParameterCount']) diff --git a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy index cc0702698..8eef3a720 100644 --- a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy +++ b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy @@ -1,6 +1,5 @@ package org.ods.orchestration.usecase - import org.apache.commons.lang.StringUtils import org.ods.orchestration.util.HtmlFormatterUtil @@ -1043,13 +1042,13 @@ class LeVADocumentUseCase extends DocGenUseCase { //Add break space in url in manufacturer def p = ~'https?://\\S*' def m = it.metadata.supplier =~ p - StringBuffer sb = new StringBuffer(); + StringBuffer sb = new StringBuffer() while (m.find()) { - String url = m.group(); + String url = m.group() url = url.replaceAll('/+', '$0\u200B') - m.appendReplacement(sb, url); + m.appendReplacement(sb, url) } - m.appendTail(sb); + m.appendTail(sb) clone.printsupplier = sb.toString() return clone @@ -1138,7 +1137,7 @@ class LeVADocumentUseCase extends DocGenUseCase { Map deploymentMean = deployments.find { it.key.endsWith('-deploymentMean') }.value - if(deploymentMean.type == 'tailor') { + if (deploymentMean.type == 'tailor') { return formatTIRTailorDeploymentMean(deploymentMean) } @@ -1163,9 +1162,8 @@ class LeVADocumentUseCase extends DocGenUseCase { } private static Map> formatTIRBuilds(Map> builds) { - if (!builds) { - return [:] as Map> + return [:] } return builds.collectEntries { String buildKey, Map build -> @@ -1206,7 +1204,7 @@ class LeVADocumentUseCase extends DocGenUseCase { tailorParamFile: 'None', tailorParams : 'None', tailorPreserve : 'No extra resources specified to be preserved' - ].withDefault {'N/A'} + ].withDefault { 'N/A' } return mean.collectEntries { k, v -> [(k): v ?: defaultValues[k]] @@ -1596,7 +1594,7 @@ class LeVADocumentUseCase extends DocGenUseCase { [ id: it.id, description: it.metadata?.description, - tests: componentTestMapping[it.id]? componentTestMapping[it.id].join(", "): "None defined" + tests: componentTestMapping[it.id] ? componentTestMapping[it.id].join(", ") : "None defined" ] } } diff --git a/src/org/ods/orchestration/util/HtmlFormatterUtil.groovy b/src/org/ods/orchestration/util/HtmlFormatterUtil.groovy index 36e8a1d8c..659e30e2e 100644 --- a/src/org/ods/orchestration/util/HtmlFormatterUtil.groovy +++ b/src/org/ods/orchestration/util/HtmlFormatterUtil.groovy @@ -17,7 +17,6 @@ class HtmlFormatterUtil { } private static String itemsUl(items, String emptyDefault, String cssClass, Closure formatItem) { - if (!items) { return emptyDefault } @@ -26,4 +25,5 @@ class HtmlFormatterUtil { return "
                    ${body}
                  " } + } diff --git a/src/org/ods/util/HelmStatus.groovy b/src/org/ods/util/HelmStatus.groovy index 34e7b529e..e5eb3fdb2 100644 --- a/src/org/ods/util/HelmStatus.groovy +++ b/src/org/ods/util/HelmStatus.groovy @@ -166,7 +166,6 @@ class HelmStatus { return result } - @NonCPS String toString() { return toMap().toMapString() @@ -221,7 +220,6 @@ class HelmStatus { return obj as List } - @NonCPS private static Tuple2, List> collectMissingStringAttributes( Map jsonObject, List stringAttributes) { From d7959e05c69ee4d1ff5d489bf4754b2fab250c18 Mon Sep 17 00:00:00 2001 From: Carlos Antelo <180194797+anteloro-boeh@users.noreply.github.com> Date: Thu, 21 Nov 2024 19:21:47 +0100 Subject: [PATCH 32/36] fix: wrong per-env config files in helm upgrade commands --- CHANGELOG.md | 2 ++ src/org/ods/component/Context.groovy | 2 -- .../component/HelmDeploymentStrategy.groovy | 14 ++++++++----- .../component/TailorDeploymentStrategy.groovy | 2 +- .../usecase/LeVADocumentUseCase.groovy | 21 ++++++++++++++----- .../usecase/LeVADocumentUseCaseSpec.groovy | 10 ++++----- 6 files changed, 33 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 395a9bb55..a02aac56f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,10 @@ * Deprecation of vuln-type and scanners config in Trivy ([#1150](https://github.com/opendevstack/ods-jenkins-shared-library/issues/1150)) * Add preserve-digests cli option to skopeo copy command in CopyImageStage ([#1166](https://github.com/opendevstack/ods-jenkins-shared-library/issues/1166)) * Allow registry/image:tag sources in CopyImageStage instead of directly falling back to internal registry ([#1177](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1177)) +* Simplify successor management since now issue links are no longer inherited ([#1116](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1116)) * TIR - remove dynamic pod data, surface helm status and helm report tables reformatting ([#1143](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1143)) + ### Fixed * Fix Tailor deployment drifts for D, Q envs ([#1055](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1055)) diff --git a/src/org/ods/component/Context.groovy b/src/org/ods/component/Context.groovy index 30c382330..7640584d6 100644 --- a/src/org/ods/component/Context.groovy +++ b/src/org/ods/component/Context.groovy @@ -57,8 +57,6 @@ class Context implements IContext { } if (executedWithErrors) { - // FIXME this ExecutionException() constructor is protected, - // if there are errors, this exception will fail to be created due to constructor's access restrictions throw new ExecutionException("Jenkins serialization issue, when: context.assemble()") } } diff --git a/src/org/ods/component/HelmDeploymentStrategy.groovy b/src/org/ods/component/HelmDeploymentStrategy.groovy index ee21cb9c9..dda2f127e 100644 --- a/src/org/ods/component/HelmDeploymentStrategy.groovy +++ b/src/org/ods/component/HelmDeploymentStrategy.groovy @@ -16,9 +16,8 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { private final OpenShiftService openShift private final JenkinsService jenkins private final ILogger logger - + private IPipelineSteps steps // assigned in constructor - private final IPipelineSteps steps private final RolloutOpenShiftDeploymentOptions options @SuppressWarnings(['AbcMetric', 'CyclomaticComplexity', 'ParameterCount']) @@ -135,12 +134,12 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { // deal with dynamic value files - which are env dependent def mergedHelmValuesFiles = [] - options.helmEnvBasedValuesFiles = options.helmEnvBasedValuesFiles.collect { - it.replace('.env.', ".${context.environment}.") + def envConfigFiles = options.helmEnvBasedValuesFiles.collect {filenamePattern -> + filenamePattern.replace('.env.', ".${context.environment}.") } mergedHelmValuesFiles.addAll(options.helmValuesFiles) - mergedHelmValuesFiles.addAll(options.helmEnvBasedValuesFiles) + mergedHelmValuesFiles.addAll(envConfigFiles) openShift.helmUpgrade( targetProject, @@ -155,6 +154,11 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { } } + // rollout returns a map like this: + // [ + // 'DeploymentConfig/foo': [[podName: 'foo-a', ...], [podName: 'foo-b', ...]], + // 'Deployment/bar': [[podName: 'bar-a', ...]] + // ] @TypeChecked(TypeCheckingMode.SKIP) private Map> getRolloutData( HelmStatus helmStatus diff --git a/src/org/ods/component/TailorDeploymentStrategy.groovy b/src/org/ods/component/TailorDeploymentStrategy.groovy index 23cf47b89..cfe1d45e8 100644 --- a/src/org/ods/component/TailorDeploymentStrategy.groovy +++ b/src/org/ods/component/TailorDeploymentStrategy.groovy @@ -15,9 +15,9 @@ class TailorDeploymentStrategy extends AbstractDeploymentStrategy { private final OpenShiftService openShift private final JenkinsService jenkins private final ILogger logger + private final IPipelineSteps steps // assigned in constructor - private final IPipelineSteps steps private final RolloutOpenShiftDeploymentOptions options @SuppressWarnings(['AbcMetric', 'CyclomaticComplexity', 'ParameterCount']) diff --git a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy index 8eef3a720..3e93b7cfb 100644 --- a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy +++ b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy @@ -1074,6 +1074,13 @@ class LeVADocumentUseCase extends DocGenUseCase { String createTIR(Map repo, Map data) { logger.debug("createTIR - repo:${repo}, data:${data}") + // TODO Determine where to get the target environment in a straightforward way + def targetEnvironment = Project.getConcreteEnvironment( + project.buildParams.targetEnvironment, + project.buildParams.version.toString(), + project.versionedDevEnvsEnabled + ) + def documentType = DocumentType.TIR as String def installationTestData = data?.tests?.installation @@ -1092,7 +1099,7 @@ class LeVADocumentUseCase extends DocGenUseCase { def keysInDoc = ['Technology-' + repo.id] def docHistory = this.getAndStoreDocumentHistory(documentType + '-' + repo.id, keysInDoc) Map> deployments = repo.data.openshift.deployments ?: [:] - Map deploymentMean = prepareDeploymentMeanInfo(deployments) + Map deploymentMean = prepareDeploymentMeanInfo(deployments, targetEnvironment) def documentData = [ metadata : this.getDocumentMetadata(this.DOCUMENT_TYPE_NAMES[documentType], repo), @@ -1133,7 +1140,7 @@ class LeVADocumentUseCase extends DocGenUseCase { /* * Retrieves the deployment mean and fills empty values with proper defaults */ - protected static Map prepareDeploymentMeanInfo(Map> deployments) { + protected static Map prepareDeploymentMeanInfo(Map> deployments, String targetEnvironment) { Map deploymentMean = deployments.find { it.key.endsWith('-deploymentMean') }.value @@ -1141,7 +1148,7 @@ class LeVADocumentUseCase extends DocGenUseCase { return formatTIRTailorDeploymentMean(deploymentMean) } - return formatTIRHelmDeploymentMean(deploymentMean) + return formatTIRHelmDeploymentMean(deploymentMean, targetEnvironment) } /** @@ -1173,9 +1180,13 @@ class LeVADocumentUseCase extends DocGenUseCase { } as Map> } - protected static Map formatTIRHelmDeploymentMean(Map mean) { + protected static Map formatTIRHelmDeploymentMean(Map mean, String targetEnvironment) { Map formattedMean = [:] + def envConfigFiles = mean.helmEnvBasedValuesFiles?.collect{ filenamePattern -> + filenamePattern.replace('.env.', ".${targetEnvironment}.") + } + formattedMean.namespace = mean.namespace ?: 'None' formattedMean.type = mean.type formattedMean.descriptorPath = mean.chartDir ?: '.' @@ -1183,7 +1194,7 @@ class LeVADocumentUseCase extends DocGenUseCase { formattedMean.additionalCmdLineArgs = mean.helmAdditionalFlags.join(' ') ?: 'None' formattedMean.configParams = HtmlFormatterUtil.toUl(mean.helmValues as Map, 'None') formattedMean.configFiles = HtmlFormatterUtil.toUl(mean.helmValuesFiles as List, 'None') - formattedMean.envConfigFiles = HtmlFormatterUtil.toUl(mean.helmEnvBasedValuesFiles as List, 'None') + formattedMean.envConfigFiles = HtmlFormatterUtil.toUl(envConfigFiles as List, 'None') Map formattedStatus = [:] diff --git a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy index 8edd8bd1b..fcb84ccaa 100644 --- a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy +++ b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy @@ -1299,7 +1299,7 @@ class LeVADocumentUseCaseSpec extends SpecHelper { 'additionalCmdLineArgs': '--additional-flag-1 --additional-flag-2', 'configParams': '''
                  • registry: image-registry.openshift.svc:1000
                  • componentId: backend-helm-monorepo
                  ''', 'configFiles': '''
                  • values.yaml
                  ''', - 'envConfigFiles': '''
                  • values1.env.yaml
                  • values2.env.yaml
                  ''', + 'envConfigFiles': '''
                  • values1.dev.yaml
                  • values2.dev.yaml
                  ''', 'deploymentStatus': [ 'deployStatus': 'Successfully deployed', 'resultMessage': 'Upgrade complete', @@ -1330,7 +1330,7 @@ class LeVADocumentUseCaseSpec extends SpecHelper { ] when: - def deploymentMeanInfo = usecase.prepareDeploymentMeanInfo(deployments) + def deploymentMeanInfo = usecase.prepareDeploymentMeanInfo(deployments, 'dev') def deploymentInfo = usecase.prepareDeploymentInfo(deployments) then: @@ -1355,7 +1355,7 @@ class LeVADocumentUseCaseSpec extends SpecHelper { deploymentMean.helmStatus.resourcesByKind = [:] when: - def deploymentMeanInfo = usecase.prepareDeploymentMeanInfo(deployments) + def deploymentMeanInfo = usecase.prepareDeploymentMeanInfo(deployments, 'dev') then: deploymentMeanInfo.namespace == 'None' @@ -1400,7 +1400,7 @@ class LeVADocumentUseCaseSpec extends SpecHelper { ] when: - def deploymentMeanInfo = usecase.prepareDeploymentMeanInfo(deployments) + def deploymentMeanInfo = usecase.prepareDeploymentMeanInfo(deployments, 'dev') def deploymentInfo = usecase.prepareDeploymentInfo(deployments) then: @@ -1423,7 +1423,7 @@ class LeVADocumentUseCaseSpec extends SpecHelper { deploymentMean.tailorSelectors = [] when: - def deploymentMeanInfo = usecase.prepareDeploymentMeanInfo(deployments) + def deploymentMeanInfo = usecase.prepareDeploymentMeanInfo(deployments, 'dev') then: deploymentMeanInfo.tailorParamFile == 'None' From dea6ab435bdbe9e1906782253b02e98994913adb Mon Sep 17 00:00:00 2001 From: Carlos Antelo <180194797+anteloro-boeh@users.noreply.github.com> Date: Fri, 22 Nov 2024 10:11:39 +0100 Subject: [PATCH 33/36] chore: manual comparison with master branch in order to find regressions --- .../util/DeploymentDescriptor.groovy | 13 ---- src/org/ods/orchestration/util/Project.groovy | 71 +++++++++++++++++++ src/org/ods/services/OpenShiftService.groovy | 15 ++-- src/org/ods/util/PodData.groovy | 12 ++++ .../org/ods/core/test/LoggerStub.groovy | 32 --------- .../usecase/LeVADocumentUseCaseSpec.groovy | 2 - .../ods/orchestration/util/ProjectSpec.groovy | 23 +++--- .../ods/services/OpenShiftServiceSpec.groovy | 4 -- 8 files changed, 101 insertions(+), 71 deletions(-) diff --git a/src/org/ods/orchestration/util/DeploymentDescriptor.groovy b/src/org/ods/orchestration/util/DeploymentDescriptor.groovy index 32170204d..76608a5ba 100644 --- a/src/org/ods/orchestration/util/DeploymentDescriptor.groovy +++ b/src/org/ods/orchestration/util/DeploymentDescriptor.groovy @@ -17,19 +17,6 @@ class DeploymentDescriptor { this.createdByBuild = createdByBuild ?: '' } - /** - * This function takes a map of deployments and returns a stripped down version of it. - * - * deploymentMean information is moved from a key of the form '${resource-name}-deploymentMean' into the Map for - * key '${resource-name}' under key 'deploymentMean'. - * - * For each deployment, it processes its containers and modifies the image name based on certain conditions. - * If the first part of the image name is in the EXCLUDE_NAMESPACES_FROM_IMPORT list, it keeps the full image name. - * Otherwise, it only keeps the last part of the image name. - * - * @param deployments The original deployments map whose values are Maps themselves - * @return A new stripped down deployments map. - */ static Map stripDeployments(Map deployments) { def strippedDownDeployments = [:] def deploymentMeanPostfix = 'deploymentMean' diff --git a/src/org/ods/orchestration/util/Project.groovy b/src/org/ods/orchestration/util/Project.groovy index 40e7a0a3f..5b82e8bd0 100644 --- a/src/org/ods/orchestration/util/Project.groovy +++ b/src/org/ods/orchestration/util/Project.groovy @@ -1301,6 +1301,8 @@ class Project { logger.info("loadJiraData: Found a predecessor project version with ID '${previousVersionId}'. Loading its data.") def savedDataFromOldVersion = this.loadSavedJiraData(previousVersionId) def mergedData = this.mergeJiraData(savedDataFromOldVersion, newData) + mergedData = this.overrideDeltaDocgenDataLinks(mergedData, newData) + mergedData = this.removeObsoleteIssuesFromComponents(mergedData) result << this.addKeyAndVersionToComponentsWithout(mergedData) result.previousVersion = previousVersionId } else { @@ -1349,6 +1351,75 @@ class Project { } } + /** + * It uses the data from the deltadocgen of the latest version as a source of truth in terms of links. + * If an issue appears in the deltadocgen report, we use all its data adding the expanded predecessors. + * If an issue appears as a link in the old data but in the deltadocgen report doesn't show the same link in the other + * direction, then we remove that link. + * + * @param mergedData resulting data of merging last release json report and deltadocgen + * @param deltaDocgenData result of deltadocgen endpoint for the latest version + * @return the merged data with the proper links + */ + protected Map overrideDeltaDocgenDataLinks(Map mergedData, Map deltaDocgenData) { + mergedData.findAll { JiraDataItem.REGULAR_ISSUE_TYPES.contains(it.key) }.each { issueType, issues -> + issues.values().each { Map issueToUpdate -> + if(deltaDocgenData[issueType] && deltaDocgenData[issueType][issueToUpdate.key]) { + def resultData = deltaDocgenData[issueType][issueToUpdate.key] + resultData << [expandedPredecessors: mergedData[issueType][issueToUpdate.key]['expandedPredecessors']] + mergedData[issueType][issueToUpdate.key] = resultData + } else { + mergedData[issueType][issueToUpdate.key].findAll { JiraDataItem.REGULAR_ISSUE_TYPES.contains(it.key) }.each { relatedIssueType, relatedIssues -> + def relatedIssuesToRemove = findRelatedIssuesToRemove(relatedIssues, deltaDocgenData, relatedIssueType, issueType, issueToUpdate) + mergedData[issueType][issueToUpdate.key][relatedIssueType].removeAll { relatedIssuesToRemove.contains(it) } + } + } + } + } + return mergedData + } + + /** + * The method checks each issue in the 'relatedIssues' list against the related issues in the 'deltaDocgenData'. + * If the issueToUpdate key does not appear in the deltaDocgenData as a related issue but the deltaDocGen has some + * issues related for that issue type, the issue is added to the list of issues to be removed. + * + * @param relatedIssues A list of related issues to be examined. + * @param deltaDocgenData A map containing data from the deltaDocGen + * @param relatedIssueType The type of the related issue. + * @param issueType The type of the issue. + * @param issueToUpdate A map containing the issue to be updated. + * + * @return A list of related issues that need to be removed. + */ + protected static List findRelatedIssuesToRemove(List relatedIssues, Map deltaDocgenData, String relatedIssueType, String issueType, Map issueToUpdate) { + def relatedIssuesToRemove = [] + relatedIssues.each { + if (deltaDocgenData[relatedIssueType][it] && deltaDocgenData[relatedIssueType][it][issueType] && !deltaDocgenData[relatedIssueType][it][issueType].contains(issueToUpdate.key)) { + relatedIssuesToRemove.add(it) + } + } + return relatedIssuesToRemove + } + + /** + * It removes any issue in the components map that does not appear under the technology map it should belong + * + * @param mergedData resulting data of merging last release json report and deltadocgen + * @return the merged data with the proper issues in the components map + */ + protected Map removeObsoleteIssuesFromComponents(Map mergedData) { + mergedData[JiraDataItem.TYPE_COMPONENTS].collectEntries { component, componentIssues -> + JiraDataItem.REGULAR_ISSUE_TYPES.each { issueType -> + if(componentIssues[issueType]) { + componentIssues[issueType].removeAll { !mergedData[issueType].keySet().contains(it) } + } + } + [(component): componentIssues] + } + return mergedData + } + protected Map loadJiraDataBugs(Map tests, String versionName = null) { if (!this.jiraUseCase) return [:] if (!this.jiraUseCase.jira) return [:] diff --git a/src/org/ods/services/OpenShiftService.groovy b/src/org/ods/services/OpenShiftService.groovy index 17f0af1d6..5a2253664 100644 --- a/src/org/ods/services/OpenShiftService.groovy +++ b/src/org/ods/services/OpenShiftService.groovy @@ -448,7 +448,7 @@ class OpenShiftService { ) } - // Returns data about the pods (replicas) of the deployment. + // Returns data about the pods (replicas) of the deployment. // If not all pods are running until the retries are exhausted, // an exception is thrown. List getPodDataForDeployment(String project, String kind, String podManagerName, int retries) { @@ -468,7 +468,7 @@ class OpenShiftService { throw new RuntimeException("Could not find 'running' pod(s) with label '${label}'") } - // getResourcesForComponent returns a map in which each kind is mapped to a list of resources names. + // getResourcesForComponent returns a map in which each kind is mapped to a list of resources. Map> getResourcesForComponent(String project, List kinds, String selector) { def items = steps.sh( script: """oc -n ${project} get ${kinds.join(',')} \ @@ -1275,7 +1275,7 @@ class OpenShiftService { ) } - @SuppressWarnings(['CyclomaticComplexity', 'AbcMetric', 'LineLength']) + @SuppressWarnings(['CyclomaticComplexity', 'AbcMetric']) @TypeChecked(TypeCheckingMode.SKIP) private List extractPodData(Map podJson) { List pods = [] @@ -1289,17 +1289,16 @@ class OpenShiftService { if (podOCData.metadata?.generateName) { pod.deploymentId = podOCData.metadata.generateName - ~/-$/ // Trim dash suffix } + pod.podNode = podOCData.spec?.nodeName ?: 'N/A' + pod.podIp = podOCData.status?.podIP ?: 'N/A' pod.podStatus = podOCData.status?.phase ?: 'N/A' + pod.podStartupTimeStamp = podOCData.status?.startTime ?: 'N/A' pod.containers = [:] // We need to get the image SHA from the containerStatuses, and not // from the pod spec because the pod spec image field is optional // and may not contain an image SHA, but e.g. a tag, depending on // the pod manager (e.g. ReplicationController, ReplicaSet). See - // Comment above from version 1.19 no longer available online - // Cluster currently run on 1.27 - // docs for 1.27: https://v1-27.docs.kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/ - // latest: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#Container - // example of an imageID: "image-registry.openshift-image-registry.svc:5000/guardians-test/core-standalone@sha256:6a2290e522133866cafc4864c30ea6ba591de4238a865936e3e4f953d60c173a" + // https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#container-v1-core. podOCData.spec?.containers?.each { container -> podOCData.status?.containerStatuses?.each { containerStatus -> if (containerStatus.name == container.name) { diff --git a/src/org/ods/util/PodData.groovy b/src/org/ods/util/PodData.groovy index 87edd4c0a..d3f05486b 100644 --- a/src/org/ods/util/PodData.groovy +++ b/src/org/ods/util/PodData.groovy @@ -24,10 +24,22 @@ class PodData { // Example: foo-3 String deploymentId + // podNode is the node name on which of the pod, equal to .spec.nodeName. + // Example: ip-172-32-53-123.eu-west-1.compute.internal + String podNode + + // podIp is the IP of the pod, equal to .status.podIP. + // Example: 10.132.16.73 + String podIp + // podStatus is the status phase of the pod, equal to .status.phase // Example: Running String podStatus + // podStartupTimeStamp is the start time of the pod, equal to .status.startTime. + // Example: 2020-11-02T10:57:35Z + String podStartupTimeStamp + // containers is a map of container names to their image. // Example: [bar: '172.30.21.193:5000/foo/bar@sha256:a828...4389'] Map containers diff --git a/test/groovy/org/ods/core/test/LoggerStub.groovy b/test/groovy/org/ods/core/test/LoggerStub.groovy index 3f63c521d..c339eccea 100644 --- a/test/groovy/org/ods/core/test/LoggerStub.groovy +++ b/test/groovy/org/ods/core/test/LoggerStub.groovy @@ -1,6 +1,5 @@ package org.ods.core.test -import groovy.json.JsonOutput import org.ods.util.ILogger class LoggerStub implements ILogger, Serializable { @@ -18,18 +17,10 @@ class LoggerStub implements ILogger, Serializable { logger.debug message } - String jsonDebug(Object jsonObject, String message = null, boolean pretty = true) { - debug(jsonMessage(jsonObject, message, pretty)) - } - String info(String message) { logger.info message } - String jsonInfo(Object jsonObject, String message = null, boolean pretty = true) { - info(jsonMessage(jsonObject, message, pretty)) - } - String warn(String message) { info ("WARN: ${message}") } @@ -38,18 +29,10 @@ class LoggerStub implements ILogger, Serializable { debug(timedCall(component, message)) } - String jsonDebugClocked(String component, Object jsonObject, String message = null, boolean pretty = true) { - debug(timedCall(component, jsonMessage(jsonObject, message, pretty))) - } - String infoClocked(String component, String message = null) { info(timedCall(component, message)) } - String jsonInfoClocked(String component, Object jsonObject, String message = null, boolean pretty = true) { - info(timedCall(component, jsonMessage(jsonObject, message, pretty))) - } - String warnClocked(String component, String message = null) { warn(timedCall(component, message)) } @@ -79,21 +62,6 @@ class LoggerStub implements ILogger, Serializable { timedCall (component) } - private def toJson(Object jsonObject, boolean pretty = true) { - def json = JsonOutput.toJson(jsonObject) - json = pretty ? JsonOutput.prettyPrint(json) : json - return json - } - - private def jsonMessage(Object jsonObject, String message, boolean pretty) { - def json = toJson(jsonObject, pretty) - def prefix = message ? "${message}, json" : 'json' - - def msg = "${prefix}: ${json}" - - return msg - } - @SuppressWarnings(['GStringAsMapKey', 'UnnecessaryElseStatement']) private def timedCall (String component, String message = null) { if (!component) { diff --git a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy index fcb84ccaa..18c507e20 100644 --- a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy +++ b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy @@ -53,8 +53,6 @@ import static util.FixtureHelper.createSockShopJUnitXmlTestResults @Slf4j class LeVADocumentUseCaseSpec extends SpecHelper { - private static final String FIXTURES_PATH = "org/ods/component/LeVADocumentUseCase" - @Rule public TemporaryFolder tempFolder diff --git a/test/groovy/org/ods/orchestration/util/ProjectSpec.groovy b/test/groovy/org/ods/orchestration/util/ProjectSpec.groovy index 931ad93e1..310683a51 100644 --- a/test/groovy/org/ods/orchestration/util/ProjectSpec.groovy +++ b/test/groovy/org/ods/orchestration/util/ProjectSpec.groovy @@ -2319,7 +2319,6 @@ class ProjectSpec extends SpecHelper { def firstVersion = '1.0' def secondVersion = '2.0' - def cmp ={ name -> [key: "CMP-${name}" as String, name: "Component 1"]} def req = { name, String version = null -> [key: "REQ-${name}" as String, description:name, versions:[version]] } def ts = { name, String version = null -> [key: "TS-${name}" as String, description:name, versions:[version]] } def rsk = { name, String version = null -> [key: "RSK-${name}" as String, description:name, versions:[version]] } @@ -2345,23 +2344,23 @@ class ProjectSpec extends SpecHelper { ts1 << [requirements: [req1.key], tests: [tst1.key, tst2.key]] rsk1 << [requirements: [req1.key], mitigations: [mit1.key]] mit1 << [requirements: [req1.key], risks: [rsk1.key]] - req2 << [predecessors: [req1.key], tests: [tst4.key]] - tst3 << [predecessors: [tst1.key]] + req2 << [predecessors: [req1.key], tests: [tst2.key,tst3.key,tst4.key], techSpecs: [ts2.key], risks: [rsk2.key], mitigations: [mit2.key]] + tst3 << [predecessors: [tst1.key], requirements: [req2.key], techSpecs: [ts2.key]] tst4 << [requirements: [req2.key]] - rsk2 << [predecessors: [rsk1.key], requirements: [req1.key]] - mit2 << [predecessors: [mit1.key], requirements: [req1.key], risks: [rsk1.key]] - ts2 << [predecessors: [ts1.key]] + rsk2 << [predecessors: [rsk1.key], requirements: [req2.key], mitigations: [mit2.key]] + mit2 << [predecessors: [mit1.key], requirements: [req2.key], risks: [rsk2.key]] + ts2 << [predecessors: [ts1.key], requirements: [req2.key], tests: [tst2.key, tst3.key]] - def req2Updated = req2.clone() + [tests: [tst4.key, tst3.key, tst2.key], techSpecs: [ts2.key], risks: [rsk2.key], mitigations: [mit2.key]] + def req2Updated = req2.clone() req2Updated << [expandedPredecessors: [[key: req1.key, versions: req1.versions]]] - def tst2Updated = tst2.clone() + [requirements: [req2.key], techSpecs: [ts2.key]] - def tst3Updated = tst3.clone() + [requirements: [req2.key], techSpecs: [ts2.key]] + def tst2Updated = tst2.clone() + def tst3Updated = tst3.clone() tst3Updated << [expandedPredecessors: [[key: tst1.key, versions: tst1.versions]]] - def rsk2Updated = rsk2.clone() + [requirements: [req2.key], mitigations: [mit2.key]] + def rsk2Updated = rsk2.clone() rsk2Updated << [expandedPredecessors: [[key: rsk1.key, versions: rsk1.versions]]] - def mit2Updated = mit2.clone() + [requirements: [req2.key], risks: [rsk2.key]] + def mit2Updated = mit2.clone() mit2Updated << [expandedPredecessors: [[key: mit1.key, versions: mit1.versions]]] - def ts2Updated = ts2.clone() + [requirements: [req2.key], tests: [tst3.key, tst2.key]] + def ts2Updated = ts2.clone() ts2Updated << [expandedPredecessors: [[key: ts1.key, versions: ts1.versions]]] def storedData = [ diff --git a/test/groovy/org/ods/services/OpenShiftServiceSpec.groovy b/test/groovy/org/ods/services/OpenShiftServiceSpec.groovy index 6eff53520..b8b3e803c 100644 --- a/test/groovy/org/ods/services/OpenShiftServiceSpec.groovy +++ b/test/groovy/org/ods/services/OpenShiftServiceSpec.groovy @@ -189,10 +189,6 @@ class OpenShiftServiceSpec extends SpecHelper { then: results.size() == expected.size() for (int i = 0; i < results.size(); i++) { - // Note: podIp, podNode and podStartupTimeStamp are no longer in type PodData. - // Each result is only a subset of the corresponding expected data. - // While it can surpise that the == assertion below would not catch this, - // it actually comes in handy so we can leave the original data in place. results[i].toMap() == expected[i] } } From 939bed5f508f2c3729ba430b68f9d278462e84ad Mon Sep 17 00:00:00 2001 From: Carlos Antelo <180194797+anteloro-boeh@users.noreply.github.com> Date: Fri, 22 Nov 2024 16:48:53 +0100 Subject: [PATCH 34/36] fix: incorrect namespaces for test and prod on TIR table --- src/org/ods/orchestration/phases/DeployOdsComponent.groovy | 4 ++++ .../ods/orchestration/usecase/LeVADocumentUseCase.groovy | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/org/ods/orchestration/phases/DeployOdsComponent.groovy b/src/org/ods/orchestration/phases/DeployOdsComponent.groovy index 2972861d6..daf308e03 100644 --- a/src/org/ods/orchestration/phases/DeployOdsComponent.groovy +++ b/src/org/ods/orchestration/phases/DeployOdsComponent.groovy @@ -40,6 +40,9 @@ class DeployOdsComponent { DeploymentDescriptor deploymentDescriptor steps.dir(openShiftDir) { + // FIXME This is not correct, when deploying on test and prod envs, the deployment descriptor + // read from file contains deploymentMean.namespace=xxx-dev, which doesn't match the target namespace, + // which should end in either -test or -prod deploymentDescriptor = DeploymentDescriptor.readFromFile(steps) logger.debug("DeploymentDescriptor '${openShiftDir}': ${deploymentDescriptor.deployments}") } @@ -56,6 +59,7 @@ class DeployOdsComponent { Map deploymentMean = deployment.deploymentMean logger.debug("Helm Config for ${deploymentName} -> ${deploymentMean}") deploymentMean['repoId'] = repo.id + deploymentMean['namespace'] = project.targetProject applyTemplates(openShiftDir, deploymentMean) diff --git a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy index 3e93b7cfb..6ea3e2a0a 100644 --- a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy +++ b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy @@ -1099,6 +1099,7 @@ class LeVADocumentUseCase extends DocGenUseCase { def keysInDoc = ['Technology-' + repo.id] def docHistory = this.getAndStoreDocumentHistory(documentType + '-' + repo.id, keysInDoc) Map> deployments = repo.data.openshift.deployments ?: [:] + Map deploymentMean = prepareDeploymentMeanInfo(deployments, targetEnvironment) def documentData = [ @@ -1187,13 +1188,16 @@ class LeVADocumentUseCase extends DocGenUseCase { filenamePattern.replace('.env.', ".${targetEnvironment}.") } + // Global config files are those that are not environment specific + def configFiles = mean.helmValuesFiles?.findAll { !envConfigFiles.contains(it) } + formattedMean.namespace = mean.namespace ?: 'None' formattedMean.type = mean.type formattedMean.descriptorPath = mean.chartDir ?: '.' formattedMean.defaultCmdLineArgs = mean.helmDefaultFlags.join(' ') ?: 'None' formattedMean.additionalCmdLineArgs = mean.helmAdditionalFlags.join(' ') ?: 'None' formattedMean.configParams = HtmlFormatterUtil.toUl(mean.helmValues as Map, 'None') - formattedMean.configFiles = HtmlFormatterUtil.toUl(mean.helmValuesFiles as List, 'None') + formattedMean.configFiles = HtmlFormatterUtil.toUl(configFiles as List, 'None') formattedMean.envConfigFiles = HtmlFormatterUtil.toUl(envConfigFiles as List, 'None') Map formattedStatus = [:] From cf250b5f226135aed0077198b3c559789e15b11b Mon Sep 17 00:00:00 2001 From: Carlos Antelo <180194797+anteloro-boeh@users.noreply.github.com> Date: Thu, 28 Nov 2024 17:34:03 +0100 Subject: [PATCH 35/36] chore: removed unneeded FIXME and TODO, wrap log.debug() --- .../component/HelmDeploymentStrategy.groovy | 2 - .../phases/DeployOdsComponent.groovy | 48 ++++++++++++------- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/org/ods/component/HelmDeploymentStrategy.groovy b/src/org/ods/component/HelmDeploymentStrategy.groovy index dda2f127e..1b16d7b40 100644 --- a/src/org/ods/component/HelmDeploymentStrategy.groovy +++ b/src/org/ods/component/HelmDeploymentStrategy.groovy @@ -163,8 +163,6 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy { private Map> getRolloutData( HelmStatus helmStatus ) { - // TODO This is still needed for several reasons, still pending refactoring in order to - // remove the need for pod data Map> rolloutData = [:] Map> deploymentKinds = helmStatus.resourcesByKind diff --git a/src/org/ods/orchestration/phases/DeployOdsComponent.groovy b/src/org/ods/orchestration/phases/DeployOdsComponent.groovy index daf308e03..1cc396c92 100644 --- a/src/org/ods/orchestration/phases/DeployOdsComponent.groovy +++ b/src/org/ods/orchestration/phases/DeployOdsComponent.groovy @@ -32,7 +32,7 @@ class DeployOdsComponent { @TypeChecked(TypeCheckingMode.SKIP) @SuppressWarnings(['AbcMetric']) - public void run(Map repo, String baseDir) { + void run(Map repo, String baseDir) { this.os = ServiceRegistry.instance.get(OpenShiftService) steps.dir(baseDir) { @@ -40,11 +40,10 @@ class DeployOdsComponent { DeploymentDescriptor deploymentDescriptor steps.dir(openShiftDir) { - // FIXME This is not correct, when deploying on test and prod envs, the deployment descriptor - // read from file contains deploymentMean.namespace=xxx-dev, which doesn't match the target namespace, - // which should end in either -test or -prod deploymentDescriptor = DeploymentDescriptor.readFromFile(steps) - logger.debug("DeploymentDescriptor '${openShiftDir}': ${deploymentDescriptor.deployments}") + if (logger.debugMode) { + logger.debug("DeploymentDescriptor '${openShiftDir}': ${deploymentDescriptor.deployments}") + } } if (!repo.data.openshift.deployments) { repo.data.openshift.deployments = [:] @@ -57,7 +56,9 @@ class DeployOdsComponent { deploymentDescriptor.deployments.each { String deploymentName, Map deployment -> // read from deploymentdescriptor Map deploymentMean = deployment.deploymentMean - logger.debug("Helm Config for ${deploymentName} -> ${deploymentMean}") + if (logger.debugMode) { + logger.debug("Helm Config for ${deploymentName} -> ${deploymentMean}") + } deploymentMean['repoId'] = repo.id deploymentMean['namespace'] = project.targetProject @@ -83,7 +84,10 @@ class DeployOdsComponent { if (!podData) { throw new RuntimeException(msgPodsNotFound) } - logger.debug("Helm podData for '${podDataContext.join(', ')}': ${podData}") + + if (logger.debugMode) { + logger.debug("Helm podData for '${podDataContext.join(', ')}': ${podData}") + } // TODO: Once the orchestration pipeline can deal with multiple replicas, // update this to deal with multiple pods. @@ -107,12 +111,16 @@ class DeployOdsComponent { } } - logger.debug("Found Deploymentmean(s) for ${repo.id}: \n${deploymentMean}") + if (logger.debugMode) { + logger.debug("Found Deploymentmean(s) for ${repo.id}: \n${deploymentMean}") + } applyTemplates(openShiftDir, deploymentMean) deploymentDescriptor.deployments.each { String deploymentName, Map deployment -> Map deploymentMean4Deployment = deployment.deploymentMean - logger.debug("Tailor Config for ${deploymentName} -> ${deploymentMean4Deployment}") + if (logger.debugMode) { + logger.debug("Tailor Config for ${deploymentName} -> ${deploymentMean4Deployment}") + } importImages(deployment, deploymentName, project.sourceProject) @@ -152,7 +160,9 @@ class DeployOdsComponent { @TypeChecked(TypeCheckingMode.SKIP) private String computeStartDir() { List files = steps.findFiles(glob: "**/${DeploymentDescriptor.FILE_NAME}") - logger.debug("DeploymentDescriptors: ${files}") + if (logger.debugMode) { + logger.debug("DeploymentDescriptors: ${files}") + } // If we find anything but _exactly_ one deployment descriptor, we fail. if (!files || files.size() != 1) { String resourcePath = 'org/ods/orchestration/phases/DeployOdsComponent.computeStartDir.GString.txt' @@ -243,7 +253,9 @@ class DeployOdsComponent { def helmStatus = os.helmStatus(project.targetProject, deploymentMean.helmReleaseName) def helmStatusMap = helmStatus.toMap() deploymentMean.helmStatus = helmStatusMap - logger.debug("${this.class.name} -- HELM STATUS: ${helmStatusMap}") + if (logger.debugMode) { + logger.debug("${this.class.name} -- HELM STATUS: ${helmStatusMap}") + } } } jenkins.maybeWithPrivateKeyCredentials(secretName) { String pkeyFile -> @@ -266,10 +278,12 @@ class DeployOdsComponent { ) def imageParts = imageRaw.split('/') if (MROPipelineUtil.EXCLUDE_NAMESPACES_FROM_IMPORT.contains(imageParts.first())) { - logger.debug( - "Skipping import of '${imageRaw}', " + - "because it is defined as excluded: ${MROPipelineUtil.EXCLUDE_NAMESPACES_FROM_IMPORT}" - ) + if (logger.debugMode) { + logger.debug( + "Skipping import of '${imageRaw}', " + + "because it is defined as excluded: ${MROPipelineUtil.EXCLUDE_NAMESPACES_FROM_IMPORT}" + ) + } } else { def imageInfo = imageParts.last().split('@') def imageName = imageInfo.first() @@ -298,7 +312,9 @@ class DeployOdsComponent { } private void verifyImageShas(Map deployment, Map podContainers) { - logger.debug("ImageVerification -deployment: ${deployment} -podContainers: ${podContainers}") + if (logger.debugMode) { + logger.debug("ImageVerification -deployment: ${deployment} -podContainers: ${podContainers}") + } deployment.containers?.each { String containerName, String imageRaw -> if (!os.verifyImageSha(containerName, imageRaw, podContainers[containerName].toString())) { throw new RuntimeException("Error: Image verification for container '${containerName}' failed.") From 19447f3e6f6da49c0adf6790353d37bdcef2e831 Mon Sep 17 00:00:00 2001 From: Carlos Antelo <180194797+anteloro-boeh@users.noreply.github.com> Date: Thu, 28 Nov 2024 17:44:08 +0100 Subject: [PATCH 36/36] chore: CodeNarc reported issue, reduced cyclomatic complexity to fix it --- .../phases/DeployOdsComponent.groovy | 43 ++++++------------- 1 file changed, 12 insertions(+), 31 deletions(-) diff --git a/src/org/ods/orchestration/phases/DeployOdsComponent.groovy b/src/org/ods/orchestration/phases/DeployOdsComponent.groovy index 1cc396c92..d9a99994a 100644 --- a/src/org/ods/orchestration/phases/DeployOdsComponent.groovy +++ b/src/org/ods/orchestration/phases/DeployOdsComponent.groovy @@ -41,9 +41,7 @@ class DeployOdsComponent { DeploymentDescriptor deploymentDescriptor steps.dir(openShiftDir) { deploymentDescriptor = DeploymentDescriptor.readFromFile(steps) - if (logger.debugMode) { - logger.debug("DeploymentDescriptor '${openShiftDir}': ${deploymentDescriptor.deployments}") - } + logger.debug("DeploymentDescriptor '${openShiftDir}': ${deploymentDescriptor.deployments}") } if (!repo.data.openshift.deployments) { repo.data.openshift.deployments = [:] @@ -56,9 +54,7 @@ class DeployOdsComponent { deploymentDescriptor.deployments.each { String deploymentName, Map deployment -> // read from deploymentdescriptor Map deploymentMean = deployment.deploymentMean - if (logger.debugMode) { - logger.debug("Helm Config for ${deploymentName} -> ${deploymentMean}") - } + logger.debug("Helm Config for ${deploymentName} -> ${deploymentMean}") deploymentMean['repoId'] = repo.id deploymentMean['namespace'] = project.targetProject @@ -84,10 +80,7 @@ class DeployOdsComponent { if (!podData) { throw new RuntimeException(msgPodsNotFound) } - - if (logger.debugMode) { - logger.debug("Helm podData for '${podDataContext.join(', ')}': ${podData}") - } + logger.debug("Helm podData for '${podDataContext.join(', ')}': ${podData}") // TODO: Once the orchestration pipeline can deal with multiple replicas, // update this to deal with multiple pods. @@ -111,16 +104,12 @@ class DeployOdsComponent { } } - if (logger.debugMode) { - logger.debug("Found Deploymentmean(s) for ${repo.id}: \n${deploymentMean}") - } + logger.debug("Found Deploymentmean(s) for ${repo.id}: \n${deploymentMean}") applyTemplates(openShiftDir, deploymentMean) deploymentDescriptor.deployments.each { String deploymentName, Map deployment -> Map deploymentMean4Deployment = deployment.deploymentMean - if (logger.debugMode) { - logger.debug("Tailor Config for ${deploymentName} -> ${deploymentMean4Deployment}") - } + logger.debug("Tailor Config for ${deploymentName} -> ${deploymentMean4Deployment}") importImages(deployment, deploymentName, project.sourceProject) @@ -160,9 +149,7 @@ class DeployOdsComponent { @TypeChecked(TypeCheckingMode.SKIP) private String computeStartDir() { List files = steps.findFiles(glob: "**/${DeploymentDescriptor.FILE_NAME}") - if (logger.debugMode) { - logger.debug("DeploymentDescriptors: ${files}") - } + logger.debug("DeploymentDescriptors: ${files}") // If we find anything but _exactly_ one deployment descriptor, we fail. if (!files || files.size() != 1) { String resourcePath = 'org/ods/orchestration/phases/DeployOdsComponent.computeStartDir.GString.txt' @@ -253,9 +240,7 @@ class DeployOdsComponent { def helmStatus = os.helmStatus(project.targetProject, deploymentMean.helmReleaseName) def helmStatusMap = helmStatus.toMap() deploymentMean.helmStatus = helmStatusMap - if (logger.debugMode) { - logger.debug("${this.class.name} -- HELM STATUS: ${helmStatusMap}") - } + logger.debug("${this.class.name} -- HELM STATUS: ${helmStatusMap}") } } jenkins.maybeWithPrivateKeyCredentials(secretName) { String pkeyFile -> @@ -278,12 +263,10 @@ class DeployOdsComponent { ) def imageParts = imageRaw.split('/') if (MROPipelineUtil.EXCLUDE_NAMESPACES_FROM_IMPORT.contains(imageParts.first())) { - if (logger.debugMode) { - logger.debug( - "Skipping import of '${imageRaw}', " + - "because it is defined as excluded: ${MROPipelineUtil.EXCLUDE_NAMESPACES_FROM_IMPORT}" - ) - } + logger.debug( + "Skipping import of '${imageRaw}', " + + "because it is defined as excluded: ${MROPipelineUtil.EXCLUDE_NAMESPACES_FROM_IMPORT}" + ) } else { def imageInfo = imageParts.last().split('@') def imageName = imageInfo.first() @@ -312,9 +295,7 @@ class DeployOdsComponent { } private void verifyImageShas(Map deployment, Map podContainers) { - if (logger.debugMode) { - logger.debug("ImageVerification -deployment: ${deployment} -podContainers: ${podContainers}") - } + logger.debug("ImageVerification -deployment: ${deployment} -podContainers: ${podContainers}") deployment.containers?.each { String containerName, String imageRaw -> if (!os.verifyImageSha(containerName, imageRaw, podContainers[containerName].toString())) { throw new RuntimeException("Error: Image verification for container '${containerName}' failed.")