Skip to content

Commit a3f08c1

Browse files
authored
Merge branch 'opendevstack:master' into patch-1
2 parents de48ac6 + 4836a29 commit a3f08c1

File tree

11 files changed

+158
-28
lines changed

11 files changed

+158
-28
lines changed

.devcontainer/devcontainer.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"image": "gradle:jdk11-focal",
3+
"containerEnv": {
4+
},
5+
"features": {
6+
"ghcr.io/devcontainers/features/go:1": {"version": "1.15"}
7+
}
8+
}

.github/workflows/changelog-enforcer.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
changelog:
99
runs-on: ubuntu-latest
1010
steps:
11-
- uses: actions/checkout@v4.2.1
11+
- uses: actions/checkout@v4.2.2
1212
- uses: dangoslen/changelog-enforcer@v3
1313
with:
1414
changeLogPath: 'CHANGELOG.md'

.github/workflows/gradle.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ jobs:
77
runs-on: ubuntu-latest
88
steps:
99
- name: Checkout
10-
uses: actions/checkout@v4.2.1
10+
uses: actions/checkout@v4.2.2
1111

1212
- name: check the value of github.workspace and runner.temp
1313
run: |

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@
33
## Unreleased
44

55
### Added
6+
* add devcontainer setup ([#1172](https://github.com/opendevstack/ods-jenkins-shared-library/issues/1172))
67

78
### Changed
89
* Enhance SSDS Document Generation Performance using New Atlassian APIs ([#1084](https://github.com/opendevstack/ods-jenkins-shared-library/issues/1084))
910
* Deprecation of vuln-type and scanners config in Trivy ([#1150](https://github.com/opendevstack/ods-jenkins-shared-library/issues/1150))
11+
* Add preserve-digests cli option to skopeo copy command in CopyImageStage ([#1166](https://github.com/opendevstack/ods-jenkins-shared-library/issues/1166))
12+
* Allow registry/image:tag sources in CopyImageStage instead of directly falling back to internal registry ([#1177](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1177))
13+
* Simplify successor management since now issue links are no longer inherited ([#1116](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1116))
1014

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

docs/modules/jenkins-shared-library/partials/odsComponentStageCopyImage.adoc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ _List<String>_
2727
Next to exact matches, it also supports prefixes (e.g. `feature/`) and all branches (`*`).
2828

2929

30+
| *preserveDigests* +
31+
_Boolean_
32+
|preserveDigests allows to sync the source and destination image digests
33+
34+
The default is false, set to true to preserve digests
35+
36+
3037
| *sourceCredential* +
3138
_String_
3239
|sourceCredential is the token to use, if any, to access the source registry

src/org/ods/component/CopyImageOptions.groovy

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ class CopyImageOptions extends Options {
2929
*/
3030
Boolean verifyTLS
3131

32+
/**
33+
* preserveDigests allows to sync the source and destination image digests
34+
*
35+
* The default is false, set to true to preserve digests
36+
*/
37+
Boolean preserveDigests
38+
3239
@SuppressWarnings('UnusedPrivateField')
3340
private String registry
3441
@SuppressWarnings('UnusedPrivateField')

src/org/ods/component/CopyImageStage.groovy

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ class CopyImageStage extends Stage {
2727
config.verifyTLS = true
2828
}
2929

30+
if (config.preserveDigests == null) {
31+
config.preserveDigests = false
32+
}
33+
3034
this.options = new CopyImageOptions(config)
3135
this.openShift = openShift
3236
this.logger = logger
@@ -52,7 +56,10 @@ class CopyImageStage extends Stage {
5256

5357
def sourcetoken = options.sourceCredential ? "--src-creds ${options.sourceCredential}" : ''
5458

55-
int status = copyImage(sourcetoken, targetInternalRegistryToken, STR_DOCKER_PROTOCOL)
59+
def copyparams = ""
60+
if (this.options.preserveDigests) { copyparams += "--all --preserve-digests" }
61+
62+
int status = copyImage(sourcetoken, targetInternalRegistryToken, STR_DOCKER_PROTOCOL, copyparams)
5663
if (status != 0) {
5764
script.error("Could not copy `${this.options.sourceImageUrlIncludingRegistry}', status ${status}")
5865
}
@@ -83,11 +90,15 @@ class CopyImageStage extends Stage {
8390
}
8491
}
8592

86-
private int copyImage(sourcetoken, targetInternalRegistryToken, String dockerProtocol) {
93+
private int copyImage(sourcetoken, targetInternalRegistryToken, String dockerProtocol, String copyparams) {
94+
def registryPath = this.options.repo ? \
95+
"${this.options.registry}/${this.options.repo}/${this.options.image}" : \
96+
"${this.options.registry}/${this.options.image}"
8797
int status = steps.sh(
8898
script: """
89-
skopeo copy --src-tls-verify=${this.options.verifyTLS} ${sourcetoken} \
90-
${this.options.registry}/${this.options.repo}/${this.options.image} \
99+
skopeo copy ${copyparams} \
100+
--src-tls-verify=${this.options.verifyTLS} ${sourcetoken} \
101+
${registryPath} \
91102
--dest-creds openshift:${targetInternalRegistryToken} \
92103
${dockerProtocol}${context.clusterRegistryAddress}/${context.cdProject}/${this.options.image} \
93104
--dest-tls-verify=${this.options.verifyTLS}
@@ -99,9 +110,12 @@ class CopyImageStage extends Stage {
99110
}
100111

101112
protected String stageLabel() {
113+
def registryPath = this.options.repo ? \
114+
"${this.options.registry}/${this.options.repo}/${this.options.image}" : \
115+
"${this.options.registry}/${this.options.image}"
102116
return "${STAGE_NAME} " +
103117
"(${context.componentId}) " +
104-
"${this.options.registry}/${this.options.repo}/${this.options.image}'"
118+
"${registryPath}'"
105119
}
106120

107121
}

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

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1301,6 +1301,8 @@ class Project {
13011301
logger.info("loadJiraData: Found a predecessor project version with ID '${previousVersionId}'. Loading its data.")
13021302
def savedDataFromOldVersion = this.loadSavedJiraData(previousVersionId)
13031303
def mergedData = this.mergeJiraData(savedDataFromOldVersion, newData)
1304+
mergedData = this.overrideDeltaDocgenDataLinks(mergedData, newData)
1305+
mergedData = this.removeObsoleteIssuesFromComponents(mergedData)
13041306
result << this.addKeyAndVersionToComponentsWithout(mergedData)
13051307
result.previousVersion = previousVersionId
13061308
} else {
@@ -1349,6 +1351,75 @@ class Project {
13491351
}
13501352
}
13511353

1354+
/**
1355+
* It uses the data from the deltadocgen of the latest version as a source of truth in terms of links.
1356+
* If an issue appears in the deltadocgen report, we use all its data adding the expanded predecessors.
1357+
* If an issue appears as a link in the old data but in the deltadocgen report doesn't show the same link in the other
1358+
* direction, then we remove that link.
1359+
*
1360+
* @param mergedData resulting data of merging last release json report and deltadocgen
1361+
* @param deltaDocgenData result of deltadocgen endpoint for the latest version
1362+
* @return the merged data with the proper links
1363+
*/
1364+
protected Map overrideDeltaDocgenDataLinks(Map<String,Map> mergedData, Map<String,Map> deltaDocgenData) {
1365+
mergedData.findAll { JiraDataItem.REGULAR_ISSUE_TYPES.contains(it.key) }.each { issueType, issues ->
1366+
issues.values().each { Map issueToUpdate ->
1367+
if(deltaDocgenData[issueType] && deltaDocgenData[issueType][issueToUpdate.key]) {
1368+
def resultData = deltaDocgenData[issueType][issueToUpdate.key]
1369+
resultData << [expandedPredecessors: mergedData[issueType][issueToUpdate.key]['expandedPredecessors']]
1370+
mergedData[issueType][issueToUpdate.key] = resultData
1371+
} else {
1372+
mergedData[issueType][issueToUpdate.key].findAll { JiraDataItem.REGULAR_ISSUE_TYPES.contains(it.key) }.each { relatedIssueType, relatedIssues ->
1373+
def relatedIssuesToRemove = findRelatedIssuesToRemove(relatedIssues, deltaDocgenData, relatedIssueType, issueType, issueToUpdate)
1374+
mergedData[issueType][issueToUpdate.key][relatedIssueType].removeAll { relatedIssuesToRemove.contains(it) }
1375+
}
1376+
}
1377+
}
1378+
}
1379+
return mergedData
1380+
}
1381+
1382+
/**
1383+
* The method checks each issue in the 'relatedIssues' list against the related issues in the 'deltaDocgenData'.
1384+
* If the issueToUpdate key does not appear in the deltaDocgenData as a related issue but the deltaDocGen has some
1385+
* issues related for that issue type, the issue is added to the list of issues to be removed.
1386+
*
1387+
* @param relatedIssues A list of related issues to be examined.
1388+
* @param deltaDocgenData A map containing data from the deltaDocGen
1389+
* @param relatedIssueType The type of the related issue.
1390+
* @param issueType The type of the issue.
1391+
* @param issueToUpdate A map containing the issue to be updated.
1392+
*
1393+
* @return A list of related issues that need to be removed.
1394+
*/
1395+
protected static List findRelatedIssuesToRemove(List<String> relatedIssues, Map deltaDocgenData, String relatedIssueType, String issueType, Map issueToUpdate) {
1396+
def relatedIssuesToRemove = []
1397+
relatedIssues.each {
1398+
if (deltaDocgenData[relatedIssueType][it] && deltaDocgenData[relatedIssueType][it][issueType] && !deltaDocgenData[relatedIssueType][it][issueType].contains(issueToUpdate.key)) {
1399+
relatedIssuesToRemove.add(it)
1400+
}
1401+
}
1402+
return relatedIssuesToRemove
1403+
}
1404+
1405+
/**
1406+
* It removes any issue in the components map that does not appear under the technology map it should belong
1407+
*
1408+
* @param mergedData resulting data of merging last release json report and deltadocgen
1409+
* @return the merged data with the proper issues in the components map
1410+
*/
1411+
protected Map removeObsoleteIssuesFromComponents(Map<String,Map> mergedData) {
1412+
mergedData[JiraDataItem.TYPE_COMPONENTS].collectEntries { component, componentIssues ->
1413+
JiraDataItem.REGULAR_ISSUE_TYPES.each { issueType ->
1414+
if(componentIssues[issueType]) {
1415+
componentIssues[issueType].removeAll { !mergedData[issueType].keySet().contains(it) }
1416+
}
1417+
}
1418+
[(component): componentIssues]
1419+
}
1420+
return mergedData
1421+
}
1422+
13521423
protected Map loadJiraDataBugs(Map tests, String versionName = null) {
13531424
if (!this.jiraUseCase) return [:]
13541425
if (!this.jiraUseCase.jira) return [:]

test/groovy/org/ods/orchestration/util/ProjectSpec.groovy

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2319,7 +2319,6 @@ class ProjectSpec extends SpecHelper {
23192319
def firstVersion = '1.0'
23202320
def secondVersion = '2.0'
23212321

2322-
def cmp ={ name -> [key: "CMP-${name}" as String, name: "Component 1"]}
23232322
def req = { name, String version = null -> [key: "REQ-${name}" as String, description:name, versions:[version]] }
23242323
def ts = { name, String version = null -> [key: "TS-${name}" as String, description:name, versions:[version]] }
23252324
def rsk = { name, String version = null -> [key: "RSK-${name}" as String, description:name, versions:[version]] }
@@ -2345,23 +2344,23 @@ class ProjectSpec extends SpecHelper {
23452344
ts1 << [requirements: [req1.key], tests: [tst1.key, tst2.key]]
23462345
rsk1 << [requirements: [req1.key], mitigations: [mit1.key]]
23472346
mit1 << [requirements: [req1.key], risks: [rsk1.key]]
2348-
req2 << [predecessors: [req1.key], tests: [tst4.key]]
2349-
tst3 << [predecessors: [tst1.key]]
2347+
req2 << [predecessors: [req1.key], tests: [tst2.key,tst3.key,tst4.key], techSpecs: [ts2.key], risks: [rsk2.key], mitigations: [mit2.key]]
2348+
tst3 << [predecessors: [tst1.key], requirements: [req2.key], techSpecs: [ts2.key]]
23502349
tst4 << [requirements: [req2.key]]
2351-
rsk2 << [predecessors: [rsk1.key], requirements: [req1.key]]
2352-
mit2 << [predecessors: [mit1.key], requirements: [req1.key], risks: [rsk1.key]]
2353-
ts2 << [predecessors: [ts1.key]]
2350+
rsk2 << [predecessors: [rsk1.key], requirements: [req2.key], mitigations: [mit2.key]]
2351+
mit2 << [predecessors: [mit1.key], requirements: [req2.key], risks: [rsk2.key]]
2352+
ts2 << [predecessors: [ts1.key], requirements: [req2.key], tests: [tst2.key, tst3.key]]
23542353

2355-
def req2Updated = req2.clone() + [tests: [tst4.key, tst3.key, tst2.key], techSpecs: [ts2.key], risks: [rsk2.key], mitigations: [mit2.key]]
2354+
def req2Updated = req2.clone()
23562355
req2Updated << [expandedPredecessors: [[key: req1.key, versions: req1.versions]]]
2357-
def tst2Updated = tst2.clone() + [requirements: [req2.key], techSpecs: [ts2.key]]
2358-
def tst3Updated = tst3.clone() + [requirements: [req2.key], techSpecs: [ts2.key]]
2356+
def tst2Updated = tst2.clone()
2357+
def tst3Updated = tst3.clone()
23592358
tst3Updated << [expandedPredecessors: [[key: tst1.key, versions: tst1.versions]]]
2360-
def rsk2Updated = rsk2.clone() + [requirements: [req2.key], mitigations: [mit2.key]]
2359+
def rsk2Updated = rsk2.clone()
23612360
rsk2Updated << [expandedPredecessors: [[key: rsk1.key, versions: rsk1.versions]]]
2362-
def mit2Updated = mit2.clone() + [requirements: [req2.key], risks: [rsk2.key]]
2361+
def mit2Updated = mit2.clone()
23632362
mit2Updated << [expandedPredecessors: [[key: mit1.key, versions: mit1.versions]]]
2364-
def ts2Updated = ts2.clone() + [requirements: [req2.key], tests: [tst3.key, tst2.key]]
2363+
def ts2Updated = ts2.clone()
23652364
ts2Updated << [expandedPredecessors: [[key: ts1.key, versions: ts1.versions]]]
23662365

23672366
def storedData = [

test/groovy/vars/OdsComponentStageCopyImageSpec.groovy

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class OdsComponentStageCopyImageSpec extends PipelineSpockTestBase {
1919
def cfg = [
2020
sourceImageUrlIncludingRegistry: sourceImageUrlIncludingRegistry,
2121
verifyTLS : verifyTLS,
22+
preserveDigests : preserveDigests,
2223
tagIntoTargetEnv : tagIntoTargetEnv,
2324
]
2425
def ctxCfg = [
@@ -51,7 +52,8 @@ class OdsComponentStageCopyImageSpec extends PipelineSpockTestBase {
5152
assertCallStackContains("Resolved source Image data: ${sourceImageUrlIncludingRegistry}")
5253
assertCallStackContains("importing into: docker://internal-registry/project-cd/${imageName}:${imageTag}")
5354
// FIXME: this should probably verify that the steps.sh is called with the correct string rather than checking the full callstack
54-
assertCallStackContains("skopeo copy --src-tls-verify=${expectedVerifyTLS} docker://${sourceImageUrlIncludingRegistry} --dest-creds openshift:secret-token docker://internal-registry/project-cd/image:1f3d1 --dest-tls-verify=${expectedVerifyTLS}")
55+
assertCallStackContains("skopeo copy ${expectedCopyParams}")
56+
assertCallStackContains("--src-tls-verify=${expectedVerifyTLS} docker://${sourceImageUrlIncludingRegistry} --dest-creds openshift:secret-token docker://internal-registry/project-cd/image:1f3d1 --dest-tls-verify=${expectedVerifyTLS}")
5557
if (tagIntoTargetEnv) {
5658
1 * openShiftService.importImageTagFromProject('project-dev', imageName, 'project-cd', imageTag, imageTag)
5759
1 * openShiftService.findOrCreateImageStream('project-dev', imageName)
@@ -60,13 +62,13 @@ class OdsComponentStageCopyImageSpec extends PipelineSpockTestBase {
6062
assertJobStatusSuccess()
6163

6264
where:
63-
registry || repo || imageName || imageTag || verifyTLS || expectedVerifyTLS || tagIntoTargetEnv
64-
'example.com' || 'repo' || 'image' || '1f3d1' || true || true || true
65-
'example.com' || 'repo' || 'image' || '1f3d1' || true || true || false
66-
'example.com' || 'repo' || 'image' || '1f3d1' || false || false || true
67-
'example.com' || 'repo' || 'image' || '1f3d1' || false || false || false
68-
'example.com' || 'repo' || 'image' || '1f3d1' || null || true || true
69-
'example.com' || 'repo' || 'image' || '1f3d1' || null || true || false
65+
registry || repo || imageName || imageTag || preserveDigests || expectedCopyParams || verifyTLS || expectedVerifyTLS || tagIntoTargetEnv
66+
'example.com' || 'repo' || 'image' || '1f3d1' || true || '--all --preserve-digests ' || true || true || true
67+
'example.com' || 'repo' || 'image' || '1f3d1' || true || '--all --preserve-digests ' || true || true || false
68+
'example.com' || 'repo' || 'image' || '1f3d1' || false || '' || false || false || true
69+
'example.com' || 'repo' || 'image' || '1f3d1' || false || '' || false || false || false
70+
'example.com' || 'repo' || 'image' || '1f3d1' || null || '' || null || true || true
71+
'example.com' || 'repo' || 'image' || '1f3d1' || null || '' || null || true || false
7072

7173
}
7274
}

0 commit comments

Comments
 (0)