Skip to content

Prepare release 4.9.0 #1226

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 6, 2025
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
### Fixed
* Fix Tailor deployment drifts for D, Q envs ([#1055](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1055))

## [4.9.0] - 2025-8-06
### Changed
* Enforce prod config in metadata.yml ([#1222](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1222))
* Helm parity with Tailor ([#1219](https://github.com/opendevstack/ods-jenkins-shared-library/issues/1219))

## [4.8.2] - 2025-6-04
* Avoid check PROD environment if repos are not included in release ([#1217](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1217))
* Error generating TIR for infra components ([#1216](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1216))
Expand Down
46 changes: 10 additions & 36 deletions src/org/ods/orchestration/DeployStage.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ class DeployStage extends Stage {

@SuppressWarnings(['ParameterName', 'AbcMetric', 'MethodSize', 'LineLength'])
def run() {
def steps = ServiceRegistry.instance.get(IPipelineSteps)
def levaDocScheduler = ServiceRegistry.instance.get(LeVADocumentScheduler)
def os = ServiceRegistry.instance.get(OpenShiftService)
def util = ServiceRegistry.instance.get(MROPipelineUtil)
Expand Down Expand Up @@ -85,43 +84,18 @@ class DeployStage extends Stage {

runOnAgentPod(agentPodCondition) {
if (project.isPromotionMode) {
def targetEnvironment = project.buildParams.targetEnvironment
def targetProject = project.targetProject
def installableRepos = this.project.repositories.findAll { repo ->
// We only manage the installable repositories in OpenShift if they are included in the release
if (repo.include
&& repo.type?.toLowerCase() != MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_INFRA) {
MROPipelineUtil.PipelineConfig.INSTALLABLE_REPO_TYPES.contains(repo.type)
}
}
logger.info("Deploying project '${project.key}' into environment '${targetEnvironment}'" +
" installable repos? ${installableRepos.size()}")
def installableRepos = util.getInstallableRepos()

if (installableRepos?.size() > 0) {
if (project.targetClusterIsExternal) {
logger.info("Target cluster is external, logging into ${project.openShiftTargetApiUrl}")
script.withCredentials([
script.usernamePassword(
credentialsId: project.environmentConfig.credentialsId,
usernameVariable: 'EXTERNAL_OCP_API_SA',
passwordVariable: 'EXTERNAL_OCP_API_TOKEN'
)
]) {
OpenShiftService.loginToExternalCluster(
steps,
project.openShiftTargetApiUrl,
script.EXTERNAL_OCP_API_TOKEN
)
}
}
logger.info("Verify project deployment '${project.key}' into environment " +
"'${project.buildParams.targetEnvironment}' installable repos? ${installableRepos?.size()}")

// Check if the target environment exists in OpenShift
if (!os.envExists(targetProject)) {
throw new RuntimeException(
"Error: target environment '${targetProject}' does not exist " +
"in ${project.openShiftTargetApiUrl}."
)
}
if (installableRepos?.size() > 0) {
util.verifyEnvLoginAndExistence(script,
os,
project.targetProject,
project.data.openshift?.sessionApiUrl,
project.data.openshift?.targetApiUrl,
project.environmentConfig?.credentialsId)
}
}

Expand Down
73 changes: 67 additions & 6 deletions src/org/ods/orchestration/InitStage.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -135,12 +135,8 @@ class InitStage extends Stage {
String stageToStartAgent = findBestPlaceToStartAgent(repos, logger)

// Compute target project. For now, the existance of DEV on the same cluster is verified.
def concreteEnv = Project.getConcreteEnvironment(
project.buildParams.targetEnvironment,
project.buildParams.version.toString(),
project.versionedDevEnvsEnabled
)
def targetProject = "${project.key}-${concreteEnv}"
def targetProject = Project.getTargetProjectForEnv(project, project.buildParams.targetEnvironment)

def os = registry.get(OpenShiftService)
if (project.buildParams.targetEnvironment == 'dev' && !os.envExists(targetProject)) {
throw new RuntimeException(
Expand All @@ -150,13 +146,78 @@ class InitStage extends Stage {
}
project.setTargetProject(targetProject)

if (util.getInstallableRepos()?.size() > 0) {
validateEnvConfig(logger, registry, util)
} else {
logger.debug("No installable repos found, skipping env existence check.")
}

logger.debug 'Compute groups of repository configs for convenient parallelization'
repos = util.computeRepoGroups(repos)
registry.get(LeVADocumentScheduler).run(phase, PipelinePhaseLifecycleStage.PRE_END)

return [project: project, repos: repos, startAgent: stageToStartAgent]
}

protected void validateEnvConfig(Logger logger, ServiceRegistry registry, MROPipelineUtil util) {
logger.debug("Validate environment metadata.yml config started")
def os = registry.get(OpenShiftService)
Map envs = project.getEnvironments()
def wronglyConfiguredEnvs = []
boolean reloginRequired = false
try {
for (MROPipelineUtil.PipelineEnv env : MROPipelineUtil.PipelineEnv.values()) {
def targetProjectForEnv = Project.getTargetProjectForEnv(project, env.value)
logger.debug("Check cluster config for env ${env.value} and project ${targetProjectForEnv}")
try {
String openshiftClusterApiUrl = envs."$env.value"?.apiUrl
?: envs."$env.value"?.openshiftClusterApiUrl
String openshiftClusterCredentialsId = envs."$env.value"?.credentialsId
?: envs."$env.value"?.openshiftClusterCredentialsId
if (!openshiftClusterApiUrl && !openshiftClusterCredentialsId) {
if (!os.envExists(targetProjectForEnv)) {
wronglyConfiguredEnvs.add(env.value)
}
} else {
reloginRequired = true
util.verifyEnvLoginAndExistence(script,
os,
targetProjectForEnv,
project.data.openshift.sessionApiUrl,
openshiftClusterApiUrl,
openshiftClusterCredentialsId
)
}
} catch (Exception e) {
wronglyConfiguredEnvs.add(env.value)
}
}
if (wronglyConfiguredEnvs.size() > 0) {
String message = "The Release Manager configuration for environment(s) " +
"${wronglyConfiguredEnvs.join(', ')} is incorrect in the metadata.yml. " +
"Please verify the openshift cluster api url and credentials for " +
"each environment mentioned."
if (project.isWorkInProgress) { // Warn build pipeline in this case
project.addCommentInReleaseStatus(message)
util.warnBuild(message)
} else { // Error
util.failBuild(message)
throw new RuntimeException(message) // This also add a comment in the release status issue
}
}
} finally {
if (reloginRequired) {
logger.debug("Try to relogin to current cluster")
try {
os.reloginToCurrentClusterIfNeeded()
} catch (ex) {
logger.error("Error logging back to current cluster ${ex.getMessage()}")
}
logger.debug("Success relogging in to current cluster.")
}
}
}

private String findBestPlaceToStartAgent(List<Map> repos, ILogger logger) {
String stageToStartAgent
repos.each { repo ->
Expand Down
2 changes: 1 addition & 1 deletion src/org/ods/orchestration/phases/DeployOdsComponent.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ class DeployOdsComponent {
def imageInfo = imageParts.last().split('@')
def imageName = imageInfo.first()
def imageSha = imageInfo.last()
if (project.targetClusterIsExternal) {
if (project.targetClusterExternal) {
os.importImageShaFromSourceRegistry(
project.targetProject,
imageName,
Expand Down
4 changes: 4 additions & 0 deletions src/org/ods/orchestration/usecase/JiraUseCase.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,10 @@ class JiraUseCase {
}

void addCommentInReleaseStatus(String message) {
if (!this.jira) {
logger.debug("Jira not connected, cannot append comment: ${message}")
return
}
def changeId = this.project.buildParams.changeId
if (message) {
def projectKey = this.project.jiraProjectKey
Expand Down
19 changes: 15 additions & 4 deletions src/org/ods/orchestration/util/MROPipelineUtil.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,21 @@ class MROPipelineUtil extends PipelineUtil {
]
}

class PipelineEnvs {
static final String DEV = "dev"
static final String QA = "qa"
static final String PROD = "prod"
enum PipelineEnv {

DEV("dev"),
QA("qa"),
PROD("prod")

private String value

String getValue() {
return value
}

private PipelineEnv(String value) {
this.value = value
}
}

class PipelinePhases {
Expand Down
42 changes: 41 additions & 1 deletion src/org/ods/orchestration/util/PipelineUtil.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import com.cloudbees.groovy.cps.NonCPS

import net.lingala.zip4j.ZipFile
import net.lingala.zip4j.model.ZipParameters

import org.ods.services.OpenShiftService
import org.ods.util.IPipelineSteps
import org.ods.util.ILogger
import org.ods.services.GitService
Expand Down Expand Up @@ -184,4 +184,44 @@ class PipelineUtil {
return this.steps.load(path)
}

List getInstallableRepos() {
return this.project.repositories.findAll { repo ->
// We only manage the installable repositories in OpenShift if they are included in the release
if (repo.include
&& repo.type?.toLowerCase() != MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_INFRA) {
MROPipelineUtil.PipelineConfig.INSTALLABLE_REPO_TYPES.contains(repo.type)
}
}
}

@SuppressWarnings('ParameterCount')
def verifyEnvLoginAndExistence(def script, OpenShiftService os, def targetProject, def sessionApiUrl,
def targetApiUrl, def credentialsId) {

if (Project.isTargetClusterExternal(sessionApiUrl, targetApiUrl)) {
logger.info("Target cluster is external, logging into ${targetApiUrl}")
script.withCredentials([
script.usernamePassword(
credentialsId: credentialsId,
usernameVariable: 'EXTERNAL_OCP_API_SA',
passwordVariable: 'EXTERNAL_OCP_API_TOKEN'
)
]) {
OpenShiftService.loginToExternalCluster(
steps,
targetApiUrl,
script.EXTERNAL_OCP_API_TOKEN
)
}
}

// Check if the target environment exists in OpenShift
if (!os.envExists(targetProject)) {
throw new RuntimeException(
"Error: target environment '${targetProject}' does not exist " +
"in ${targetApiUrl}."
)
}
}

}
21 changes: 16 additions & 5 deletions src/org/ods/orchestration/util/Project.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,6 @@ class Project {
protected Boolean isVersioningEnabled = false
private String _gitReleaseBranch


private TestResults aggregatedTestResults;

protected Map data = [:]
Expand Down Expand Up @@ -720,7 +719,7 @@ class Project {

void setOpenShiftData(String sessionApiUrl) {
def envConfig = getEnvironmentConfig()
def targetApiUrl = envConfig?.apiUrl
def targetApiUrl = envConfig?.apiUrl ?: envConfig?.openshiftClusterApiUrl
if (!targetApiUrl) {
targetApiUrl = sessionApiUrl
}
Expand All @@ -730,10 +729,13 @@ class Project {
}

@NonCPS
boolean getTargetClusterIsExternal() {
boolean isTargetClusterExternal() {
return isTargetClusterExternal(this.data.openshift.sessionApiUrl, this.data.openshift.targetApiUrl)
}

@NonCPS
static boolean isTargetClusterExternal(def sessionApiUrl, def targetApiUrl) {
def isExternal = false
def sessionApiUrl = this.data.openshift.sessionApiUrl
def targetApiUrl = this.data.openshift.targetApiUrl
def targetApiUrlMatcher = targetApiUrl =~ /:[0-9]+$/
if (targetApiUrlMatcher.find()) {
isExternal = sessionApiUrl != targetApiUrl
Expand Down Expand Up @@ -779,6 +781,15 @@ class Project {
environment
}

static String getTargetProjectForEnv(Project project, String env) {
def concreteEnv = Project.getConcreteEnvironment(
env,
project.buildParams.version.toString(),
project.versionedDevEnvsEnabled
)
return "${project.key}-${concreteEnv}"
}

static List<String> getBuildEnvironment(IPipelineSteps steps, boolean debug = false, boolean versionedDevEnvsEnabled = false) {
def params = loadBuildParams(steps)

Expand Down
39 changes: 20 additions & 19 deletions src/org/ods/services/OpenShiftService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ class OpenShiftService {
def upgradeFlags = defaultFlags.collect { it }
additionalFlags.collect { upgradeFlags << it }
valuesFiles.collect { upgradeFlags << "-f ${it}".toString() }
values.put('ODS_OPENSHIFT_APP_DOMAIN', getApplicationDomain())
values.collect { k, v -> upgradeFlags << "--set ${k}=${v}".toString() }
if (withDiff) {
def diffFlags = upgradeFlags.findAll { it }
Expand Down Expand Up @@ -1057,7 +1058,7 @@ class OpenShiftService {
}
// if we have a resourceName only return the items matching that
if (resourceName != null) {
def filteredPods= pods.findAll { it.podName.startsWith(resourceName) }
def filteredPods = pods.findAll { it.podName.startsWith(resourceName) }
return filteredPods
}
return pods
Expand Down Expand Up @@ -1227,24 +1228,6 @@ class OpenShiftService {
)
}

private void reloginToCurrentClusterIfNeeded() {
def kubeUrl = steps.env.KUBERNETES_MASTER ?: 'https://kubernetes.default:443'
def success = steps.sh(
script: """
${logger.shellScriptDebugFlag}
oc login ${kubeUrl} --insecure-skip-tls-verify=true \
--token=\$(cat /run/secrets/kubernetes.io/serviceaccount/token) &> /dev/null
""",
returnStatus: true,
label: 'Check if OCP session exists'
) == 0
if (!success) {
throw new RuntimeException(
'Could not (re)login to cluster, this is a systemic failure'
)
}
}

private void importImageFromProject(
String project,
String sourceProject,
Expand Down Expand Up @@ -1463,4 +1446,22 @@ class OpenShiftService {
returnStdout: true
).toString().trim()
}

void reloginToCurrentClusterIfNeeded() {
def kubeUrl = steps.env.KUBERNETES_MASTER ?: 'https://kubernetes.default:443'
def success = steps.sh(
script: """
${logger.shellScriptDebugFlag}
oc login ${kubeUrl} --insecure-skip-tls-verify=true \
--token=\$(cat /run/secrets/kubernetes.io/serviceaccount/token) &> /dev/null
""",
returnStatus: true,
label: 'Check if OCP session exists'
) == 0
if (!success) {
throw new RuntimeException(
'Could not (re)login to cluster, this is a systemic failure'
)
}
}
}
Loading