Skip to content

Commit aa16238

Browse files
authored
Verify cluster configuration in metatdata.yml (#1222)
Implement env config validation.
1 parent 57e77f2 commit aa16238

14 files changed

+475
-87
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77

88
### Changed
9+
* Enforce prod config in metadata.yml ([#1222](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1222))
910
* Helm parity with Tailor ([#1219](https://github.com/opendevstack/ods-jenkins-shared-library/issues/1219))
1011

1112

src/org/ods/orchestration/DeployStage.groovy

Lines changed: 10 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ class DeployStage extends Stage {
2222

2323
@SuppressWarnings(['ParameterName', 'AbcMetric', 'MethodSize', 'LineLength'])
2424
def run() {
25-
def steps = ServiceRegistry.instance.get(IPipelineSteps)
2625
def levaDocScheduler = ServiceRegistry.instance.get(LeVADocumentScheduler)
2726
def os = ServiceRegistry.instance.get(OpenShiftService)
2827
def util = ServiceRegistry.instance.get(MROPipelineUtil)
@@ -85,43 +84,18 @@ class DeployStage extends Stage {
8584

8685
runOnAgentPod(agentPodCondition) {
8786
if (project.isPromotionMode) {
88-
def targetEnvironment = project.buildParams.targetEnvironment
89-
def targetProject = project.targetProject
90-
def installableRepos = this.project.repositories.findAll { repo ->
91-
// We only manage the installable repositories in OpenShift if they are included in the release
92-
if (repo.include
93-
&& repo.type?.toLowerCase() != MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_INFRA) {
94-
MROPipelineUtil.PipelineConfig.INSTALLABLE_REPO_TYPES.contains(repo.type)
95-
}
96-
}
97-
logger.info("Deploying project '${project.key}' into environment '${targetEnvironment}'" +
98-
" installable repos? ${installableRepos.size()}")
87+
def installableRepos = util.getInstallableRepos()
9988

100-
if (installableRepos?.size() > 0) {
101-
if (project.targetClusterIsExternal) {
102-
logger.info("Target cluster is external, logging into ${project.openShiftTargetApiUrl}")
103-
script.withCredentials([
104-
script.usernamePassword(
105-
credentialsId: project.environmentConfig.credentialsId,
106-
usernameVariable: 'EXTERNAL_OCP_API_SA',
107-
passwordVariable: 'EXTERNAL_OCP_API_TOKEN'
108-
)
109-
]) {
110-
OpenShiftService.loginToExternalCluster(
111-
steps,
112-
project.openShiftTargetApiUrl,
113-
script.EXTERNAL_OCP_API_TOKEN
114-
)
115-
}
116-
}
89+
logger.info("Verify project deployment '${project.key}' into environment " +
90+
"'${project.buildParams.targetEnvironment}' installable repos? ${installableRepos?.size()}")
11791

118-
// Check if the target environment exists in OpenShift
119-
if (!os.envExists(targetProject)) {
120-
throw new RuntimeException(
121-
"Error: target environment '${targetProject}' does not exist " +
122-
"in ${project.openShiftTargetApiUrl}."
123-
)
124-
}
92+
if (installableRepos?.size() > 0) {
93+
util.verifyEnvLoginAndExistence(script,
94+
os,
95+
project.targetProject,
96+
project.data.openshift?.sessionApiUrl,
97+
project.data.openshift?.targetApiUrl,
98+
project.environmentConfig?.credentialsId)
12599
}
126100
}
127101

src/org/ods/orchestration/InitStage.groovy

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,8 @@ class InitStage extends Stage {
135135
String stageToStartAgent = findBestPlaceToStartAgent(repos, logger)
136136

137137
// Compute target project. For now, the existance of DEV on the same cluster is verified.
138-
def concreteEnv = Project.getConcreteEnvironment(
139-
project.buildParams.targetEnvironment,
140-
project.buildParams.version.toString(),
141-
project.versionedDevEnvsEnabled
142-
)
143-
def targetProject = "${project.key}-${concreteEnv}"
138+
def targetProject = Project.getTargetProjectForEnv(project, project.buildParams.targetEnvironment)
139+
144140
def os = registry.get(OpenShiftService)
145141
if (project.buildParams.targetEnvironment == 'dev' && !os.envExists(targetProject)) {
146142
throw new RuntimeException(
@@ -150,13 +146,78 @@ class InitStage extends Stage {
150146
}
151147
project.setTargetProject(targetProject)
152148

149+
if (util.getInstallableRepos()?.size() > 0) {
150+
validateEnvConfig(logger, registry, util)
151+
} else {
152+
logger.debug("No installable repos found, skipping env existence check.")
153+
}
154+
153155
logger.debug 'Compute groups of repository configs for convenient parallelization'
154156
repos = util.computeRepoGroups(repos)
155157
registry.get(LeVADocumentScheduler).run(phase, PipelinePhaseLifecycleStage.PRE_END)
156158

157159
return [project: project, repos: repos, startAgent: stageToStartAgent]
158160
}
159161

162+
protected void validateEnvConfig(Logger logger, ServiceRegistry registry, MROPipelineUtil util) {
163+
logger.debug("Validate environment metadata.yml config started")
164+
def os = registry.get(OpenShiftService)
165+
Map envs = project.getEnvironments()
166+
def wronglyConfiguredEnvs = []
167+
boolean reloginRequired = false
168+
try {
169+
for (MROPipelineUtil.PipelineEnv env : MROPipelineUtil.PipelineEnv.values()) {
170+
def targetProjectForEnv = Project.getTargetProjectForEnv(project, env.value)
171+
logger.debug("Check cluster config for env ${env.value} and project ${targetProjectForEnv}")
172+
try {
173+
String openshiftClusterApiUrl = envs."$env.value"?.apiUrl
174+
?: envs."$env.value"?.openshiftClusterApiUrl
175+
String openshiftClusterCredentialsId = envs."$env.value"?.credentialsId
176+
?: envs."$env.value"?.openshiftClusterCredentialsId
177+
if (!openshiftClusterApiUrl && !openshiftClusterCredentialsId) {
178+
if (!os.envExists(targetProjectForEnv)) {
179+
wronglyConfiguredEnvs.add(env.value)
180+
}
181+
} else {
182+
reloginRequired = true
183+
util.verifyEnvLoginAndExistence(script,
184+
os,
185+
targetProjectForEnv,
186+
project.data.openshift.sessionApiUrl,
187+
openshiftClusterApiUrl,
188+
openshiftClusterCredentialsId
189+
)
190+
}
191+
} catch (Exception e) {
192+
wronglyConfiguredEnvs.add(env.value)
193+
}
194+
}
195+
if (wronglyConfiguredEnvs.size() > 0) {
196+
String message = "The Release Manager configuration for environment(s) " +
197+
"${wronglyConfiguredEnvs.join(', ')} is incorrect in the metadata.yml. " +
198+
"Please verify the openshift cluster api url and credentials for " +
199+
"each environment mentioned."
200+
if (project.isWorkInProgress) { // Warn build pipeline in this case
201+
project.addCommentInReleaseStatus(message)
202+
util.warnBuild(message)
203+
} else { // Error
204+
util.failBuild(message)
205+
throw new RuntimeException(message) // This also add a comment in the release status issue
206+
}
207+
}
208+
} finally {
209+
if (reloginRequired) {
210+
logger.debug("Try to relogin to current cluster")
211+
try {
212+
os.reloginToCurrentClusterIfNeeded()
213+
} catch (ex) {
214+
logger.error("Error logging back to current cluster ${ex.getMessage()}")
215+
}
216+
logger.debug("Success relogging in to current cluster.")
217+
}
218+
}
219+
}
220+
160221
private String findBestPlaceToStartAgent(List<Map> repos, ILogger logger) {
161222
String stageToStartAgent
162223
repos.each { repo ->

src/org/ods/orchestration/phases/DeployOdsComponent.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ class DeployOdsComponent {
271271
def imageInfo = imageParts.last().split('@')
272272
def imageName = imageInfo.first()
273273
def imageSha = imageInfo.last()
274-
if (project.targetClusterIsExternal) {
274+
if (project.targetClusterExternal) {
275275
os.importImageShaFromSourceRegistry(
276276
project.targetProject,
277277
imageName,

src/org/ods/orchestration/usecase/JiraUseCase.groovy

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,10 @@ class JiraUseCase {
427427
}
428428

429429
void addCommentInReleaseStatus(String message) {
430+
if (!this.jira) {
431+
logger.debug("Jira not connected, cannot append comment: ${message}")
432+
return
433+
}
430434
def changeId = this.project.buildParams.changeId
431435
if (message) {
432436
def projectKey = this.project.jiraProjectKey

src/org/ods/orchestration/util/MROPipelineUtil.groovy

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,21 @@ class MROPipelineUtil extends PipelineUtil {
4646
]
4747
}
4848

49-
class PipelineEnvs {
50-
static final String DEV = "dev"
51-
static final String QA = "qa"
52-
static final String PROD = "prod"
49+
enum PipelineEnv {
50+
51+
DEV("dev"),
52+
QA("qa"),
53+
PROD("prod")
54+
55+
private String value
56+
57+
String getValue() {
58+
return value
59+
}
60+
61+
private PipelineEnv(String value) {
62+
this.value = value
63+
}
5364
}
5465

5566
class PipelinePhases {

src/org/ods/orchestration/util/PipelineUtil.groovy

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import com.cloudbees.groovy.cps.NonCPS
77

88
import net.lingala.zip4j.ZipFile
99
import net.lingala.zip4j.model.ZipParameters
10-
10+
import org.ods.services.OpenShiftService
1111
import org.ods.util.IPipelineSteps
1212
import org.ods.util.ILogger
1313
import org.ods.services.GitService
@@ -184,4 +184,44 @@ class PipelineUtil {
184184
return this.steps.load(path)
185185
}
186186

187+
List getInstallableRepos() {
188+
return this.project.repositories.findAll { repo ->
189+
// We only manage the installable repositories in OpenShift if they are included in the release
190+
if (repo.include
191+
&& repo.type?.toLowerCase() != MROPipelineUtil.PipelineConfig.REPO_TYPE_ODS_INFRA) {
192+
MROPipelineUtil.PipelineConfig.INSTALLABLE_REPO_TYPES.contains(repo.type)
193+
}
194+
}
195+
}
196+
197+
@SuppressWarnings('ParameterCount')
198+
def verifyEnvLoginAndExistence(def script, OpenShiftService os, def targetProject, def sessionApiUrl,
199+
def targetApiUrl, def credentialsId) {
200+
201+
if (Project.isTargetClusterExternal(sessionApiUrl, targetApiUrl)) {
202+
logger.info("Target cluster is external, logging into ${targetApiUrl}")
203+
script.withCredentials([
204+
script.usernamePassword(
205+
credentialsId: credentialsId,
206+
usernameVariable: 'EXTERNAL_OCP_API_SA',
207+
passwordVariable: 'EXTERNAL_OCP_API_TOKEN'
208+
)
209+
]) {
210+
OpenShiftService.loginToExternalCluster(
211+
steps,
212+
targetApiUrl,
213+
script.EXTERNAL_OCP_API_TOKEN
214+
)
215+
}
216+
}
217+
218+
// Check if the target environment exists in OpenShift
219+
if (!os.envExists(targetProject)) {
220+
throw new RuntimeException(
221+
"Error: target environment '${targetProject}' does not exist " +
222+
"in ${targetApiUrl}."
223+
)
224+
}
225+
}
226+
187227
}

src/org/ods/orchestration/util/Project.groovy

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,6 @@ class Project {
299299
protected Boolean isVersioningEnabled = false
300300
private String _gitReleaseBranch
301301

302-
303302
private TestResults aggregatedTestResults;
304303

305304
protected Map data = [:]
@@ -720,7 +719,7 @@ class Project {
720719

721720
void setOpenShiftData(String sessionApiUrl) {
722721
def envConfig = getEnvironmentConfig()
723-
def targetApiUrl = envConfig?.apiUrl
722+
def targetApiUrl = envConfig?.apiUrl ?: envConfig?.openshiftClusterApiUrl
724723
if (!targetApiUrl) {
725724
targetApiUrl = sessionApiUrl
726725
}
@@ -730,10 +729,13 @@ class Project {
730729
}
731730

732731
@NonCPS
733-
boolean getTargetClusterIsExternal() {
732+
boolean isTargetClusterExternal() {
733+
return isTargetClusterExternal(this.data.openshift.sessionApiUrl, this.data.openshift.targetApiUrl)
734+
}
735+
736+
@NonCPS
737+
static boolean isTargetClusterExternal(def sessionApiUrl, def targetApiUrl) {
734738
def isExternal = false
735-
def sessionApiUrl = this.data.openshift.sessionApiUrl
736-
def targetApiUrl = this.data.openshift.targetApiUrl
737739
def targetApiUrlMatcher = targetApiUrl =~ /:[0-9]+$/
738740
if (targetApiUrlMatcher.find()) {
739741
isExternal = sessionApiUrl != targetApiUrl
@@ -779,6 +781,15 @@ class Project {
779781
environment
780782
}
781783

784+
static String getTargetProjectForEnv(Project project, String env) {
785+
def concreteEnv = Project.getConcreteEnvironment(
786+
env,
787+
project.buildParams.version.toString(),
788+
project.versionedDevEnvsEnabled
789+
)
790+
return "${project.key}-${concreteEnv}"
791+
}
792+
782793
static List<String> getBuildEnvironment(IPipelineSteps steps, boolean debug = false, boolean versionedDevEnvsEnabled = false) {
783794
def params = loadBuildParams(steps)
784795

src/org/ods/services/OpenShiftService.groovy

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,7 +1058,7 @@ class OpenShiftService {
10581058
}
10591059
// if we have a resourceName only return the items matching that
10601060
if (resourceName != null) {
1061-
def filteredPods= pods.findAll { it.podName.startsWith(resourceName) }
1061+
def filteredPods = pods.findAll { it.podName.startsWith(resourceName) }
10621062
return filteredPods
10631063
}
10641064
return pods
@@ -1228,24 +1228,6 @@ class OpenShiftService {
12281228
)
12291229
}
12301230

1231-
private void reloginToCurrentClusterIfNeeded() {
1232-
def kubeUrl = steps.env.KUBERNETES_MASTER ?: 'https://kubernetes.default:443'
1233-
def success = steps.sh(
1234-
script: """
1235-
${logger.shellScriptDebugFlag}
1236-
oc login ${kubeUrl} --insecure-skip-tls-verify=true \
1237-
--token=\$(cat /run/secrets/kubernetes.io/serviceaccount/token) &> /dev/null
1238-
""",
1239-
returnStatus: true,
1240-
label: 'Check if OCP session exists'
1241-
) == 0
1242-
if (!success) {
1243-
throw new RuntimeException(
1244-
'Could not (re)login to cluster, this is a systemic failure'
1245-
)
1246-
}
1247-
}
1248-
12491231
private void importImageFromProject(
12501232
String project,
12511233
String sourceProject,
@@ -1469,4 +1451,22 @@ class OpenShiftService {
14691451
returnStdout: true
14701452
).toString().trim()
14711453
}
1454+
1455+
void reloginToCurrentClusterIfNeeded() {
1456+
def kubeUrl = steps.env.KUBERNETES_MASTER ?: 'https://kubernetes.default:443'
1457+
def success = steps.sh(
1458+
script: """
1459+
${logger.shellScriptDebugFlag}
1460+
oc login ${kubeUrl} --insecure-skip-tls-verify=true \
1461+
--token=\$(cat /run/secrets/kubernetes.io/serviceaccount/token) &> /dev/null
1462+
""",
1463+
returnStatus: true,
1464+
label: 'Check if OCP session exists'
1465+
) == 0
1466+
if (!success) {
1467+
throw new RuntimeException(
1468+
'Could not (re)login to cluster, this is a systemic failure'
1469+
)
1470+
}
1471+
}
14721472
}

0 commit comments

Comments
 (0)