Skip to content
Merged
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@

### Fixed
* Fix Tailor deployment drifts for D, Q envs ([#1055](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1055))

## [4.13.0] - 2026-06-30
### Changed
* Push container images to AWS ECR independently of EKS, while optionally triggering EKS deployments when needed ([#1267](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1267))
* Updated Java version in sonarqube execution to Java 21 ([#1269](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1269))

### Fixed
* Fix branch mapping logic to ensure correct environment assignment ([#1263](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1263))
* Fix Nexus not accepting npm artifacts when uploaded through odsComponentStageUploadToNexus ([#1268](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1268))
* Aqua Stage is being skipped when branches are not eligable ([#1170](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1170))
Expand Down Expand Up @@ -46,6 +53,7 @@
### Fixed
* Fix SonarQube run enable/disable logic ([#1259](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1259))


## [4.11.1] - 2025-12-19

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@ In addition to the configuration options below, one can use e.g. a `Tailorfile`
| Option | Description


| *awsEnvPath* +
_String_
|Path to AWS environment configuration env files (defaults to `./environments`).
Files (dev.yml, test.yaml, prod.yaml) must contain the following keys:
account: "..."
eksCluster: "..."
region: ...
credentials:
key: ... (e.g. projectName-cd-aws-us-access-key-id-dev)
secret: ... (e.g. projectName-cd-aws-us-secret-access-key-dev)
Only relevant for AWS-based deployments (odsComponentStageRolloutAWSDeployment)


| *branch* +
_String_
|Branch to run stage for.
Expand Down Expand Up @@ -165,6 +178,14 @@ _List<String>_
directory referenced by `chartDir` exists.


| *helmWithOnlyECR* +
_Boolean_
|Whether to use Helm for deployment or only sync with ECR (defaults to `false`)
If `true`, the process will only use the ECR repository to store the image. So, no Helm deployment will be done.
If `false`, the image will be stored in ECR and the helm deployment will be executed.
Only relevant for AWS-based deployments (odsComponentStageRolloutAWSDeployment)


| *imageTag* +
_String_
|Image tag on which to apply the `latest` tag (defaults to `context.shortGitCommit`).
Expand Down
17 changes: 0 additions & 17 deletions src/org/ods/component/AbstractDeploymentStrategy.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -37,23 +37,6 @@ abstract class AbstractDeploymentStrategy implements IDeploymentStrategy {
originalVersions
}

protected findOrCreateImageStream(String targetProject, String image) {
try {
openShift.findOrCreateImageStream(targetProject, image)
} catch (Exception ex) {
steps.error "Could not find/create ImageStream ${image} in ${targetProject}. Error was: ${ex}"
}
}

protected void retagImages(String targetProject, Set<String> images) {
images.each { image ->
findOrCreateImageStream(targetProject, image)
openShift.importImageTagFromProject(
targetProject, image, context.cdProject, options.imageTag, options.imageTag
)
}
}

@TypeChecked(TypeCheckingMode.SKIP)
protected Set<String> getBuiltImages() {
context.buildArtifactURIs.builds.keySet().findAll { it ->
Expand Down
2 changes: 1 addition & 1 deletion src/org/ods/component/Context.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ class Context implements IContext {
// https://issues.jenkins-ci.org/browse/JENKINS-27421 and
// https://issues.jenkins-ci.org/browse/JENKINS-35191.
for (def key : config.branchToEnvironmentMapping.keySet()) {
if (config.gitBranch.startsWith(key)) {
if (config.gitBranch.startsWith(key) && key.endsWith('/')) {
setMostSpecificEnvironment(
config.branchToEnvironmentMapping[key],
config.gitBranch.replace(key, '')
Expand Down
43 changes: 43 additions & 0 deletions src/org/ods/component/ECROnlyDeploymentStrategy.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.ods.component

import org.ods.util.ILogger
import org.ods.util.PodData

/**
* Deployment strategy that only pushes images to ECR without performing a
* Helm upgrade or any OpenShift/EKS rollout. Use this when images must be
* synchronised to ECR but the actual cluster deployment is handled elsewhere.
*/
class ECROnlyDeploymentStrategy extends AbstractDeploymentStrategy {

private final IContext context
private final ILogger logger
private final IImageRepository imageRepository
private final RolloutOpenShiftDeploymentOptions options

ECROnlyDeploymentStrategy(
IContext context,
Map<String, Object> config,
IImageRepository imageRepository,
ILogger logger
) {
this.context = context
this.logger = logger
this.imageRepository = imageRepository
this.options = new RolloutOpenShiftDeploymentOptions(config)
}

@Override
Map<String, List<PodData>> deploy() {
def targetProject = context.targetProject
if (options.helmValues && options.helmValues['namespaceOverride']) {
targetProject = options.helmValues['namespaceOverride']
logger.info("Override namespace deployment to ${targetProject}")
}
logger.info("Retagging images for ${targetProject}")
imageRepository.retagImages(targetProject, getBuiltImages(), options.imageTag, options.imageTag)
logger.info("ECR-only deployment configured, skipping Helm/OpenShift rollout.")
return [:]
}

}
63 changes: 63 additions & 0 deletions src/org/ods/component/HelmDeploymentConfig.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package org.ods.component

import com.cloudbees.groovy.cps.NonCPS
import groovy.transform.TypeChecked
import groovy.transform.TypeCheckingMode

/**
* Centralises the default configuration values shared by Helm-based deployment
* stages and strategies. Call {@link #applyDefaults} once with the pipeline
* context and the user-supplied config map; values that were not explicitly set
* by the user will be filled in with sensible defaults.
*/
@TypeChecked
class HelmDeploymentConfig {

@NonCPS
@TypeChecked(TypeCheckingMode.SKIP)
static void applyDefaults(IContext context, Map<String, Object> config) {
if (!config.selector) {
config.selector = context.selector
}
if (!config.imageTag) {
config.imageTag = context.shortGitCommit
}
if (!config.deployTimeoutMinutes) {
config.deployTimeoutMinutes = context.openshiftRolloutTimeout ?: 15
}
if (!config.deployTimeoutRetries) {
config.deployTimeoutRetries = context.openshiftRolloutTimeoutRetries ?: 5
}
if (!config.chartDir) {
config.chartDir = 'chart'
}
if (!config.containsKey('helmReleaseName')) {
config.helmReleaseName = context.componentId
}
if (!config.containsKey('helmValues')) {
config.helmValues = [:]
}
if (!config.containsKey('helmValuesFiles')) {
config.helmValuesFiles = ['values.yaml']
}
if (!config.containsKey('helmEnvBasedValuesFiles')) {
config.helmEnvBasedValuesFiles = []
}
if (!config.containsKey('helmDefaultFlags')) {
config.helmDefaultFlags = ['--install', '--atomic']
}
if (!config.containsKey('helmAdditionalFlags')) {
config.helmAdditionalFlags = []
}
if (!config.containsKey('helmDiff')) {
config.helmDiff = true
}
if (!config.helmPrivateKeyCredentialsId) {
config.helmPrivateKeyCredentialsId = "${context.cdProject}-helm-private-key"
}
if (!config.containsKey('helmReleasesHistoryLimit')) {
config.helmReleasesHistoryLimit = 5
}
}

}
81 changes: 27 additions & 54 deletions src/org/ods/component/HelmDeploymentStrategy.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.ods.component

import groovy.transform.TypeChecked
import groovy.transform.TypeCheckingMode
import org.ods.orchestration.util.MROPipelineUtil
import org.ods.services.JenkinsService
import org.ods.services.OpenShiftService
import org.ods.util.HelmStatus
Expand All @@ -17,6 +18,8 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy {
private final JenkinsService jenkins
private final ILogger logger
private final IPipelineSteps steps
private final IImageRepository imageRepository

// assigned in constructor
private final RolloutOpenShiftDeploymentOptions options

Expand All @@ -27,57 +30,17 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy {
Map<String, Object> config,
OpenShiftService openShift,
JenkinsService jenkins,
IImageRepository imageRepository,
ILogger logger
) {
if (!config.selector) {
config.selector = context.selector
}
if (!config.imageTag) {
config.imageTag = context.shortGitCommit
}
if (!config.deployTimeoutMinutes) {
config.deployTimeoutMinutes = context.openshiftRolloutTimeout ?: 15
}
if (!config.deployTimeoutRetries) {
config.deployTimeoutRetries = context.openshiftRolloutTimeoutRetries ?: 5
}
// Helm options
if (!config.chartDir) {
config.chartDir = 'chart'
}
if (!config.containsKey('helmReleaseName')) {
config.helmReleaseName = context.componentId
}
if (!config.containsKey('helmValues')) {
config.helmValues = [:]
}
if (!config.containsKey('helmValuesFiles')) {
config.helmValuesFiles = ['values.yaml']
}
if (!config.containsKey('helmEnvBasedValuesFiles')) {
config.helmEnvBasedValuesFiles = []
}
// helmDefaultFlags are always set and cannot be overridden
config.helmDefaultFlags = ['--install', '--atomic']
if (!config.containsKey('helmAdditionalFlags')) {
config.helmAdditionalFlags = []
}
if (!config.containsKey('helmDiff')) {
config.helmDiff = true
}
if (!config.helmPrivateKeyCredentialsId) {
config.helmPrivateKeyCredentialsId = "${context.cdProject}-helm-private-key"
}
if (!config.containsKey('helmReleasesHistoryLimit')) {
config.helmReleasesHistoryLimit = 5
}
HelmDeploymentConfig.applyDefaults(context, config)
this.context = context
this.logger = logger
this.steps = steps

this.options = new RolloutOpenShiftDeploymentOptions(config)
this.openShift = openShift
this.jenkins = jenkins
this.imageRepository = imageRepository
}

@Override
Expand All @@ -86,13 +49,19 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy {
logger.warn 'Skipping because of empty (target) environment ...'
return [:]
}

// Tag images which have been built in this pipeline from cd project into target project
retagImages(context.targetProject, getBuiltImages())
// Maybe we need to deploy to another namespace
// (ie we want to deploy a monitoring stack into a specific namespace)
def targetProject = context.targetProject
if (options.helmValues['namespaceOverride']) {
targetProject = options.helmValues['namespaceOverride']
logger.info("Override namespace deployment to ${targetProject} ")
}
logger.info("Retagging images for ${targetProject} ")
imageRepository.retagImages(targetProject, getBuiltImages(), options.imageTag, options.imageTag)

logger.info("Rolling out ${context.componentId} with HELM, selector: ${options.selector}")
helmUpgrade(context.targetProject)
HelmStatus helmStatus = openShift.helmStatus(context.targetProject, options.helmReleaseName)
helmUpgrade(targetProject)
HelmStatus helmStatus = openShift.helmStatus(targetProject, options.helmReleaseName)
if (logger.debugMode) {
def helmStatusMap = helmStatus.toMap()
logger.debug("${this.class.name} -- HELM STATUS: ${helmStatusMap}")
Expand All @@ -102,8 +71,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(helmStatus)
return rolloutData
Map<String, List<PodData>> rolloutData = [:]
if (steps.env.repoType == MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_INFRA) {
return rolloutData
} else {
return getRolloutData(helmStatus, targetProject)
}
}

private void helmUpgrade(String targetProject) {
Expand Down Expand Up @@ -167,7 +140,7 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy {
// ]
@TypeChecked(TypeCheckingMode.SKIP)
private Map<String, List<PodData>> getRolloutData(
HelmStatus helmStatus
HelmStatus helmStatus, String targetProject
) {
Map<String, List<PodData>> rolloutData = [:]

Expand All @@ -183,7 +156,7 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy {
names.each { name ->
// Get container images directly from the resource spec (deployment/statefulset/cronjob)
def containers = openShift.getContainerImagesWithNameFromPodSpec(
context.targetProject, kind, name
targetProject, kind, name
)
logger.debug("Helm container images for ${kind}/${name}: ${containers}")

Expand All @@ -201,7 +174,7 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy {
def deploymentMean = [
type: 'helm',
selector: options.selector,
namespace: context.targetProject,
namespace: targetProject,
chartDir: options.chartDir,
helmReleaseName: options.helmReleaseName,
helmEnvBasedValuesFiles: options.helmEnvBasedValuesFiles,
Expand Down Expand Up @@ -252,4 +225,4 @@ class HelmDeploymentStrategy extends AbstractDeploymentStrategy {
return rolloutData
}

}
}
7 changes: 7 additions & 0 deletions src/org/ods/component/IImageRepository.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.ods.component

interface IImageRepository {

void retagImages(String targetProject, Set<String> images, String sourceTag, String targetTag)

}
Loading
Loading