diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5350178..7c29335 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,12 +1,8 @@ -# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file ---- +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + version: 2 updates: - - package-ecosystem: "maven" - directory: "/" + - package-ecosystem: github-actions + directory: / schedule: - interval: "weekly" - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" \ No newline at end of file + interval: monthly \ No newline at end of file diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 4ec641f..4ed532e 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -1,3 +1,2 @@ # https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.adoc -_extends: .github -tag-template: zdevops-$NEXT_MINOR_VERSION \ No newline at end of file +_extends: .github \ No newline at end of file diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml new file mode 100644 index 0000000..64a6693 --- /dev/null +++ b/.github/workflows/cd.yaml @@ -0,0 +1,15 @@ +# Note: additional setup is required, see https://www.jenkins.io/redirect/continuous-delivery-of-plugins + +name: cd +on: + workflow_dispatch: + check_run: + types: + - completed + +jobs: + maven-cd: + uses: jenkins-infra/github-reusable-workflows/.github/workflows/maven-cd.yml@v1 + secrets: + MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} + MAVEN_TOKEN: ${{ secrets.MAVEN_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/jenkins-security-scan.yml b/.github/workflows/jenkins-security-scan.yml new file mode 100644 index 0000000..6fc0b2b --- /dev/null +++ b/.github/workflows/jenkins-security-scan.yml @@ -0,0 +1,21 @@ +name: Jenkins Security Scan + +on: + push: + branches: + - main + pull_request: + types: [ opened, synchronize, reopened ] + workflow_dispatch: + +permissions: + security-events: write + contents: read + actions: read + +jobs: + security-scan: + uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2 + with: + java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate. + # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default. diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml deleted file mode 100644 index 281fade..0000000 --- a/.github/workflows/release-drafter.yml +++ /dev/null @@ -1,17 +0,0 @@ -# Note: additional setup is required, see https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.adoc - -name: Release Drafter - -on: - push: - branches: - - "main" - -jobs: - update_release_draft: - runs-on: ubuntu-latest - steps: - # Drafts your next Release notes as Pull Requests are merged into the default branch - - uses: release-drafter/release-drafter@v5 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml index 80a2e47..34fc756 100644 --- a/.mvn/extensions.xml +++ b/.mvn/extensions.xml @@ -2,6 +2,6 @@ io.jenkins.tools.incrementals git-changelist-maven-extension - 1.6 + 1.8 \ No newline at end of file diff --git a/.mvn/maven.config b/.mvn/maven.config index 2582cab..61cf4e5 100644 --- a/.mvn/maven.config +++ b/.mvn/maven.config @@ -1,3 +1,3 @@ -Pconsume-incrementals -Pmight-produce-incrementals --DaltDeploymentRepository=maven.jenkins-ci.org::default::https://repo.jenkins-ci.org/releases/ \ No newline at end of file +-Dchangelist.format=%d.v%s \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 91ab51b..b967071 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ All notable changes to the Zowe zDevOps Jenkins Plugin will be documented in thi * Feature: Added Jenkins Freestyle UI method - "Write text to member" ([4e4d73cc](https://github.com/zowe/zowe-zdevops-jenkins-plugin/commit/4e4d73cc)) * Feature: Added dataset member name validation ([b05c7436](https://github.com/zowe/zowe-zdevops-jenkins-plugin/commit/b05c7436)) * Feature: Added hpi-builder.yml GitHub workflow for automated .hpi builds ([c0fd9f27](https://github.com/zowe/zowe-zdevops-jenkins-plugin/commit/c0fd9f27)) +* Feature: Added failOnExist parameter for allocateDS, deleteDataset and deleteDatasetsByMask declarative/Freestyle UI methods (checks the presence/absence of a dataset on the system) ([db232f49](https://github.com/zowe/zowe-zdevops-jenkins-plugin/commit/db232f49)) ### Bugfixes diff --git a/Jenkinsfile b/Jenkinsfile index 12f6a8d..da6f699 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -8,4 +8,4 @@ * Copyright IBA Group 2022 */ -buildPlugin(useContainerAgent: true, tests: [[skip: 'true']], configurations: [[ platform: 'linux', jdk: '17' ]]) +buildPlugin(useContainerAgent: true, configurations: [[ platform: 'linux', jdk: '17' ]]) diff --git a/README.md b/README.md index 2ff73de..4e6173d 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Thank you for considering IBA Group for your mainframe needs. ## Before use - Plugin configuration After successfully installing the plugin, you need to configure it for further work - this will require a minimum of actions. -1. Move to “Manage Jenkins” -> “Configure System / System” -> scroll to the very bottom of the list of installed plugins and find the panel with the name - “z/OS Connection List” +1. Move to 'Manage Jenkins' -> 'Configure System / System' -> scroll to the very bottom of the list of installed plugins and find the panel with the name - 'z/OS Connection List' 2. This setting allows you to add all necessary z/OS systems and configure access to them. It is necessary to set the connection name (it is also the ID for declarative methods in the code). For the example: ```z/os-connection-name``` 3. The URL address and port of the required mainframe to connect via z/OSMF. Example: ```https://:``` @@ -45,7 +45,7 @@ stage ("stage-name") { submitJobSync "//'EXAMPLE.DATASET(MEMBER)'" downloadDS "EXAMPLE.DATASET(MEMBER)" downloadDS dsn:"EXAMPLE.DATASET(MEMBER)", vol:"VOL001" - allocateDS dsn:"EXAMPLE.DATASET", alcUnit:"TRK", dsOrg:"PS", primary:1, secondary:1, recFm:"FB" + allocateDS dsn:"EXAMPLE.DATASET", alcUnit:"TRK", dsOrg:"PS", primary:1, secondary:1, recFm:"FB", failOnExist:"False" writeFileToDS dsn:"EXAMPLE.DATASET", file:"workspaceFile" writeFileToDS dsn:"EXAMPLE.DATASET", file:"D:\\files\\localFile" writeToDS dsn:"EXAMPLE.DATASET", text:"Write this string to dataset" @@ -57,9 +57,9 @@ stage ("stage-name") { writeFileToFile destFile: "u/USER/myfile", sourceFile: "myfile.txt" writeFileToFile destFile: "u/USER/myfile", sourceFile: "myfile.txt", binary: "true" - deleteDataset dsn:"EXAMPLE.DATASET" - deleteDataset dsn:"EXAMPLE.DATASET", member:"MEMBER" - deleteDatasetsByMask mask:"EXAMPLE.DATASET.*" + deleteDataset dsn:"EXAMPLE.DATASET", failOnNotExist:"False" + deleteDataset dsn:"EXAMPLE.DATASET", member:"MEMBER", failOnNotExist:"True" + deleteDatasetsByMask mask:"EXAMPLE.DATASET.*", failOnNotExist:"False" } // ... } @@ -70,7 +70,30 @@ stage ("stage-name") { ### allocateDS - Represents an action for allocating a dataset in a declarative style ```groovy -allocateDS dsn:"EXAMPLE.DATASET", dsOrg:"PS", primary:1, secondary:1, recFm:"FB" +zosmf ("z/os-connection-name") { + allocateDS( + // Mandatory Parameters below: + dsn: "EXAMPLE.DATASET", + dsOrg: "PS", + primary: 1, + secondary: 1, + recFm: "FB", + failOnExist:"False", + // Optional Parameters below: + volser:"YOURVOL", + unit:"SYSDA", + alcUnit:"TRK", + dirBlk:"5", + blkSize:"800", + lrecl:"80", + storClass:"STORAGECLASS", + mgntClass:"MGMTCLASS", + dataClass:"DATACLASS", + avgBlk:"10", + dsnType:"LIBRARY", + dsModel:"MODEL.DATASET.NAME" + ) +} ``` **Mandatory Parameters:** * ```dsn:"EXAMPLE.DATASET"``` - The name of the dataset to be allocated @@ -78,6 +101,7 @@ allocateDS dsn:"EXAMPLE.DATASET", dsOrg:"PS", primary:1, secondary:1, recFm:"FB" * ```primary:"1"``` - The primary allocation size in cylinders or tracks * ```secondary:"1"``` - The secondary allocation size in cylinders or tracks * ```recFm:"FB"``` - The record format (could be only F, FB, V, VB, U, VSAM, VA) + * ```failOnExist:"False"``` - If the dataset already exists and the option is enabled, execution will halt. (Boolean parameter, is set to 'False' by default) **Optional parms:** * ```volser:"YOURVOL"``` - Volume serial number where the dataset will be allocated. @@ -96,17 +120,20 @@ allocateDS dsn:"EXAMPLE.DATASET", dsOrg:"PS", primary:1, secondary:1, recFm:"FB" ### deleteDataset - Represents an action for deleting datasets and members in a declarative style ```groovy -deleteDataset dsn:"EXAMPLE.DATASET" +zosmf ("z/os-connection-name") { + deleteDataset dsn: "EXAMPLE.DATASET", member:"MEMBER", failOnNotExist:"False" +} ``` **Mandatory Parameters:** * ```dsn:"EXAMPLE.DATASET"``` - Sequential or library dataset name for deletion * ```member:"MEMBER"``` - Dataset member name for deletion + * ```failOnNotExist:"False"``` - If the dataset has been deleted and the option is enabled, execution will halt. (Boolean parameter, is set to 'False' by default) **Expected behavior under various deletion scenarios:** * To delete a member from the library, the dsn and member parameters must be specified: ``` - deleteDataset dsn:"EXAMPLE.DATASET", member:"MEMBER" + deleteDataset dsn:"EXAMPLE.DATASET", member:"MEMBER", failOnNotExist:"False" ``` * You cannot delete a VSAM dataset this way. Otherwise, you will get output similar to: @@ -176,7 +203,7 @@ Pipeline can be used either directly inside the ```Pipeline``` code block in the This pipeline example uses all currently available methods and functionality of the Zowe zDevOps plugin. **Steps to Execute the Pipeline:** -1. Add a zosmf connection in settings (“Manage Jenkins” -> “Configure System / System” -> z/OS Connection List). Enter a connection name, zosmf url, username and password. +1. Add a zosmf connection in settings ('Manage Jenkins' -> 'Configure System / System' -> z/OS Connection List). Enter a connection name, zosmf url, username and password. 2. Create a new Jenkins item -> ```Pipeline``` and open its configuration. 3. In the ```Pipeline``` section, paste the code from the example below and replace all the necessary variables with your data 4. Done, enjoy the minimal ready-made pipeline template! @@ -217,9 +244,9 @@ pipeline { stage('Allocate DSs') { steps { zosmf("${ZOS_CONN_ID}") { - allocateDS dsn:"${PS_DATASET_1}", dsOrg:"PS", primary:1, secondary:1, recFm:"FB" - allocateDS dsn:"${PS_DATASET_2}", dsOrg:"PS", primary:1, secondary:1, recFm:"FB", alcUnit:"TRK" - allocateDS dsn:"${PO_DATASET}(${PO_MEMBER})", dsOrg:"PO", primary:1, secondary:1, recFm:"FB" + allocateDS dsn:"${PS_DATASET_1}", dsOrg:"PS", primary:1, secondary:1, recFm:"FB", failOnExist:"False" + allocateDS dsn:"${PS_DATASET_2}", dsOrg:"PS", primary:1, secondary:1, recFm:"FB", alcUnit:"TRK", failOnExist:"False" + allocateDS dsn:"${PO_DATASET}(${PO_MEMBER})", dsOrg:"PO", primary:1, secondary:1, recFm:"FB", failOnExist:"False" } } } @@ -270,8 +297,8 @@ pipeline { stage('Clean up') { steps { zosmf("${ZOS_CONN_ID}") { - deleteDataset dsn:"${PS_DATASET_1}" - deleteDatasetsByMask mask:"${HLQ}.NEW.*" + deleteDataset dsn:"${PS_DATASET_1}", failOnNotExist:"False" + deleteDatasetsByMask mask:"${HLQ}.NEW.*", failOnNotExist:"True" } } } @@ -321,7 +348,7 @@ The plugin are packaged as self-contained .hpi files, which have all the ### [Zowe zDevOps plugin installation .hpi file](https://github.com/IBA-mainframe-dev/Global-Repository-for-Mainframe-Developers/blob/master/Jenkins%20zOS%20DevOps%20plugin%20installable%20hpi/zos-devops.hpi) Assuming a .hpi file has been downloaded, a logged-in Jenkins administrator may upload the file from within the web UI: -1. Navigate to the Manage Jenkins > Manage Plugins page in the web UI. +1. Navigate to the Manage Jenkins > Plugins page in the web UI. 2. Click on the Advanced tab. 3. Choose the .hpi file from your system or enter a URL to the archive file under the Deploy Plugin section. 4. Deploy the plugin file. @@ -332,8 +359,8 @@ Assuming a .hpi file has been downloaded, a logged-in Jenkins administrat 3. To generate the ```target``` dir with generated-sources - you have to run the Maven command: ```mvn localizer:generate``` 4. Next, you need to generate an installation file: .hpi or .jpi file (both are installation files for the Jenkins plugin). This can be done by executing Maven command ```mvn install``` or by ```mvn hpi:hpi```. 5. After building the .hpi/.jpi file, it should appear in a /build/libs/.hpi directory -6. Next you need to login into the Jenkins, move to the “Manage Jenkins” -> “Manage Plugins” -> “Advanced (tab)” -> “Deploy Plugin” (You can select a plugin file from your local system or provide a URL to install a plugin from outside the central plugin repository) -> Specify the path to the generated .hpi/.jpi file (or by dragging the file from Intellij IDEA project to the file upload field in the Jenkins). -7. Click “Deploy”, reboot Jenkins after installation. The Plugin is ready to go! +6. Next you need to login into the Jenkins, move to the 'Manage Jenkins' -> 'Plugins' -> 'Advanced settings (tab)' -> 'Deploy Plugin' (You can select a plugin file from your local system or provide a URL to install a plugin from outside the central plugin repository) -> Specify the path to the generated .hpi/.jpi file (or by dragging the file from Intellij IDEA project to the file upload field in the Jenkins). +7. Click 'Deploy', reboot Jenkins after installation. The Plugin is ready to go! ## How to run Jenkins plugin in Debug mode in a local Jenkins sandbox diff --git a/pom.xml b/pom.xml index 7471310..463a31d 100644 --- a/pom.xml +++ b/pom.xml @@ -8,26 +8,25 @@ - org.zowe + io.jenkins.plugins zdevops - ${revision}${changelist} + ${revision}.${changelist} Zowe zDevOps hpi Zowe mainframe z/OS automation plugin, working through z/OSMF REST API and using Zowe Kotlin SDK https://github.com/jenkinsci/${project.artifactId}-plugin - - 0.3.0 - -SNAPSHOT + 1.2.0 + 999999-SNAPSHOT 2.414.3 17 1.9.20 true 1.13 4.10.0 - 0.5.0-rc.11 + 0.5.0 5.6.1 official 17 @@ -62,8 +61,8 @@ - scm:git:https://github.com/${gitHubRepo}.git - scm:git:git@github.com:${gitHubRepo}.git + scm:git:https://github.com/${gitHubRepo} + scm:git:https://github.com/${gitHubRepo} https://github.com/${gitHubRepo} ${scmTag} @@ -336,7 +335,7 @@ org.yaml snakeyaml - 2.2 + 2.3 @@ -372,7 +371,13 @@ com.google.code.gson gson - 2.10.1 + 2.11.0 + + + + com.google.errorprone + error_prone_annotations + 2.27.0 @@ -460,7 +465,8 @@ - zowe.jfrog.io + + org.zowe.sdk https://zowe.jfrog.io/artifactory/libs-release diff --git a/src/main/kotlin/org/zowe/zdevops/classic/files/dsn/DownloadDatasetStep.kt b/src/main/kotlin/org/zowe/zdevops/classic/files/dsn/DownloadDatasetStep.kt index db7e98e..98e9579 100644 --- a/src/main/kotlin/org/zowe/zdevops/classic/files/dsn/DownloadDatasetStep.kt +++ b/src/main/kotlin/org/zowe/zdevops/classic/files/dsn/DownloadDatasetStep.kt @@ -1,11 +1,15 @@ /* + * Copyright (c) 2023-2024 IBA Group. + * * This program and the accompanying materials are made available under the terms of the * Eclipse Public License v2.0 which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-v20.html * * SPDX-License-Identifier: EPL-2.0 * - * Copyright IBA Group 2023 + * Contributors: + * IBA Group + * Zowe Community */ package org.zowe.zdevops.classic.files.dsn @@ -70,7 +74,8 @@ constructor( zosConnection: ZOSConnection ) { val workspace = build.executor?.currentWorkspace!! - downloadDSOrDSMemberByType(dsn, vol, returnEtag, listener, zosConnection, workspace) + val jenkinsJobUrl = build.getEnvironment(listener)["JOB_URL"] + downloadDSOrDSMemberByType(dsn, vol, returnEtag, listener, zosConnection, workspace, jenkinsJobUrl) } diff --git a/src/main/kotlin/org/zowe/zdevops/declarative/files/dsn/DownloadFileDeclarative.kt b/src/main/kotlin/org/zowe/zdevops/declarative/files/dsn/DownloadFileDeclarative.kt index 108df17..2710ec7 100644 --- a/src/main/kotlin/org/zowe/zdevops/declarative/files/dsn/DownloadFileDeclarative.kt +++ b/src/main/kotlin/org/zowe/zdevops/declarative/files/dsn/DownloadFileDeclarative.kt @@ -1,11 +1,15 @@ /* + * Copyright (c) 2022-2024 IBA Group. + * * This program and the accompanying materials are made available under the terms of the * Eclipse Public License v2.0 which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-v20.html * * SPDX-License-Identifier: EPL-2.0 * - * Copyright IBA Group 2022 + * Contributors: + * IBA Group + * Zowe Community */ package org.zowe.zdevops.declarative.files.dsn @@ -51,7 +55,8 @@ class DownloadFileDeclarative @DataBoundConstructor constructor(val dsn: String) zosConnection: ZOSConnection ) { val workspacePath = FilePath(null, workspace.remote.replace(workspace.name,"")) - downloadDSOrDSMemberByType(dsn, vol, returnEtag, listener, zosConnection, workspacePath) + val jenkinsJobUrl = env["BUILD_URL"] + "/execution/node/3/" + downloadDSOrDSMemberByType(dsn, vol, returnEtag, listener, zosConnection, workspacePath, jenkinsJobUrl) } diff --git a/src/main/kotlin/org/zowe/zdevops/logic/DownloadOperation.kt b/src/main/kotlin/org/zowe/zdevops/logic/DownloadOperation.kt index 69edb90..e82a05a 100644 --- a/src/main/kotlin/org/zowe/zdevops/logic/DownloadOperation.kt +++ b/src/main/kotlin/org/zowe/zdevops/logic/DownloadOperation.kt @@ -1,17 +1,22 @@ /* + * Copyright (c) 2023-2024 IBA Group. + * * This program and the accompanying materials are made available under the terms of the * Eclipse Public License v2.0 which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-v20.html * * SPDX-License-Identifier: EPL-2.0 * - * Copyright IBA Group 2023 + * Contributors: + * IBA Group + * Zowe Community */ package org.zowe.zdevops.logic import hudson.AbortException import hudson.FilePath +import hudson.console.HyperlinkNote import hudson.model.TaskListener import org.apache.commons.io.IOUtils import org.zowe.kotlinsdk.DatasetOrganization @@ -24,7 +29,8 @@ import org.zowe.zdevops.Messages import java.io.File import java.io.InputStream import java.io.StringWriter - +import java.net.URLEncoder +import java.nio.charset.StandardCharsets /** @@ -36,6 +42,7 @@ import java.io.StringWriter * @param zosConnection The connection to the z/OS system. * @param workspace The workspace where the dataset will be downloaded. * @param listener The listener for capturing task progress and logs. + * @param jenkinsJobUrl The job/pipeline URL */ fun downloadDS( dsn: String, @@ -43,7 +50,8 @@ fun downloadDS( returnEtag: Boolean?, zosConnection: ZOSConnection, workspace: FilePath, - listener: TaskListener + listener: TaskListener, + jenkinsJobUrl: String?, ) { var downloadedDSN: InputStream? try { @@ -55,7 +63,9 @@ fun downloadDS( IOUtils.copy(downloadedDSN, writer, "UTF-8") val file = File("$workspace\\$dsn") file.writeText(writer.toString()) - listener.logger.println(Messages.zdevops_declarative_DSN_downloaded_success(dsn)) + val urlEncodedDsn = URLEncoder.encode(dsn, StandardCharsets.UTF_8.toString()) + listener.logger.println(Messages.zdevops_declarative_DSN_downloaded_success( + HyperlinkNote.encodeTo("${jenkinsJobUrl}ws/$urlEncodedDsn/*view*/", dsn))) } /** @@ -67,6 +77,7 @@ fun downloadDS( * @param listener The listener for capturing task progress and logs. * @param zosConnection The connection to the z/OS system. * @param workspace The workspace where the dataset will be downloaded. + * @param jenkinsJobUrl The job/pipeline URL */ fun downloadDSOrDSMemberByType( dsn: String, @@ -74,23 +85,24 @@ fun downloadDSOrDSMemberByType( returnEtag: Boolean?, listener: TaskListener, zosConnection: ZOSConnection, - workspace: FilePath + workspace: FilePath, + jenkinsJobUrl: String? ) { listener.logger.println(Messages.zdevops_declarative_DSN_downloading(dsn, vol, zosConnection.host, zosConnection.zosmfPort)) val dsnMemberPattern = Regex("[\\w#\$@.-]{1,}\\([\\w#\$@]{1,8}\\)") //means it's a PDS member if (dsn.contains(dsnMemberPattern)) { - downloadDS(dsn, vol, returnEtag, zosConnection, workspace, listener) + downloadDS(dsn, vol, returnEtag, zosConnection, workspace, listener, jenkinsJobUrl) } else { val dsnList = ZosDsnList(zosConnection).listDsn(dsn, ListParams(vol)) if (dsnList.items.isEmpty()) { throw AbortException("Can't find $dsn ${ if(vol.isNullOrBlank()) "" else "on volume $vol"}") } when (dsnList.items.first().datasetOrganization) { - DatasetOrganization.PS -> downloadDS(dsn, vol, returnEtag, zosConnection, workspace, listener) + DatasetOrganization.PS -> downloadDS(dsn, vol, returnEtag, zosConnection, workspace, listener, jenkinsJobUrl) DatasetOrganization.PO, DatasetOrganization.POE -> { listener.logger.println(Messages.zdevops_declarative_DSN_downloading_members(dsn)) ZosDsnList(zosConnection).listDsnMembers(dsn, ListParams(vol)).items.forEach { - downloadDS("${dsn}(${it.name})", vol, returnEtag, zosConnection, workspace, listener) + downloadDS("${dsn}(${it.name})", vol, returnEtag, zosConnection, workspace, listener, jenkinsJobUrl) } } else -> listener.logger.println(Messages.zdevops_declarative_DSN_downloading_invalid_dsorg()) diff --git a/src/main/kotlin/org/zowe/zdevops/logic/SubmitJobOperation.kt b/src/main/kotlin/org/zowe/zdevops/logic/SubmitJobOperation.kt index 8fd19bd..0586d1c 100644 --- a/src/main/kotlin/org/zowe/zdevops/logic/SubmitJobOperation.kt +++ b/src/main/kotlin/org/zowe/zdevops/logic/SubmitJobOperation.kt @@ -25,6 +25,8 @@ import org.zowe.zdevops.Messages import org.zowe.zdevops.utils.extractSubmitJobMessage import org.zowe.zdevops.utils.runMFTryCatchWrappedQuery import java.io.File +import java.net.URLEncoder +import java.nio.charset.StandardCharsets /** * Submits a z/OS job @@ -93,10 +95,11 @@ fun submitJobSync( val logPath = "$workspacePath/${finalResult.jobName}.${finalResult.jobId}" val file = File(logPath) file.writeText(fullLog) + val urlEncodedJobName = URLEncoder.encode(finalResult.jobName, StandardCharsets.UTF_8.toString()) listener.logger.println(Messages.zdevops_declarative_ZOSJobs_got_log( HyperlinkNote.encodeTo( linkBuilder(buildUrl, finalResult.jobName, finalResult.jobId), - "${finalResult.jobName}.${finalResult.jobId}" + "$urlEncodedJobName.${finalResult.jobId}" ) )) } else { diff --git a/src/test/kotlin/org/zowe/zdevops/classic/files/dsn/DownloadDatasetStepSpec.kt b/src/test/kotlin/org/zowe/zdevops/classic/files/dsn/DownloadDatasetStepSpec.kt index 4269fe6..4f421cf 100644 --- a/src/test/kotlin/org/zowe/zdevops/classic/files/dsn/DownloadDatasetStepSpec.kt +++ b/src/test/kotlin/org/zowe/zdevops/classic/files/dsn/DownloadDatasetStepSpec.kt @@ -11,9 +11,11 @@ package org.zowe.zdevops.classic.files.dsn import hudson.AbortException +import hudson.EnvVars import hudson.FilePath import hudson.model.Executor import hudson.model.Item +import hudson.model.TaskListener import hudson.util.FormValidation import io.kotest.assertions.assertSoftly import io.kotest.assertions.fail @@ -70,6 +72,12 @@ class DownloadDatasetStepSpec : ShouldSpec({ every { mockInstance.currentWorkspace } returns FilePath(virtualChannel, mockDir.absolutePath) return mockInstance } + + override fun getEnvironment(log: TaskListener): EnvVars { + val env = EnvVars() + env["JOB_URL"] = "TEST" + return env + } } afterEach {