Skip to content

4.x Partial rebuild fix #978

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

Open
wants to merge 50 commits into
base: 4.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
54fd402
Move targetProject up
Mar 10, 2023
dcfb836
add information on artifact created
Mar 10, 2023
ddb0ebf
debug on downloaded file
Mar 10, 2023
b96bc9b
more debug on why not downloaded
Mar 10, 2023
bf8a0ad
() missing
Mar 10, 2023
3806f76
on success
Mar 10, 2023
b2c0b3e
NexusService.groovy online editiert mit Bitbucket
Mar 10, 2023
0a05777
iffailure - throw
Mar 10, 2023
c66b99b
cater for parsing exception
Mar 10, 2023
ec91ce8
NexusService.groovy online editiert mit Bitbucket
Mar 10, 2023
03b74f2
surface parsing exception
Mar 10, 2023
685b086
use dynamic path
Mar 10, 2023
bae1076
NexusService.groovy online editiert mit Bitbucket
Mar 10, 2023
df69789
NexusService.groovy online editiert mit Bitbucket
Mar 10, 2023
9d4aac5
force mkdir
Mar 10, 2023
ef8de29
spill out folder creation success
Mar 10, 2023
2b1f004
hack to create dir
Mar 10, 2023
97dac42
skip for resurrect
Mar 10, 2023
0ccc0be
return on resurrect
Mar 10, 2023
110fd8c
add current commit Emptxy check
Mar 10, 2023
285b947
naming, and check for lenght
Mar 10, 2023
cd18044
don't force commit - if there is nothing to add
Mar 10, 2023
02a745b
missing comma
Mar 10, 2023
8f47d97
check for empty commit
Mar 10, 2023
6ab08f0
change logic a bit on dirty commit
Mar 10, 2023
66c41b5
add new commit logic
Mar 10, 2023
5d759c9
dont show untracked files
Mar 10, 2023
0e0f9b8
... return
Mar 10, 2023
f0ff453
cleanup
Mar 10, 2023
29fa91d
Cleanup Nexus service
Mar 10, 2023
8f6d9ed
Cleanup
Mar 10, 2023
99f54a4
cleanup
Mar 10, 2023
e6e5b78
comment finalize for ods-tst
Mar 10, 2023
62aec20
remove finalize NON ods component
Mar 10, 2023
afaa704
add ods-service to eligable ones
Mar 10, 2023
8de1e41
finally fix this double error
Mar 10, 2023
24935c6
correct inclusion of eligable repo type
Mar 10, 2023
c5c7c89
allow partials also for assembly mode
Mar 10, 2023
54f10e7
2nd fix to let the root error bubble
Mar 10, 2023
45a446d
fix2 for double errors when jenkins overwrites the original exception
Mar 10, 2023
62d12d4
codenarc
clemensutschig Mar 11, 2023
10ec6e2
changelog and codenarc
clemensutschig Mar 11, 2023
d568cc9
more codenarc
clemensutschig Mar 11, 2023
44d5d0a
enforce directory to be there
clemensutschig Mar 11, 2023
807dedf
use correct extractionpath2
clemensutschig Mar 11, 2023
11623cb
fix regression on map key
clemensutschig Mar 11, 2023
87081de
type fun with URI
clemensutschig Mar 11, 2023
83013a9
do NOT make this the global default
clemensutschig Mar 11, 2023
c4c64b1
codenarc
clemensutschig Mar 11, 2023
e9d7cc6
Merge branch '4.x' into partial_rebuild
BraisVQ Mar 21, 2023
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 @@ -3,6 +3,7 @@
## Unreleased

### Fixed
- allowPartialRebuild is broken ([#977](https://github.com/opendevstack/ods-jenkins-shared-library/pull/977))
- Memory leak fixes for component pipeline ([#857](https://github.com/opendevstack/ods-jenkins-shared-library/issues/857))

## [4.2.0] - 2023-02-21
Expand Down
6 changes: 6 additions & 0 deletions src/org/ods/component/Pipeline.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,12 @@ class Pipeline implements Serializable {
if (!this.bitbucketNotificationEnabled) {
return
}
// in some nasty nullpointer cases - jenkins suddenly nullifies this bitbucket service?
// which in case a previous error, will push the nullp up the stack, and hide the "real" error
if (!bitbucketService) {
logger.warn "Cannot set Bitbucket build status to '${state}' as bitbucket service is null!"
return
}
if (!context.buildUrl || !context.gitCommit) {
logger.info "Cannot set Bitbucket build status to '${state}' because required data is missing!"
return
Expand Down
36 changes: 20 additions & 16 deletions src/org/ods/orchestration/InitStage.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,26 @@ class InitStage extends Stage {

def repos = project.repositories
MROPipelineUtil util = registry.get(MROPipelineUtil)

// Compute target project. For now, the existance of DEV on the same cluster is verified.
// This must be done here - very early, otherwise the resurrection code will later fail
// as it depends on the target environment to check the artifacts there
def concreteEnv = Project.getConcreteEnvironment(
project.buildParams.targetEnvironment,
project.buildParams.version.toString(),
project.versionedDevEnvsEnabled
)
def targetProject = "${project.key}-${concreteEnv}"
def os = registry.get(OpenShiftService)
if (project.buildParams.targetEnvironment == 'dev' && !os.envExists(targetProject)) {
throw new RuntimeException(
"Target project ${targetProject} does not exist " +
"(versionedDevEnvsEnabled=${project.versionedDevEnvsEnabled})."
)
}
logger.debug "Computed targetProject: ${targetProject}"
project.setTargetProject(targetProject)

Closure checkoutClosure = buildCheckOutClousure(repos, logger, envState, util)
Closure<String> loadClosure = buildLoadClousure(logger, registry, buildParams, git, steps)
try {
Expand All @@ -104,22 +124,6 @@ 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 os = registry.get(OpenShiftService)
if (project.buildParams.targetEnvironment == 'dev' && !os.envExists(targetProject)) {
throw new RuntimeException(
"Target project ${targetProject} does not exist " +
"(versionedDevEnvsEnabled=${project.versionedDevEnvsEnabled})."
)
}
project.setTargetProject(targetProject)

logger.debug 'Compute groups of repository configs for convenient parallelization'
repos = util.computeRepoGroups(repos)
registry.get(LeVADocumentScheduler).run(phase, PipelinePhaseLifecycleStage.PRE_END)
Expand Down
1 change: 1 addition & 0 deletions src/org/ods/orchestration/phases/DeployOdsComponent.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -263,4 +263,5 @@ class DeployOdsComponent {
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ class FinalizeNonOdsComponent {
private String commitBuildReference() {
"${steps.currentBuild.description}\r${steps.env.BUILD_URL}"
}

}
7 changes: 6 additions & 1 deletion src/org/ods/orchestration/phases/FinalizeOdsComponent.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ class FinalizeOdsComponent {
@TypeChecked(TypeCheckingMode.SKIP)
public void run(Map repo, String baseDir) {
this.os = ServiceRegistry.instance.get(OpenShiftService)

logger.debug("Checking ${repo} for resurrection ... ")
// check if resurrected
if (repo.data?.openshift?.resurrectedBuild) {
logger.info("Skipping ${repo.id} as it was resurrected")
return
}
steps.dir(baseDir) {
Map deploymentMean = verifyDeploymentsBuiltByODS(repo).values().get(0) as Map
logger.debug("DeploymentMean: ${deploymentMean}")
Expand Down
6 changes: 6 additions & 0 deletions src/org/ods/orchestration/usecase/DocGenUseCase.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,12 @@ abstract class DocGenUseCase {
def basename = getDocumentBasename(
documentType, oldBuildVersion, buildVersionKey[1], repo)
def path = "${this.steps.env.WORKSPACE}/reports/${repo.id}"
// hack - classically it does not work, code returns false, dir not there
// def mkdirresult = new File(path).mkdir() -> returns false!
// so we have to go the jenkins version
this.steps.dir (path) {
this.steps.writeFile(file: 'dummy', text: 'dummy')
}

def fileExtensions = getFiletypeForDocumentType(documentType)
String storageType = fileExtensions.storage ?: 'zip'
Expand Down
32 changes: 17 additions & 15 deletions src/org/ods/orchestration/util/MROPipelineUtil.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -269,16 +269,14 @@ class MROPipelineUtil extends PipelineUtil {
]
def repoPath = "${this.steps.env.WORKSPACE}/${REPOS_BASE_DIR}/${repo.id}"
loadPipelineConfig(repoPath, repo)
if (this.project.isAssembleMode) {
if (this.project.forceGlobalRebuild) {
this.logger.debug('Project forces global rebuild ...')
} else {
this.steps.dir(repoPath) {
this.logger.startClocked("${repo.id}-resurrect-data")
this.logger.debug('Checking if repo can be resurrected from previous build ...')
amendRepoForResurrectionIfEligible(repo)
this.logger.debugClocked("${repo.id}-resurrect-data")
}
if (this.project.forceGlobalRebuild) {
this.logger.debug("Global force-rebuild configured for '${repo.id}'")
} else if (this.project.isAssembleMode || this.project.isWorkInProgress) {
this.steps.dir(repoPath) {
this.logger.startClocked("${repo.id}-resurrect-data")
this.logger.debug("Checking if repo '${repo.id}' can be resurrected from previous build(s) ...")
amendRepoForResurrectionIfEligible(repo)
this.logger.debugClocked("${repo.id}-resurrect-data")
}
}
}
Expand Down Expand Up @@ -394,15 +392,17 @@ class MROPipelineUtil extends PipelineUtil {
} else if (this.project.isPromotionMode && name == PipelinePhases.BUILD) {
executeODSComponent(repo, baseDir)
} else if (this.project.isAssembleMode && name == PipelinePhases.FINALIZE) {
new FinalizeNonOdsComponent(project, steps, git, logger).run(repo, baseDir)
this.logger.warn("Repo '${repo.id}' is of type ODS Infrastructure Component - SKIPPING FINALIZE!")
// new FinalizeNonOdsComponent(project, steps, git, logger).run(repo, baseDir)
} else {
this.logger.debug("Repo '${repo.id}' is of type ODS Infrastructure as Code Component/Configuration Management. Nothing to do in phase '${name}' for target environment'${targetEnvToken}'.")
}
} else if (repo.type?.toLowerCase() == PipelineConfig.REPO_TYPE_ODS_LIB) {
if (this.project.isAssembleMode && name == PipelinePhases.BUILD) {
executeODSComponent(repo, baseDir)
} else if (this.project.isAssembleMode && name == PipelinePhases.FINALIZE) {
new FinalizeNonOdsComponent(project, steps, git, logger).run(repo, baseDir)
this.logger.warn("Repo '${repo.id}' is of type ODS library Component - SKIPPING FINALIZE!")
// new FinalizeNonOdsComponent(project, steps, git, logger).run(repo, baseDir)
} else {
this.logger.debug("Repo '${repo.id}' is of type ODS library. Nothing to do in phase '${name}' for target environment'${targetEnvToken}'.")
}
Expand Down Expand Up @@ -431,7 +431,8 @@ class MROPipelineUtil extends PipelineUtil {
} else if (name == PipelinePhases.TEST) {
executeODSComponent(repo, baseDir)
} else if (this.project.isAssembleMode && name == PipelinePhases.FINALIZE) {
new FinalizeNonOdsComponent(project, steps, git, logger).run(repo, baseDir)
this.logger.warn("Repo '${repo.id}' is of type ODS Test Component - SKIPPING FINALIZE!")
// new FinalizeNonOdsComponent(project, steps, git, logger).run(repo, baseDir)
} else {
this.logger.debug("Repo '${repo.id}' is of type ODS Test Component. Nothing to do in phase '${name}' for target environment '${targetEnvToken}'.")
}
Expand Down Expand Up @@ -513,7 +514,8 @@ class MROPipelineUtil extends PipelineUtil {
private void amendRepoForResurrectionIfEligible(Map repo) {
def os = ServiceRegistry.instance.get(OpenShiftService)

if (repo.type?.toLowerCase() != PipelineConfig.REPO_TYPE_ODS_CODE) {
if (!repo.type?.toLowerCase() == PipelineConfig.REPO_TYPE_ODS_CODE &&
!repo.type?.toLowerCase() == PipelineConfig.REPO_TYPE_ODS_SERVICE) {
logger.info(
"Resurrection of previous build for '${repo.id}' not possible as " +
"type '${repo.type}' is not eligible."
Expand All @@ -524,7 +526,7 @@ class MROPipelineUtil extends PipelineUtil {
if (repo.forceRebuild) {
logger.info(
"Resurrection of previous build for '${repo.id}' not possible as " +
"repo '${repo.id}' is forced to rebuild."
"repo '${repo.id}' is forced to rebuild (configuration)."
)
return
}
Expand Down
24 changes: 24 additions & 0 deletions src/org/ods/services/GitService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ class GitService {
}

def commit(List files, String msg, boolean allowEmpty = true) {
if (allowEmpty && !files) {
logger.debug("Nothing to commit - skipping")
return
}
def allowEmptyFlag = allowEmpty ? '--allow-empty' : ''
def filesToAddCommand = "git add ${files.join(' ')}"
if (files.empty) {
Expand All @@ -215,6 +219,16 @@ class GitService {
script.sh(
script: """
${filesToAddCommand}
""",
label: 'Stash'
)
// check if the commit is dirty, so real changes happened - if not - skip
if (!isCommitDirty()) {
logger.debug("Commit stash is Not dirty - skipping")
return
}
script.sh(
script: """
git commit -m "${msg}" ${allowEmptyFlag}
""",
label: 'Commit'
Expand Down Expand Up @@ -337,6 +351,16 @@ class GitService {
script.sh("git checkout ${branchToMerge}")
}

def isCommitDirty () {
def content = script.sh(
script: "git status --porcelain -uno",
label: "get current commit stash",
returnStdout: true
).trim()
logger.debug("Check current commit stash: |${content}|")
return (content.length() > 0)
}

def checkoutNewLocalBranch(String name) {
// Local state might have a branch from previous, failed pipeline runs.
// If so, we'd rather start from a clean state.
Expand Down
11 changes: 8 additions & 3 deletions src/org/ods/services/NexusService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ class NexusService {
if (artifactExists) {
artifactExists.delete()
}
// NOTE: unnirest expects the directory tree to be present! MUST be created from outside
def response = restCall.asFile("${extractionPath}/${name}")

response.ifFailure {
Expand All @@ -161,14 +162,18 @@ class NexusService {
if (response.getStatus() == 404) {
message = "Error: unable to get artifact. Nexus could not be found at: '${urlToDownload}'."
}
// very weird, we get a 200 as failure with a good artifact, wtf.
if (response.getStatus() != 200) {

// if we get a 200 as failure with a good artifact, wtf. - parsing error?!
if (response.getStatus() == 200) {
throw new RuntimeException("Could not parse response from: ${urlToDownload}",
response.getParsingError().get())
} else {
throw new RuntimeException(message)
}
}

return [
uri: this.baseURL.resolve("/repository/${nexusRepository}/${nexusDirectory}/${name}"),
uri: new URI(urlToDownload),
content: response.getBody(),
]
}
Expand Down
5 changes: 4 additions & 1 deletion test/groovy/org/ods/services/NexusServiceSpec.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,12 @@ class NexusServiceSpec extends SpecHelper {

def server = createServer(WireMock.&get, request, response)
def service = createService(server.port(), request.username, request.password)
// this has changed appearently, nio file permissions?
def extractionPath = System.getProperty("java.io.tmpdir")
new File(extractionPath).mkdir()

when:
Map result = service.retrieveArtifact(request.data.repository, request.data.directory, request.data.name, "abc")
Map result = service.retrieveArtifact(request.data.repository, request.data.directory, request.data.name, extractionPath)

then:
result.uri == new URI("http://localhost:${server.port()}${request.path}")
Expand Down