Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
3 changes: 3 additions & 0 deletions src/org/ods/component/AbstractDeploymentStrategy.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ abstract class AbstractDeploymentStrategy implements IDeploymentStrategy {
@Override
abstract Map<String, List<PodData>> deploy()

// Fetches original kubernetes revisions of deployment resources.
//
// returns a two level map with keys resourceKind -> resourceName -> revision (int)
protected Map<String, Map<String, Integer>> fetchOriginalVersions(Map<String, List<String>> deploymentResources) {
def originalVersions = [:]
deploymentResources.each { resourceKind, resourceNames ->
Expand Down
100 changes: 64 additions & 36 deletions src/org/ods/component/HelmDeploymentStrategy.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This map seems to be used only for logging purposes. I would enclose the instruction that performs the log in a condition on logger.getDebugOn() and generate the map inside, so that it isn't generated unless necessary.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you - will adjust the code!

def deploymentResources = getDeploymentResources(helmStatus)

def deploymentResources = openShift.getResourcesForComponent(
context.targetProject, DEPLOYMENT_KINDS, options.selector
)
logger.info("${this.class.name} -- HELM STATUS")
logger.info(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logging the contents of the helm status map is detailed debug info. Please change this to logger.debug and enclose it in a condition on logger.getDebugOn() so that no processing is done, if debug mode is inactive.

JsonOutput.prettyPrint(
JsonOutput.toJson(helmStatusMap)))

// not sure if we need both HELM STATUS and DEPLOYMENT RESOURCES"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, don't leave this kind of comments. Could you make a decision and remove it?

logger.info("${this.class.name} -- DEPLOYMENT RESOURCES")
logger.info(
JsonOutput.prettyPrint(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also feels like debug level. Let's discuss this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logging of DEPLOYMENT RESOURCES comes from master. Should it be changed?

Expand All @@ -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 ->
Expand All @@ -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<String, String>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use def here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion - I will make that change and also look out for similar cases.

mergedHelmValues << options.helmValues

// we add the global ones - this allows usage in subcharts
Expand All @@ -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<String>
mergedHelmValuesFiles.addAll(options.helmValuesFiles)

options.helmEnvBasedValuesFiles.each { envValueFile ->
Expand All @@ -161,46 +169,66 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy {
}
}

private Map<String, List<String>> getDeploymentResources(HelmStatusSimpleData helmStatus) {
helmStatus.getResourcesByKind(DEPLOYMENT_KINDS)
}

// rollout returns a map like this:
// [
// 'DeploymentConfig/foo': [[podName: 'foo-a', ...], [podName: 'foo-b', ...]],
// 'Deployment/bar': [[podName: 'bar-a', ...]]
// ]
@TypeChecked(TypeCheckingMode.SKIP)
private Map<String, List<PodData>> getRolloutData(
Map<String, List<String>> 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> 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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to use a specific exception that can be specifically caught, if necessary.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My expectation is that it should never happen. Would there be a reason to nonetheless catch it somewhere?

}

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
}
Expand Down
27 changes: 23 additions & 4 deletions src/org/ods/orchestration/phases/DeployOdsComponent.groovy
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -58,21 +60,29 @@ class DeployOdsComponent {
applyTemplates(openShiftDir, deploymentMean)

def retries = project.environmentConfig?.openshiftRolloutTimeoutRetries ?: 10
def podData = null
def podDataContext = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this data is only to generate an error message, please, generate only when you are about to echo the message.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May rationale here was that the case where no pods are not found is not typically happening. If we move the message generation in a conditional it could be that the code itself is flawed and potentially cause an exception.
Given that I already have tested the code as is I am now confident that the code would work and we could still move it.

"targetProject=${project.targetProject}",
"selector=${deploymentMean.selector}",
"name=${deploymentName}",
]
def msgPodsNotFound = "Could not find 'running' pod(s) for '${podDataContext.join(', ')}'"
List<PodData> 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()
Expand Down Expand Up @@ -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)))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks will change!


}
}
jenkins.maybeWithPrivateKeyCredentials(secretName) { String pkeyFile ->
Expand Down
Loading