From 70994d7aed5490d16541fbc84df64a1a62b43af9 Mon Sep 17 00:00:00 2001 From: IBA-mainframe-dev Date: Thu, 24 Oct 2024 14:19:12 +0200 Subject: [PATCH 1/4] Zowe zDevOps plugin 1.2.0 release --- .github/dependabot.yml | 14 +- .github/release-drafter.yml | 3 +- .github/setup/action.yml | 21 ++ .github/sonar/action.yml | 22 ++ .github/workflows/cd.yaml | 15 + .github/workflows/hpi-builder.yml | 60 +++ .github/workflows/release-drafter.yml | 17 - .gitignore | 6 +- .mvn/extensions.xml | 2 +- .mvn/maven.config | 2 +- .mvn/wrapper/maven-wrapper.properties | 18 + Jenkinsfile | 2 +- README.md | 346 +++++++++++++++--- mvnw | 250 +++++++++++++ mvnw.cmd | 146 ++++++++ pom.xml | 234 +++++++++--- .../zdevops/classic/AbstractBuildStep.kt | 58 --- .../zdevops/classic/steps/SubmitJobStep.kt | 71 ---- .../jobs/DownloadFileDeclarative.kt | 88 ----- .../jobs/SubmitJobSyncStepDeclarative.kt | 74 ---- .../jobs/WriteToDatasetDeclarative.kt | 67 ---- .../jobs/WriteToMemberDeclarative.kt | 68 ---- .../plugins/zdevops/logic/DeleteOperation.kt | 72 ---- .../SimpleMFExceptionMessageExtractor.kt | 33 -- .../zowe/zdevops/classic/AbstractBuildStep.kt | 134 +++++++ .../classic/steps/AllocateDatasetStep.kt | 335 +++++++++++++++++ .../classic/steps/DeleteDatasetStep.kt | 80 ++++ .../classic/steps/DeleteDatasetsByMaskStep.kt | 49 +++ .../classic/steps/DownloadDatasetStep.kt | 104 ++++++ .../classic/steps/PerformTsoCommandStep.kt | 93 +++++ .../zdevops/classic/steps/SubmitJobStep.kt | 70 ++++ .../classic/steps/WriteFileToDatasetStep.kt | 188 ++++++++++ .../classic/steps/WriteFileToFileStep.kt | 144 ++++++++ .../classic/steps/WriteFileToMemberStep.kt | 201 ++++++++++ .../classic/steps/WriteToDatasetStep.kt | 89 +++++ .../zdevops/classic/steps/WriteToFileStep.kt | 90 +++++ .../classic/steps/WriteToMemberStep.kt | 101 +++++ .../zowe}/zdevops/config/ZOSConnection.kt | 13 +- .../zowe}/zdevops/config/ZOSConnectionList.kt | 4 +- .../declarative/AbstractZosmfAction.kt | 22 +- .../zdevops/declarative/ZosmfExecution.kt | 2 +- .../declarative/ZosmfStepDeclarative.kt | 11 +- .../jobs/AllocateDatasetDeclarative.kt | 81 ++-- .../jobs/DeleteDatasetDeclarative.kt | 15 +- .../jobs/DeleteDatasetsByMaskDeclarative.kt | 15 +- .../jobs/DownloadFileDeclarative.kt | 57 +++ .../jobs/PerformTsoCommandDeclarative.kt | 74 ++++ .../jobs/SubmitJobStepDeclarative.kt | 16 +- .../jobs/SubmitJobSyncStepDeclarative.kt | 45 +++ .../jobs/WriteFIleToMemberDeclarative.kt | 27 +- .../jobs/WriteFileToDatasetDeclarative.kt | 6 +- .../jobs/WriteFileToFileDeclarative.kt | 18 +- .../jobs/WriteToDatasetDeclarative.kt | 43 +++ .../jobs/WriteToFileDeclarative.kt | 24 +- .../jobs/WriteToMemberDeclarative.kt | 44 +++ .../zowe/zdevops/logic/AllocateOperation.kt | 92 +++++ .../org/zowe/zdevops/logic/DeleteOperation.kt | 102 ++++++ .../zowe/zdevops/logic/DownloadOperation.kt | 99 +++++ .../logic/PerformTsoCommandOperation.kt | 48 +++ .../zowe/zdevops/logic/SubmitJobOperation.kt | 107 ++++++ .../org/zowe/zdevops/logic/WriteOperation.kt | 123 +++++++ .../zdevops/model/ResolvedZOSConnection.kt | 2 +- .../zdevops/utils/ConnectionValidation.kt | 74 ++++ .../zowe/zdevops/utils/FieldsValidation.kt | 86 +++++ .../SimpleMFExceptionMessageExtractor.kt | 60 +++ .../META-INF/hudson.remoting.ClassFilter | 1 + src/main/resources/index.jelly | 4 +- .../zowe}/zdevops/Messages.properties | 42 ++- .../steps/AllocateDatasetStep/config.jelly | 92 +++++ .../AllocateDatasetStep/config.properties | 23 ++ .../steps/DeleteDatasetStep/config.jelly | 18 + .../steps/DeleteDatasetStep/config.properties | 3 + .../DeleteDatasetsByMaskStep/config.jelly | 15 + .../config.properties | 2 + .../steps/DownloadDatasetStep}/config.jelly | 5 +- .../DownloadDatasetStep/config.properties | 3 + .../steps/PerformTsoCommandStep/config.jelly | 15 + .../PerformTsoCommandStep/config.properties | 3 + .../classic/steps/SubmitJobStep/config.jelly | 37 ++ .../steps/SubmitJobStep/config.properties | 8 +- .../steps/WriteFileToDatasetStep/config.jelly | 54 +++ .../WriteFileToDatasetStep/config.properties | 8 + .../steps/WriteFileToFileStep/config.jelly | 57 +++ .../WriteFileToFileStep/config.properties | 8 + .../steps/WriteFileToMemberStep/config.jelly | 57 +++ .../WriteFileToMemberStep/config.properties | 9 + .../steps/WriteToDatasetStep/config.jelly | 15 + .../WriteToDatasetStep/config.properties | 3 + .../steps/WriteToFileStep/config.jelly | 18 + .../steps/WriteToFileStep/config.properties | 4 + .../steps/WriteToMemberStep/config.jelly | 18 + .../steps/WriteToMemberStep/config.properties | 4 + .../zdevops/config/ZOSConnection/config.jelly | 2 +- .../config/ZOSConnection/config.properties | 0 .../config/ZOSConnectionList/config.jelly | 0 .../ZOSConnectionList/config.properties | 0 .../zowe/zdevops/MockResponseDispatcher.kt | 58 +++ .../org/zowe/zdevops/MockServerFactory.kt | 43 +++ .../classic/steps/AllocateDatasetStepSpec.kt | 233 ++++++++++++ .../classic/steps/DeleteDatasetStepSpec.kt | 162 ++++++++ .../steps/DeleteDatasetsByMaskStepSpec.kt | 148 ++++++++ .../classic/steps/DownloadDatasetStepSpec.kt | 292 +++++++++++++++ .../steps/PerformTsoCommandStepSpec.kt | 177 +++++++++ .../classic/steps/SubmitJobStepSpec.kt | 249 +++++++++++++ .../steps/WriteFileToDatasetStepSpec.kt | 139 +++++++ .../classic/steps/WriteFileToFileStepSpec.kt | 135 +++++++ .../steps/WriteFileToMemberStepSpec.kt | 136 +++++++ .../classic/steps/WriteToDatasetStepSpec.kt | 102 ++++++ .../classic/steps/WriteToFileStepSpec.kt | 103 ++++++ .../classic/steps/WriteToMemberStepSpec.kt | 121 ++++++ .../zdevops/classic/steps/dummyClasses.kt | 141 +++++++ .../jobs/AllocateDatasetDeclarativeSpec.kt | 111 ++++++ .../jobs/DeleteDatasetDeclarativeSpec.kt | 233 ++++++++++++ .../jobs/DownloadFileDeclarativeSpec.kt | 116 ++++++ .../jobs/SubmitJobStepDeclarativeSpec.kt | 106 ++++++ .../jobs/SubmitJobSyncStepDeclarativeSpec.kt | 213 +++++++++++ .../jobs/WriteFileToFileDeclarativeSpec.kt | 110 ++++++ .../jobs/WriteFileToMemberDeclarativeSpec.kt | 109 ++++++ .../zdevops/declarative/jobs/dummyClasses.kt | 145 ++++++++ .../kotlin/org/zowe/zdevops/testConstants.kt | 13 + .../zdevops/utils/FieldsValidationSpec.kt | 45 +++ .../resources/mock/emptyDataSetsList.json | 5 + src/test/resources/mock/endTsoResponse.json | 6 + src/test/resources/mock/getJobResponse.json | 15 + .../mock/getJobSpoolFilesResponse.json | 18 + .../mock/getSpoolFileRecordsResponse.txt | 21 ++ src/test/resources/mock/getTsoResponse.json | 50 +++ .../resources/mock/listDataSetMembers.json | 27 ++ src/test/resources/mock/listDataSets.json | 94 +++++ src/test/resources/mock/listDataSetsPS.json | 28 ++ .../mock/retrieveDatasetContentResponse.txt | 4 + src/test/resources/mock/sendTsoResponse.json | 26 ++ src/test/resources/mock/startTsoResponse.json | 16 + .../resources/mock/submitJobFailResponse.json | 7 + .../resources/mock/submitJobResponse.json | 15 + src/test/resources/mock/test_file.txt | 1 + 136 files changed, 8172 insertions(+), 806 deletions(-) create mode 100644 .github/setup/action.yml create mode 100644 .github/sonar/action.yml create mode 100644 .github/workflows/cd.yaml create mode 100644 .github/workflows/hpi-builder.yml delete mode 100644 .github/workflows/release-drafter.yml create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100644 mvnw create mode 100644 mvnw.cmd delete mode 100644 src/main/kotlin/io/jenkins/plugins/zdevops/classic/AbstractBuildStep.kt delete mode 100644 src/main/kotlin/io/jenkins/plugins/zdevops/classic/steps/SubmitJobStep.kt delete mode 100644 src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/DownloadFileDeclarative.kt delete mode 100644 src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/SubmitJobSyncStepDeclarative.kt delete mode 100644 src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/WriteToDatasetDeclarative.kt delete mode 100644 src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/WriteToMemberDeclarative.kt delete mode 100644 src/main/kotlin/io/jenkins/plugins/zdevops/logic/DeleteOperation.kt delete mode 100644 src/main/kotlin/io/jenkins/plugins/zdevops/utils/SimpleMFExceptionMessageExtractor.kt create mode 100644 src/main/kotlin/org/zowe/zdevops/classic/AbstractBuildStep.kt create mode 100644 src/main/kotlin/org/zowe/zdevops/classic/steps/AllocateDatasetStep.kt create mode 100644 src/main/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetStep.kt create mode 100644 src/main/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetsByMaskStep.kt create mode 100644 src/main/kotlin/org/zowe/zdevops/classic/steps/DownloadDatasetStep.kt create mode 100644 src/main/kotlin/org/zowe/zdevops/classic/steps/PerformTsoCommandStep.kt create mode 100644 src/main/kotlin/org/zowe/zdevops/classic/steps/SubmitJobStep.kt create mode 100644 src/main/kotlin/org/zowe/zdevops/classic/steps/WriteFileToDatasetStep.kt create mode 100644 src/main/kotlin/org/zowe/zdevops/classic/steps/WriteFileToFileStep.kt create mode 100644 src/main/kotlin/org/zowe/zdevops/classic/steps/WriteFileToMemberStep.kt create mode 100644 src/main/kotlin/org/zowe/zdevops/classic/steps/WriteToDatasetStep.kt create mode 100644 src/main/kotlin/org/zowe/zdevops/classic/steps/WriteToFileStep.kt create mode 100644 src/main/kotlin/org/zowe/zdevops/classic/steps/WriteToMemberStep.kt rename src/main/kotlin/{io/jenkins/plugins => org/zowe}/zdevops/config/ZOSConnection.kt (89%) rename src/main/kotlin/{io/jenkins/plugins => org/zowe}/zdevops/config/ZOSConnectionList.kt (95%) rename src/main/kotlin/{io/jenkins/plugins => org/zowe}/zdevops/declarative/AbstractZosmfAction.kt (72%) rename src/main/kotlin/{io/jenkins/plugins => org/zowe}/zdevops/declarative/ZosmfExecution.kt (96%) rename src/main/kotlin/{io/jenkins/plugins => org/zowe}/zdevops/declarative/ZosmfStepDeclarative.kt (78%) rename src/main/kotlin/{io/jenkins/plugins => org/zowe}/zdevops/declarative/jobs/AllocateDatasetDeclarative.kt (63%) rename src/main/kotlin/{io/jenkins/plugins => org/zowe}/zdevops/declarative/jobs/DeleteDatasetDeclarative.kt (88%) rename src/main/kotlin/{io/jenkins/plugins => org/zowe}/zdevops/declarative/jobs/DeleteDatasetsByMaskDeclarative.kt (86%) create mode 100644 src/main/kotlin/org/zowe/zdevops/declarative/jobs/DownloadFileDeclarative.kt create mode 100644 src/main/kotlin/org/zowe/zdevops/declarative/jobs/PerformTsoCommandDeclarative.kt rename src/main/kotlin/{io/jenkins/plugins => org/zowe}/zdevops/declarative/jobs/SubmitJobStepDeclarative.kt (63%) create mode 100644 src/main/kotlin/org/zowe/zdevops/declarative/jobs/SubmitJobSyncStepDeclarative.kt rename src/main/kotlin/{io/jenkins/plugins => org/zowe}/zdevops/declarative/jobs/WriteFIleToMemberDeclarative.kt (72%) rename src/main/kotlin/{io/jenkins/plugins => org/zowe}/zdevops/declarative/jobs/WriteFileToDatasetDeclarative.kt (92%) rename src/main/kotlin/{io/jenkins/plugins => org/zowe}/zdevops/declarative/jobs/WriteFileToFileDeclarative.kt (79%) create mode 100644 src/main/kotlin/org/zowe/zdevops/declarative/jobs/WriteToDatasetDeclarative.kt rename src/main/kotlin/{io/jenkins/plugins => org/zowe}/zdevops/declarative/jobs/WriteToFileDeclarative.kt (62%) create mode 100644 src/main/kotlin/org/zowe/zdevops/declarative/jobs/WriteToMemberDeclarative.kt create mode 100644 src/main/kotlin/org/zowe/zdevops/logic/AllocateOperation.kt create mode 100644 src/main/kotlin/org/zowe/zdevops/logic/DeleteOperation.kt create mode 100644 src/main/kotlin/org/zowe/zdevops/logic/DownloadOperation.kt create mode 100644 src/main/kotlin/org/zowe/zdevops/logic/PerformTsoCommandOperation.kt create mode 100644 src/main/kotlin/org/zowe/zdevops/logic/SubmitJobOperation.kt create mode 100644 src/main/kotlin/org/zowe/zdevops/logic/WriteOperation.kt rename src/main/kotlin/{io/jenkins/plugins => org/zowe}/zdevops/model/ResolvedZOSConnection.kt (93%) create mode 100644 src/main/kotlin/org/zowe/zdevops/utils/ConnectionValidation.kt create mode 100644 src/main/kotlin/org/zowe/zdevops/utils/FieldsValidation.kt create mode 100644 src/main/kotlin/org/zowe/zdevops/utils/SimpleMFExceptionMessageExtractor.kt rename src/main/resources/{io/jenkins/plugins => org/zowe}/zdevops/Messages.properties (58%) create mode 100644 src/main/resources/org/zowe/zdevops/classic/steps/AllocateDatasetStep/config.jelly create mode 100644 src/main/resources/org/zowe/zdevops/classic/steps/AllocateDatasetStep/config.properties create mode 100644 src/main/resources/org/zowe/zdevops/classic/steps/DeleteDatasetStep/config.jelly create mode 100644 src/main/resources/org/zowe/zdevops/classic/steps/DeleteDatasetStep/config.properties create mode 100644 src/main/resources/org/zowe/zdevops/classic/steps/DeleteDatasetsByMaskStep/config.jelly create mode 100644 src/main/resources/org/zowe/zdevops/classic/steps/DeleteDatasetsByMaskStep/config.properties rename src/main/resources/{io/jenkins/plugins/zdevops/classic/steps/SubmitJobStep => org/zowe/zdevops/classic/steps/DownloadDatasetStep}/config.jelly (63%) create mode 100644 src/main/resources/org/zowe/zdevops/classic/steps/DownloadDatasetStep/config.properties create mode 100644 src/main/resources/org/zowe/zdevops/classic/steps/PerformTsoCommandStep/config.jelly create mode 100644 src/main/resources/org/zowe/zdevops/classic/steps/PerformTsoCommandStep/config.properties create mode 100644 src/main/resources/org/zowe/zdevops/classic/steps/SubmitJobStep/config.jelly rename src/main/resources/{io/jenkins/plugins => org/zowe}/zdevops/classic/steps/SubmitJobStep/config.properties (71%) create mode 100644 src/main/resources/org/zowe/zdevops/classic/steps/WriteFileToDatasetStep/config.jelly create mode 100644 src/main/resources/org/zowe/zdevops/classic/steps/WriteFileToDatasetStep/config.properties create mode 100644 src/main/resources/org/zowe/zdevops/classic/steps/WriteFileToFileStep/config.jelly create mode 100644 src/main/resources/org/zowe/zdevops/classic/steps/WriteFileToFileStep/config.properties create mode 100644 src/main/resources/org/zowe/zdevops/classic/steps/WriteFileToMemberStep/config.jelly create mode 100644 src/main/resources/org/zowe/zdevops/classic/steps/WriteFileToMemberStep/config.properties create mode 100644 src/main/resources/org/zowe/zdevops/classic/steps/WriteToDatasetStep/config.jelly create mode 100644 src/main/resources/org/zowe/zdevops/classic/steps/WriteToDatasetStep/config.properties create mode 100644 src/main/resources/org/zowe/zdevops/classic/steps/WriteToFileStep/config.jelly create mode 100644 src/main/resources/org/zowe/zdevops/classic/steps/WriteToFileStep/config.properties create mode 100644 src/main/resources/org/zowe/zdevops/classic/steps/WriteToMemberStep/config.jelly create mode 100644 src/main/resources/org/zowe/zdevops/classic/steps/WriteToMemberStep/config.properties rename src/main/resources/{io/jenkins/plugins => org/zowe}/zdevops/config/ZOSConnection/config.jelly (91%) rename src/main/resources/{io/jenkins/plugins => org/zowe}/zdevops/config/ZOSConnection/config.properties (100%) rename src/main/resources/{io/jenkins/plugins => org/zowe}/zdevops/config/ZOSConnectionList/config.jelly (100%) rename src/main/resources/{io/jenkins/plugins => org/zowe}/zdevops/config/ZOSConnectionList/config.properties (100%) create mode 100644 src/test/kotlin/org/zowe/zdevops/MockResponseDispatcher.kt create mode 100644 src/test/kotlin/org/zowe/zdevops/MockServerFactory.kt create mode 100644 src/test/kotlin/org/zowe/zdevops/classic/steps/AllocateDatasetStepSpec.kt create mode 100644 src/test/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetStepSpec.kt create mode 100644 src/test/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetsByMaskStepSpec.kt create mode 100644 src/test/kotlin/org/zowe/zdevops/classic/steps/DownloadDatasetStepSpec.kt create mode 100644 src/test/kotlin/org/zowe/zdevops/classic/steps/PerformTsoCommandStepSpec.kt create mode 100644 src/test/kotlin/org/zowe/zdevops/classic/steps/SubmitJobStepSpec.kt create mode 100644 src/test/kotlin/org/zowe/zdevops/classic/steps/WriteFileToDatasetStepSpec.kt create mode 100644 src/test/kotlin/org/zowe/zdevops/classic/steps/WriteFileToFileStepSpec.kt create mode 100644 src/test/kotlin/org/zowe/zdevops/classic/steps/WriteFileToMemberStepSpec.kt create mode 100644 src/test/kotlin/org/zowe/zdevops/classic/steps/WriteToDatasetStepSpec.kt create mode 100644 src/test/kotlin/org/zowe/zdevops/classic/steps/WriteToFileStepSpec.kt create mode 100644 src/test/kotlin/org/zowe/zdevops/classic/steps/WriteToMemberStepSpec.kt create mode 100644 src/test/kotlin/org/zowe/zdevops/classic/steps/dummyClasses.kt create mode 100644 src/test/kotlin/org/zowe/zdevops/declarative/jobs/AllocateDatasetDeclarativeSpec.kt create mode 100644 src/test/kotlin/org/zowe/zdevops/declarative/jobs/DeleteDatasetDeclarativeSpec.kt create mode 100644 src/test/kotlin/org/zowe/zdevops/declarative/jobs/DownloadFileDeclarativeSpec.kt create mode 100644 src/test/kotlin/org/zowe/zdevops/declarative/jobs/SubmitJobStepDeclarativeSpec.kt create mode 100644 src/test/kotlin/org/zowe/zdevops/declarative/jobs/SubmitJobSyncStepDeclarativeSpec.kt create mode 100644 src/test/kotlin/org/zowe/zdevops/declarative/jobs/WriteFileToFileDeclarativeSpec.kt create mode 100644 src/test/kotlin/org/zowe/zdevops/declarative/jobs/WriteFileToMemberDeclarativeSpec.kt create mode 100644 src/test/kotlin/org/zowe/zdevops/declarative/jobs/dummyClasses.kt create mode 100644 src/test/kotlin/org/zowe/zdevops/testConstants.kt create mode 100644 src/test/kotlin/org/zowe/zdevops/utils/FieldsValidationSpec.kt create mode 100644 src/test/resources/mock/emptyDataSetsList.json create mode 100644 src/test/resources/mock/endTsoResponse.json create mode 100644 src/test/resources/mock/getJobResponse.json create mode 100644 src/test/resources/mock/getJobSpoolFilesResponse.json create mode 100644 src/test/resources/mock/getSpoolFileRecordsResponse.txt create mode 100644 src/test/resources/mock/getTsoResponse.json create mode 100644 src/test/resources/mock/listDataSetMembers.json create mode 100644 src/test/resources/mock/listDataSets.json create mode 100644 src/test/resources/mock/listDataSetsPS.json create mode 100644 src/test/resources/mock/retrieveDatasetContentResponse.txt create mode 100644 src/test/resources/mock/sendTsoResponse.json create mode 100644 src/test/resources/mock/startTsoResponse.json create mode 100644 src/test/resources/mock/submitJobFailResponse.json create mode 100644 src/test/resources/mock/submitJobResponse.json create mode 100644 src/test/resources/mock/test_file.txt 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/setup/action.yml b/.github/setup/action.yml new file mode 100644 index 0000000..802090c --- /dev/null +++ b/.github/setup/action.yml @@ -0,0 +1,21 @@ +name: "Setup action" +description: "Prepares for execution - set up Java, Kotlin, Maven" + +inputs: + jdkVersion: + description: "JDK version" + required: false + default: "17" + mavenVersion: + description: "Maven version" + required: false + default: "3.9.4" + +runs: + using: "composite" + steps: + - name: Set up JDK ${{ inputs.jdkVersion }} + uses: actions/setup-java@v2 + with: + distribution: 'zulu' + java-version: ${{ inputs.jdkVersion }} diff --git a/.github/sonar/action.yml b/.github/sonar/action.yml new file mode 100644 index 0000000..47f2a5c --- /dev/null +++ b/.github/sonar/action.yml @@ -0,0 +1,22 @@ +name: "Setup action" +description: "Runs sonar scans" + + +runs: + using: composite + steps: + - name: Cache SonarCloud packages + uses: actions/cache@v1 + with: + path: ~/.sonar/caches + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + + - name: Code coverage and publish results + shell: bash + run: > + ./mvnw sonar:sonar + -Dorg.gradle.jvmargs="-XX:MaxMetaspaceSize=512m" + -Dresults="build/reports/tests/test,build/test-results/test,build/reports/jacoco/test/html" + -Psonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_TOKEN + -Dsonar.coverage.jacoco.xmlReportPaths="build/reports/jacoco.xml" \ 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/hpi-builder.yml b/.github/workflows/hpi-builder.yml new file mode 100644 index 0000000..7c5ff09 --- /dev/null +++ b/.github/workflows/hpi-builder.yml @@ -0,0 +1,60 @@ +name: Jenkins plugin .hpi build + +# This workflow will build an executable Jenkins plugin in .hpi format for further manual installation in Jenkins + +on: [push, workflow_dispatch] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - name: Checkout Jenkins plugin code + uses: actions/checkout@v4 + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + cache: maven + + - name: Build Jenkins Plugin into .hpi + shell: bash + run: ./mvnw clean package + + - name: Verify content of target folder + shell: bash + run: pwd && ls -l target/ + + - name: Archive the Build Output + uses: actions/upload-artifact@v4 + with: + name: zowe-zdevops-hpi + path: target/*.hpi + compression-level: 0 # no compression + + + test: + + runs-on: ubuntu-latest + + needs: build + steps: + - name: Checkout the plugin GitHub repository + uses: actions/checkout@v4 + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + cache: maven + + - name: Run Unit Tests with Maven + shell: bash + run: ./mvnw test 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/.gitignore b/.gitignore index f3b299b..347a6e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,11 @@ -# mvn hpi:run work target .idea/ .vscode +.gradle/ build/ +/*.iml + + +src/test/resources/trash/ 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/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..25effb9 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.1 +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip diff --git a/Jenkinsfile b/Jenkinsfile index 87d1157..da6f699 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -8,4 +8,4 @@ * Copyright IBA Group 2022 */ -buildPlugin(useContainerAgent: true, tests: [[skip: 'true']], configurations: [[ platform: 'linux', jdk: '11' ]]) \ No newline at end of file +buildPlugin(useContainerAgent: true, configurations: [[ platform: 'linux', jdk: '17' ]]) diff --git a/README.md b/README.md index 8565c27..370496b 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,15 @@ # Zowe zDevOps Jenkins plugin -## About the plugin +## About the plugin The Zowe zDevOps Jenkins Plugin by [IBA Group](https://ibagroupit.com/?utm_campaign=IBA_W-Mainframe&utm_source=jenkins&utm_medium=referral&utm_content=description_zdevops) is an open-source, secure , and reliable agent-less Jenkins plugin that makes it possible to perform most of the actual tasks on the mainframe, managing it with a modern native mainframe zOSMF REST API and the capabilities of available zOSMF SDKs. -## Advantages +## Main features - Secure and modern connection of Jenkins to the mainframes through the use of zOSMF REST API. - The functionality is based on the Kotlin SDK methods, such as JCL jobs submission, download, allocate, write to the dataset, etc., with a log collected upon completion. -- Multiple connections to various mainframes—z/OS Connections List where you can save all the necessary systems and credentials; all data is safely stored under the protection of Jenkins Credentials manager. -- Agent-less. +- Multiple connections to various mainframes — z/OS Connections List where you can save all the necessary systems and credentials (all data is safely stored under the protection of Jenkins Credentials manager). +- Agent-less solution. - z/OSMF connection validation. +- Convenient user interface panels for working with the mainframe - Fast execution and functional extensibility. ## About us @@ -24,76 +25,307 @@ Please feel free to contact us or schedule a call with our Mainframe DevOps expe Thank you for considering IBA Group for your mainframe needs. -## Manual plugin installation by the .hpi executable file -The plugin are packaged as self-contained .hpi files, which have all the necessary code, images, and other resources which the plugin needs to operate successfully. - -### Already packaged and tested installation .hpi file can be downloaded from a link from a nearby GitHub repository: -### [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. -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. - -## Manual Jenkins plugin installation (Installation via source code build and .hpi file upload) -1. Download the Zowe zDevOps Jenkins plugin source code from its [official GitHub repository](https://github.com/jenkinsci/zdevops-plugin) -2. It is necessary to build the project with the help of the Maven Build Tool -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! - -## Plugin configuration +## 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” -> scroll down 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 the call in the code). For the example: ```z/os-connection-name``` + 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://:``` 4. Add credentials (Mainframe User ID + Password) under which you can connect to the system. You can save as many connections as you like, the system will keep the corresponding user IDs/passwords. -## Use case -- Add a zosmf connection in settings (Manage Jenkins -> Configure System -> z/OS Connection List). Enter a connection name, zosmf url, username and password. -- Create a new item -> ```Pipeline``` and open its configuration. - Create a zosmf section inside the steps of the stage and pass the connection name as a parameter of the section. Inside the zosmf body invoke necessary zosmf functions (they will be automatically done in a specified connection context). Take a look at the example below: +## Declarative methods brief list ```groovy stage ("stage-name") { steps { // ... zosmf("z/os-connection-name") { - submitJob "//'EXAMPLE.DATASET(JCLJOB)'" - submitJobSync "//'EXAMPLE.DATASET(JCLJOB)'" - downloadDS "USER.LIB(MEMBER)" - downloadDS dsn:"USER.LIB(MEMBER)", vol:"VOL001" - allocateDS dsn:"STV.TEST5", alcUnit:"TRK", dsOrg:"PS", primary:1, secondary:1, recFm:"FB" - writeFileToDS dsn:"USER.DATASET", file:"workspaceFile" - writeFileToDS dsn:"USER.DATASET", file:"D:\\files\\localFile" - writeToDS dsn:"USER.DATASET", text:"Write this string to dataset" - writeFileToMember dsn:"USER.DATASET", member:"MEMBER", file:"workspaceFile" - writeFileToMember dsn:"USER.DATASET", member:"MEMBER", file:"D:\\files\\localFile" - writeToMember dsn:"USER.DATASET", member:"MEMBER", text:"Write this string to member" - - writeToFile destFile: "u/USER/doc", text: "Hello there" - writeFileToFile destFile: "u/USER/doc", sourceFile: "myfile.txt" - writeFileToFile destFile: "u/USER/doc", sourceFile: "myfile.txt", binary: "true" - - deleteDataset dsn:"USER.DATASET" - deleteDataset dsn:"USER.DATASET", member:"MEMBER1" - deleteDatasetsByMask mask:"USER.DATASET.*" + submitJob "//'EXAMPLE.DATASET(MEMBER)'" + 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", 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" + writeFileToMember dsn:"EXAMPLE.DATASET", member:"MEMBER", file:"workspaceFile" + writeFileToMember dsn:"EXAMPLE.DATASET", member:"MEMBER", file:"D:\\files\\localFile" + writeToMember dsn:"EXAMPLE.DATASET", member:"MEMBER", text:"Write this string to member" + + writeToFile destFile: "u/USER/myfile", text: "Write this string to file" + writeFileToFile destFile: "u/USER/myfile", sourceFile: "myfile.txt" + writeFileToFile destFile: "u/USER/myfile", sourceFile: "myfile.txt", binary: "true" + + deleteDataset dsn:"EXAMPLE.DATASET", failOnNotExist:"False" + deleteDataset dsn:"EXAMPLE.DATASET", member:"MEMBER", failOnNotExist:"True" + deleteDatasetsByMask mask:"EXAMPLE.DATASET.*", failOnNotExist:"False" } // ... } } ``` +## Declarative Methods Detail Description + +### allocateDS - Represents an action for allocating a dataset in a declarative style +```groovy +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 + * ```dsOrg:"PS"``` - The dataset organization (could be only PO, POE, PS, VS) + * ```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. + * ```unit:"SYSDA"``` - Specifies the type of storage device. SYSDA is a common direct access storage device. + * ```alcUnit:"TRK"``` - Allocation units (CYL for cylinders, TRK for tracks). + * ```dirBlk:"5"``` - Directory block records. + * ```blkSize:"800"``` - BLKSIZE=800: Block size of 800 bytes. + * ```lrecl:"80"``` - Logical record length. + * ```storClass:"STORAGECLASS"``` - Storage class for SMS-managed datasets. + * ```mgntClass:"MGMTCLASS"``` - Management class for SMS-managed datasets. + * ```dataClass:"DATACLASS"``` - Data class for SMS-managed datasets. + * ```avgBlk:"10"``` - Average block length. + * ```dsnType:"LIBRARY"``` - Specifies the type of dataset, LIBRARY for a PDS or PDSE. + * ```dsModel:"MODEL.DATASET.NAME"``` - Data set model is a predefined set of attributes that can be used to allocate new data sets with the same characteristics ("LIKE" parameter). + + +### deleteDataset - Represents an action for deleting datasets and members in a declarative style +```groovy +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", failOnNotExist:"False" + ``` + +* You cannot delete a VSAM dataset this way. Otherwise, you will get output similar to: + ``` + Deleting dataset EXAMPLE.VSAM.DATASET with connection :10443 + ISRZ002 Deallocation failed - Deallocation failed for data set 'EXAMPLE.VSAM.DATASET' + ``` + +* What do you get if a dataset does not exist? + + ``` + Deleting dataset EXAMPLE.DS.DOES.NOT.EXIST with connection :10443 + ISRZ002 Data set not cataloged - 'EXAMPLE.DS.DOES.NOT.EXIST' was not found in catalog. + ``` + +* What do you get if a dataset is busy by a user or a program? + + ``` + Deleting dataset EXAMPLE.DS.ISUSED.BY.USER with connection :10443 + ISRZ002 Data set in use - Data set 'EXAMPLE.DS.ISUSED.BY.USER' in use by another user, try later or enter HELP for a list of jobs and users allocated to 'EXAMPLE.DS.ISUSED.BY.USER'. + ``` + +## Use case example +Here you can find an example of a minimal declarative Jenkins pipeline for execution, testing and further modification for your personal needs. +Pipeline can be used either directly inside the ```Pipeline``` code block in the Jenkins server, or in a ```Jenkinsfile``` stored in Git +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. +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! + +```groovy +pipeline { + agent any + + environment { + // Define environment variables + GIT_REPOSITORY_URL = 'https://github.com/your-username/your-repo.git' // Replace with your GitHub URL + GIT_BRANCH = 'main' // Replace with your GitHub branch name + GIT_USER_CREDENTIAL_ID = 'jenkins-cred-key' // Replace with your Jenkins GitHub credential ID + ZOS_CONN_ID = 'z/os-connection-name' // Replace with your z/OS Connection ID from zDevOps plugin settings + HLQ = 'HLQ' // Replace with your z/OS high-level qualifier (HLQ) + PS_DATASET_1 = "${HLQ}.NEW.TEST1" // OPTIONAL: Replace with the dataset names you need + PS_DATASET_2 = "${HLQ}.NEW.TEST2" // OPTIONAL + PO_DATASET = "${HLQ}.NEW.TEST3" // OPTIONAL + PO_MEMBER = "NEWMEM" // OPTIONAL + JCL_JOB_TEMPLATE = "jcl_job_example" // Replace with the name of your file that contains the JCL job code + JIRA_URL = 'https://your-jira-instance.atlassian.net' // Replace with your Jira URL + JIRA_USER = 'your-jira-email@example.com' // Replace with your Jira user email + JIRA_API_TOKEN = 'your-jira-api-token' // Replace with your Jira API token + JIRA_ISSUE_KEY = 'PROJECT-123' // Replace with your Jira issue key + } + + stages { + stage('Checkout') { + steps { + // Checkout the source code from Git + checkout scmGit( + branches: [[name: "${GIT_BRANCH}"]], + userRemoteConfigs: [[credentialsId: "${GIT_USER_CREDENTIAL_ID}", + url: "${GIT_REPOSITORY_URL}"]]) + } + } + + stage('Allocate DSs') { + steps { + zosmf("${ZOS_CONN_ID}") { + 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" + } + } + } + + stage('Add JCL content') { + steps { + script { + // Read the content of the JCL job into a variable + env.JCL_CONTENT = readFile("${JCL_JOB_TEMPLATE}").trim() + // Print the content of the file (for debugging purposes) + echo "JCL job content:\n${env.JCL_CONTENT}" + } + zosmf("${ZOS_CONN_ID}") { + writeFileToDS dsn:"${PS_DATASET_2}", file:"${JCL_JOB_TEMPLATE}" + writeFileToMember dsn:"${PO_DATASET}", member:"${PO_MEMBER}", file:"${JCL_JOB_TEMPLATE}" + writeToDS dsn:"${PS_DATASET_1}", text:"${env.JCL_CONTENT}" + } + } + } + + stage('Add USS content') { + steps { + zosmf("${ZOS_CONN_ID}") { + writeToFile destFile: "u/${HLQ}/test_file1", text: "${env.JCL_CONTENT}" + writeFileToFile destFile: "u/${HLQ}/test_file2", sourceFile: "${JCL_JOB_TEMPLATE}", binary: "true" + } + } + } + + stage('Submit JCL jobs') { + steps { + zosmf("${ZOS_CONN_ID}") { + submitJob "//'${PS_DATASET_1}'" + submitJobSync "//'${PO_DATASET}(NEWMEM)'" + } + } + } + + stage('Download datasets') { + steps { + zosmf("${ZOS_CONN_ID}") { + downloadDS "${PS_DATASET_1}" + downloadDS dsn:"${PS_DATASET_2}" + } + } + } + + stage('Clean up') { + steps { + zosmf("${ZOS_CONN_ID}") { + deleteDataset dsn:"${PS_DATASET_1}", failOnNotExist:"False" + deleteDatasetsByMask mask:"${HLQ}.NEW.*", failOnNotExist:"True" + } + } + } + } + + post { + always { + script { + def jiraStatus = currentBuild.currentResult == 'SUCCESS' ? 'Build Successful' : 'Build Failed' + def jiraComment = """ + { + "body": "Jenkins build ${jiraStatus} for Job ${env.JOB_NAME} - Build #${env.BUILD_NUMBER}. + [View the build here|${env.BUILD_URL}]" + } + """ + + httpRequest acceptType: 'APPLICATION_JSON', + contentType: 'APPLICATION_JSON', + httpMode: 'POST', + requestBody: jiraComment, + url: "${JIRA_URL}/rest/api/2/issue/${JIRA_ISSUE_KEY}/comment", + authentication: 'jira-credentials-id' + } + } + + success { + // Notify success (example: send email) + mail to: '${JIRA_USER}', + subject: "SUCCESS: Build ${env.BUILD_NUMBER}", + body: "The build ${env.BUILD_NUMBER} was successful." + } + + failure { + // Notify failure (example: send email) + mail to: '${JIRA_USER}', + subject: "FAILURE: Build ${env.BUILD_NUMBER}", + body: "The build ${env.BUILD_NUMBER} failed. Please check the Jenkins logs for more details." + } + } +} +``` + +## Manual plugin installation by the .hpi executable file +The plugin are packaged as self-contained .hpi files, which have all the necessary code, images, and other resources which the plugin needs to operate successfully. + +### Already packaged and tested installation .hpi file can be downloaded from a link from a nearby GitHub repository: +### [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 > 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. + +## Manual Jenkins plugin installation (Installation via source code build and .hpi file upload) +1. Download the Zowe zDevOps Jenkins plugin source code from its [official GitHub repository](https://github.com/jenkinsci/zdevops-plugin) +2. It is necessary to build the project with the help of the Maven Build Tool +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' -> '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 For debugging purposes run following Maven command from plugin project directory: -```mvn hpi:run``` +```./mvnw hpi:run``` Or by ```mvnDebug hpi:run``` - this will copy all the dependencies down (rather than in your jenkins install) and run it in place. @@ -101,8 +333,14 @@ By default, the debugging instance is then available at [http://localhost:8080/j In order to launch Jenkins on a different port than 8080 use this system property: -```mvn hpi:run -Djetty.port=8090``` +```./mvnw hpi:run -Djetty.port=8090``` Changing the default context path can be achieved by setting this system property: -```mvn hpi:run -Dhpi.prefix=/debug``` \ No newline at end of file +```./mvnw hpi:run -Dhpi.prefix=/debug``` + +## How to run unit-tests with JaCoCo report + +```./mvnw clean verify``` + +The results are available under `target/site/jacoco/index.html` diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..ac8e247 --- /dev/null +++ b/mvnw @@ -0,0 +1,250 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.1 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl="${value-}" ;; + distributionSha256Sum) distributionSha256Sum="${value-}" ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_HOME="$HOME/.m2/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..7b0c094 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,146 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.1 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/pom.xml b/pom.xml index 469f7a3..c65d50f 100644 --- a/pom.xml +++ b/pom.xml @@ -4,34 +4,45 @@ org.jenkins-ci.plugins plugin - 4.61 + 4.78 + io.jenkins.plugins zdevops ${revision}${changelist} Zowe zDevOps - io.jenkins.plugins 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.1.0 + 1.2.0- -SNAPSHOT - 2.375 - 11 - 1.8.20 + 2.414.3 + 17 + 1.9.20 true 1.13 + 4.10.0 + 0.5.0-rc.11 + 5.6.1 official - 11 - true + 17 UTF-8 - High jenkinsci/${project.artifactId}-plugin + + UTF-8 + zowe + zowe_zowe-zdevops-jenkins-plugin + zowe-zdevops-jenkins-plugin + project.version + kotlin + https://github.com/zowe/zowe-zdevops-jenkins-plugin + + 17 @@ -50,13 +61,14 @@ - 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} + ${project.basedir}/src/main/kotlin org.jetbrains.kotlin @@ -82,6 +94,11 @@ test-compile + + + ${project.basedir}/src/test/kotlin/ + + kapt @@ -125,10 +142,10 @@ org.apache.maven.plugins maven-compiler-plugin - 3.11.0 + 3.13.0 - 11 - 11 + 17 + 17 @@ -154,18 +171,69 @@ + + org.apache.maven.plugins + maven-jar-plugin + + + + true + + + + + org.apache.maven.plugins maven-surefire-plugin - 3.0.0 + 3.1.2 - ${tests.skip} + + InjectedTest + + + **/*Spec.* + **/*Test.* + + + org.jacoco + jacoco-maven-plugin + 0.8.9 + + + coverage-initialize + + prepare-agent + + + + coverage-report + post-integration-test + + report + + + + + + + + + io.jenkins.tools.bom + bom-2.414.x + 2884.vc36b_64ce114a_ + pom + import + + + + org.jetbrains.kotlin @@ -175,15 +243,27 @@ org.jetbrains.kotlin - kotlin-test-junit + kotlin-stdlib-jdk8 ${kotlin.version} - test + + + + org.jetbrains.kotlin + kotlin-reflect + ${kotlin.version} + runtime + + + + org.slf4j + slf4j-api + 2.0.7 commons-io commons-io - 2.11.0 + 2.15.1 @@ -195,7 +275,7 @@ org.codehaus.plexus plexus-utils - 3.5.1 + 4.0.0 @@ -231,62 +311,67 @@ com.squareup.retrofit2 retrofit - 2.9.0 + 2.11.0 com.squareup.retrofit2 converter-gson - 2.9.0 + 2.11.0 com.squareup.retrofit2 converter-scalars - 2.9.0 + 2.11.0 org.zowe.sdk zowe-kotlin-sdk - 0.4.0-rc.2 + ${zowekotlinsdk.version} + + + + org.yaml + snakeyaml + 2.2 org.jenkins-ci.plugins credentials - 1224.vc23ca_a_9a_2cb_0 org.jenkins-ci.plugins.workflow - workflow-step-api - 639.v6eca_cd8c04a_a_ + workflow-job org.jenkins-ci.plugins.workflow - workflow-aggregator - 596.v8c21c963d92d - test + workflow-cps - com.google.code.gson - gson - 2.10.1 + org.jenkins-ci.plugins.workflow + workflow-basic-steps - org.jenkins-ci.plugins - script-security - 1244.ve463715a_f89c + org.jenkins-ci.plugins.workflow + workflow-durable-task-step - org.jenkins-ci.plugins - mailer - 1.34 + org.jenkinsci.plugins + pipeline-model-definition + + + + com.google.code.gson + gson + 2.10.1 @@ -296,32 +381,75 @@ - org.jenkins-ci - annotation-indexer - 1.17 + com.squareup.okhttp3 + okhttp + ${okhttp3.version} - junit - junit - 4.13.2 + org.jetbrains.kotlin + kotlin-test-junit + ${kotlin.version} test - org.hamcrest - hamcrest-all - 1.3 + io.mockk + mockk-jvm + 1.13.7 test - org.hamcrest - hamcrest-core - 2.2 + io.kotest + kotest-assertions-core-jvm + ${kotest.version} + test + + + + io.kotest + kotest-runner-junit5-jvm + ${kotest.version} + test + + + + com.squareup.okhttp3 + mockwebserver + ${okhttp3.version} test + + com.squareup.okhttp3 + okhttp-tls + ${okhttp3.version} + test + + + + + + org.jenkins-ci + annotation-indexer + + + + org.jenkins-ci.plugins + script-security + + + + org.jenkins-ci.plugins + credentials + + + + org.jenkins-ci.plugins.workflow + workflow-step-api + + @@ -331,7 +459,7 @@ - org.zowe.sdk + zowe.jfrog.io https://zowe.jfrog.io/artifactory/libs-release diff --git a/src/main/kotlin/io/jenkins/plugins/zdevops/classic/AbstractBuildStep.kt b/src/main/kotlin/io/jenkins/plugins/zdevops/classic/AbstractBuildStep.kt deleted file mode 100644 index 1e769a5..0000000 --- a/src/main/kotlin/io/jenkins/plugins/zdevops/classic/AbstractBuildStep.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 - */ - -package io.jenkins.plugins.zdevops.classic - -import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection -import hudson.Launcher -import hudson.tasks.BuildStepDescriptor -import hudson.tasks.Builder -import jenkins.tasks.SimpleBuildStep -import java.io.PrintWriter -import java.io.StringWriter -import io.jenkins.plugins.zdevops.config.ZOSConnectionList -import hudson.model.* -import io.jenkins.plugins.zdevops.Messages -import java.net.URL - -abstract class AbstractBuildStep(private val connectionName: String) : Builder(), SimpleBuildStep { - - abstract fun perform(build: AbstractBuild<*, *>, - launcher: Launcher, - listener: BuildListener, - zosConnection: ZOSConnection) - - override fun perform(build: AbstractBuild<*, *>, - launcher: Launcher, - listener: BuildListener): Boolean { - - val connection = ZOSConnectionList.resolve(connectionName) ?: let{ - val exception = IllegalArgumentException(Messages.zdevops_config_ZOSConnection_resolve_unknown(connectionName)) - val sw = StringWriter() - exception.printStackTrace(PrintWriter(sw)) - listener.logger.println(sw.toString()) - throw exception - } - val connURL = URL(connection.url) - val zosConnection = ZOSConnection( - connURL.host, connURL.port.toString(), connection.username, connection.password, connURL.protocol - ) - - perform(build, launcher, listener, zosConnection) - return true - } - - companion object { - open class DefaultBuildDescriptor(private val descriptorDisplayName: String = ""): BuildStepDescriptor() { - override fun getDisplayName() = descriptorDisplayName - override fun isApplicable(jobType: Class>?) = true - } - } -} diff --git a/src/main/kotlin/io/jenkins/plugins/zdevops/classic/steps/SubmitJobStep.kt b/src/main/kotlin/io/jenkins/plugins/zdevops/classic/steps/SubmitJobStep.kt deleted file mode 100644 index d9fd2c8..0000000 --- a/src/main/kotlin/io/jenkins/plugins/zdevops/classic/steps/SubmitJobStep.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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 - */ - -package io.jenkins.plugins.zdevops.classic.steps - -import org.zowe.kotlinsdk.zowe.client.sdk.zosjobs.SubmitJobs -import io.jenkins.plugins.zdevops.Messages -import io.jenkins.plugins.zdevops.classic.AbstractBuildStep -import io.jenkins.plugins.zdevops.config.ZOSConnectionList -import hudson.AbortException -import hudson.Extension -import hudson.Launcher -import hudson.model.AbstractBuild -import hudson.model.BuildListener -import hudson.util.ListBoxModel -import jenkins.model.GlobalConfiguration -import org.kohsuke.stapler.DataBoundConstructor -import java.io.PrintWriter -import java.io.StringWriter - -class SubmitJobStep -@DataBoundConstructor -constructor( - connectionName: String, - val jobName: String -) : AbstractBuildStep(connectionName) { - override fun perform( - build: AbstractBuild<*, *>, - launcher: Launcher, - listener: BuildListener, - zosConnection: org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection - ) { - runCatching { - listener.logger.println(Messages.zdevops_classic_ZOSJobs_submitting(jobName, zosConnection.host, zosConnection.zosmfPort)) - val submitJobRsp = SubmitJobs(zosConnection).submitJob(jobName) - listener.logger.println( - Messages.zdevops_classic_ZOSJobs_submitted_success( - submitJobRsp.jobid, - submitJobRsp.jobname, - submitJobRsp.owner - ) - ) - }.onFailure { - val sw = StringWriter() - it.printStackTrace(PrintWriter(sw)) - listener.logger.println(sw.toString()) - throw AbortException(Messages.zdevops_classic_ZOSJobs_submitted_fail(jobName)) - } - } - - - @Extension - class DescriptorImpl : Companion.DefaultBuildDescriptor(Messages.zdevops_classic_submitJobStep_display_name()) { - fun doFillConnectionNameItems(): ListBoxModel { - val result = ListBoxModel() - - GlobalConfiguration.all().get(ZOSConnectionList::class.java)?.connections?.forEach { - result.add("${it.name} - (${it.url})", it.name) - } - - return result - } - } -} diff --git a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/DownloadFileDeclarative.kt b/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/DownloadFileDeclarative.kt deleted file mode 100644 index ff075e7..0000000 --- a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/DownloadFileDeclarative.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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 - */ - -package io.jenkins.plugins.zdevops.declarative.jobs - -import org.zowe.kotlinsdk.DatasetOrganization -import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection -import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.ZosDsnDownload -import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.ZosDsnList -import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.input.DownloadParams -import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.input.ListParams -import io.jenkins.plugins.zdevops.declarative.AbstractZosmfAction -import hudson.* -import hudson.model.Run -import hudson.model.TaskListener -import org.apache.commons.io.IOUtils -import org.jenkinsci.Symbol -import org.kohsuke.stapler.DataBoundConstructor -import org.kohsuke.stapler.DataBoundSetter -import java.io.File -import java.io.StringWriter - -class DownloadFileDeclarative @DataBoundConstructor constructor(private val dsn: String) : - AbstractZosmfAction() { - - private var vol: String? = null - private var getETag: Boolean? = null - - @DataBoundSetter - fun setVol(vol: String) { this.vol = vol } - - @DataBoundSetter - fun setGetETag(getETag: Boolean) { this.getETag = getETag } - - override val exceptionMessage: String = zMessages.zdevops_declarative_DSN_downloaded_fail(dsn) - - fun downloadDS(dsn: String, - zosConnection: ZOSConnection, - workspace: FilePath, - listener: TaskListener) { - val downloadedDSN = ZosDsnDownload(zosConnection).downloadDsn(dsn, DownloadParams(dsn,getETag,vol)) - - val writer = StringWriter() - IOUtils.copy(downloadedDSN, writer, "UTF-8") - val workspacePath = workspace.remote.replace(workspace.name,"") - val file = File("$workspacePath$dsn") - file.writeText(writer.toString()) - listener.logger.println(zMessages.zdevops_declarative_DSN_downloaded_success(dsn)) - } - - override fun perform( - run: Run<*, *>, - workspace: FilePath, - env: EnvVars, - launcher: Launcher, - listener: TaskListener, - zosConnection: ZOSConnection - ) { - listener.logger.println(zMessages.zdevops_declarative_DSN_downloading(dsn, vol, zosConnection.host, zosConnection.zosmfPort)) - - if (dsn.contains(Regex("[\\w#\$@.-]{1,}\\([\\w#\$@]{1,8}\\)"))) { //means it's a PDS member - downloadDS(dsn,zosConnection,workspace,listener) - } else { - when (ZosDsnList(zosConnection).listDsn(dsn, ListParams(vol)).items.first().datasetOrganization) { - DatasetOrganization.PS -> downloadDS(dsn,zosConnection,workspace,listener) - DatasetOrganization.PO -> { - listener.logger.println(zMessages.zdevops_declarative_DSN_downloading_members(dsn)) - ZosDsnList(zosConnection).listDsnMembers(dsn,ListParams(vol)).items.forEach { - downloadDS("${dsn}(${it.name})",zosConnection,workspace,listener) - } - } - else -> listener.logger.println(zMessages.zdevops_declarative_DSN_downloading_invalid_dsorg()) - } - } - } - - - @Symbol("downloadDS") - @Extension - class DescriptorImpl : Companion.DefaultBuildDescriptor("Download File Declarative") -} diff --git a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/SubmitJobSyncStepDeclarative.kt b/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/SubmitJobSyncStepDeclarative.kt deleted file mode 100644 index 9d088f6..0000000 --- a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/SubmitJobSyncStepDeclarative.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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 - */ - -package io.jenkins.plugins.zdevops.declarative.jobs - -import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection -import org.zowe.kotlinsdk.zowe.client.sdk.zosjobs.GetJobs -import org.zowe.kotlinsdk.zowe.client.sdk.zosjobs.MonitorJobs -import org.zowe.kotlinsdk.zowe.client.sdk.zosjobs.SubmitJobs -import io.jenkins.plugins.zdevops.declarative.AbstractZosmfAction -import hudson.* -import hudson.console.HyperlinkNote -import hudson.model.Run -import hudson.model.TaskListener -import org.jenkinsci.Symbol -import org.kohsuke.stapler.DataBoundConstructor -import java.io.File - -class SubmitJobSyncStepDeclarative @DataBoundConstructor constructor(private val fileToSubmit: String): - AbstractZosmfAction() { - - override val exceptionMessage: String = zMessages.zdevops_declarative_ZOSJobs_submitted_fail(fileToSubmit) - - override fun perform( - run: Run<*, *>, - workspace: FilePath, - env: EnvVars, - launcher: Launcher, - listener: TaskListener, - zosConnection: ZOSConnection - ) { - listener.logger.println(zMessages.zdevops_declarative_ZOSJobs_submitting(fileToSubmit, zosConnection.host, zosConnection.zosmfPort)) - val submitJobRsp = SubmitJobs(zosConnection).submitJob(fileToSubmit) - listener.logger.println(zMessages.zdevops_declarative_ZOSJobs_submitted_success(submitJobRsp.jobid, submitJobRsp.jobname, submitJobRsp.owner)) - listener.logger.println(zMessages.zdevops_declarative_ZOSJobs_submitted_waiting()) - val jobId = submitJobRsp.jobid ?: throw IllegalStateException("System response doesn't contain JOB ID.") - val jobName = submitJobRsp.jobname ?: throw IllegalStateException("System response doesn't contain JOB name.") - val finalResult = MonitorJobs(zosConnection).waitForJobOutputStatus(jobName, jobId) - listener.logger.println(zMessages.zdevops_declarative_ZOSJobs_submitted_executed(finalResult.returnedCode)) - - listener.logger.println(zMessages.zdevops_declarative_ZOSJobs_getting_log()) - val spoolFiles = GetJobs(zosConnection).getSpoolFilesForJob(finalResult) - if (spoolFiles.isNotEmpty()) { - var fullLog = spoolFiles.joinToString { GetJobs(zosConnection).getSpoolContent(it) } //spoolFiles.forEach { fullLog = fullLog.plus(GetJobs(zosConnection).getSpoolContent(it)) } - if (fullLog != null) { - val workspacePath = workspace.remote.replace(workspace.name, "") - val logPath = "$workspacePath${finalResult.jobName}.${finalResult.jobId}" - val file = File(logPath) - file.writeText(fullLog!!) - listener.logger.println(zMessages.zdevops_declarative_ZOSJobs_got_log( - HyperlinkNote.encodeTo( - "${env["BUILD_URL"]}execution/node/3/ws/${finalResult.jobName}.${finalResult.jobId}/*view*/", - "${finalResult.jobName}.${finalResult.jobId}" - ) - )) - } else { - listener.logger.println(zMessages.zdevops_spool_content_error(submitJobRsp.jobid)) - } - } else { - listener.logger.println(zMessages.zdevops_no_spool_files(submitJobRsp.jobid)) - } - } - - @Symbol("submitJobSync") - @Extension - class DescriptorImpl : Companion.DefaultBuildDescriptor("Submit Job Synchronously Declarative") -} diff --git a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/WriteToDatasetDeclarative.kt b/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/WriteToDatasetDeclarative.kt deleted file mode 100644 index f1ab66c..0000000 --- a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/WriteToDatasetDeclarative.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 - */ - -package io.jenkins.plugins.zdevops.declarative.jobs - -import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection -import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.ZosDsn -import io.jenkins.plugins.zdevops.declarative.AbstractZosmfAction -import hudson.* -import hudson.FilePath -import hudson.model.Run -import hudson.model.TaskListener -import org.jenkinsci.Symbol -import org.kohsuke.stapler.DataBoundConstructor - -class WriteToDatasetDeclarative @DataBoundConstructor constructor(private val dsn: String, - private val text: String) : - AbstractZosmfAction() { - - override val exceptionMessage: String = zMessages.zdevops_declarative_writing_DS_fail(dsn) - - override fun perform( - run: Run<*, *>, - workspace: FilePath, - env: EnvVars, - launcher: Launcher, - listener: TaskListener, - zosConnection: ZOSConnection - ) { - if (text != "") { - listener.logger.println(zMessages.zdevops_declarative_writing_DS_from_input(dsn, zosConnection.host, zosConnection.zosmfPort)) - - val stringList = text.split('\n') - val targetDS = ZosDsn(zosConnection).getDatasetInfo(dsn) - if (targetDS.recordLength == null) { - throw AbortException(zMessages.zdevops_declarative_writing_DS_no_info(dsn)) - } - var ineligibleStrings = 0 - stringList.forEach { - if (it.length > targetDS.recordLength!!) { - ineligibleStrings++ - } - } - if (ineligibleStrings > 0) { - throw AbortException(zMessages.zdevops_declarative_writing_DS_ineligible_strings(ineligibleStrings,dsn)) - } else { - val textByteArray = text.replace("\r","").toByteArray() - val writeToDS = ZosDsn(zosConnection).writeDsn(dsn, textByteArray) - listener.logger.println(zMessages.zdevops_declarative_writing_DS_success(dsn)) - } - } else { - listener.logger.println(zMessages.zdevops_declarative_writing_skip()) - } - } - - - @Symbol("writeToDS") - @Extension - class DescriptorImpl : Companion.DefaultBuildDescriptor("Write to Dataset Declarative") -} diff --git a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/WriteToMemberDeclarative.kt b/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/WriteToMemberDeclarative.kt deleted file mode 100644 index 1dd6a91..0000000 --- a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/WriteToMemberDeclarative.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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 - */ - -package io.jenkins.plugins.zdevops.declarative.jobs - -import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection -import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.ZosDsn -import io.jenkins.plugins.zdevops.declarative.AbstractZosmfAction -import hudson.* -import hudson.FilePath -import hudson.model.Run -import hudson.model.TaskListener -import org.jenkinsci.Symbol -import org.kohsuke.stapler.DataBoundConstructor - -class WriteToMemberDeclarative @DataBoundConstructor constructor(private val dsn: String, - private val member: String, - private val text: String) : - AbstractZosmfAction() { - - override val exceptionMessage: String = zMessages.zdevops_declarative_writing_DS_fail(dsn) - - override fun perform( - run: Run<*, *>, - workspace: FilePath, - env: EnvVars, - launcher: Launcher, - listener: TaskListener, - zosConnection: ZOSConnection - ) { - if (text != "") { - listener.logger.println(zMessages.zdevops_declarative_writing_DS_from_input(dsn, zosConnection.host, zosConnection.zosmfPort)) - - val stringList = text.split('\n') - val targetDS = ZosDsn(zosConnection).getDatasetInfo(dsn) - if (targetDS.recordLength == null) { - throw AbortException(zMessages.zdevops_declarative_writing_DS_no_info(dsn)) - } - var ineligibleStrings = 0 - stringList.forEach { - if (it.length > targetDS.recordLength!!) { - ineligibleStrings++ - } - } - if (ineligibleStrings > 0) { - throw AbortException(zMessages.zdevops_declarative_writing_DS_ineligible_strings(ineligibleStrings,dsn)) - } else { - val textByteArray = text.replace("\r","").toByteArray() - val writeToDS = ZosDsn(zosConnection).writeDsn(dsn, member, textByteArray) - listener.logger.println(zMessages.zdevops_declarative_writing_DS_success(dsn)) - } - } else { - listener.logger.println(zMessages.zdevops_declarative_writing_skip()) - } - } - - - @Symbol("writeToMember") - @Extension - class DescriptorImpl : Companion.DefaultBuildDescriptor("Write to Dataset Member Declarative") -} diff --git a/src/main/kotlin/io/jenkins/plugins/zdevops/logic/DeleteOperation.kt b/src/main/kotlin/io/jenkins/plugins/zdevops/logic/DeleteOperation.kt deleted file mode 100644 index 86a3760..0000000 --- a/src/main/kotlin/io/jenkins/plugins/zdevops/logic/DeleteOperation.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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 - */ - -package io.jenkins.plugins.zdevops.logic - -import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection -import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.ZosDsn -import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.ZosDsnList -import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.input.ListParams -import io.jenkins.plugins.zdevops.declarative.jobs.zMessages -import io.jenkins.plugins.zdevops.utils.runMFTryCatchWrappedQuery -import hudson.AbortException -import hudson.model.TaskListener - -/** - * This class contains logic for mainframe datasets deletion - */ -class DeleteOperation { - - companion object { - private val successMessage: String = zMessages.zdevops_deleting_ds_success() - - fun deleteDatasetsByMask(mask: String, zosConnection: ZOSConnection, listener: TaskListener) { - if (mask.isEmpty()) { - throw AbortException(zMessages.zdevops_deleting_datasets_by_mask_but_mask_is_empty()) - } - listener.logger.println(zMessages.zdevops_deleting_ds_by_mask(mask)) - val dsnList = ZosDsnList(zosConnection).listDsn(mask, ListParams()) - if (dsnList.items.isEmpty()) { - throw AbortException(zMessages.zdevops_deleting_ds_fail_no_matching_mask()) - } - dsnList.items.forEach { - runMFTryCatchWrappedQuery(listener) { - listener.logger.println(zMessages.zdevops_deleting_ds(it.name, zosConnection.host, zosConnection.zosmfPort)) - ZosDsn(zosConnection).deleteDsn(it.name) - } - } - listener.logger.println(successMessage) - } - - fun deleteDatasetOrMember(dsn: String, member: String, zosConnection: ZOSConnection, listener: TaskListener) { - if (dsn.isEmpty()) { - throw AbortException(zMessages.zdevops_deleting_ds_fail_dsn_param_empty()) - } - val memberNotEmpty = member.isNotEmpty() - val logMessage = if (memberNotEmpty) zMessages.zdevops_deleting_ds_member(member, dsn, zosConnection.host, zosConnection.zosmfPort) - else zMessages.zdevops_deleting_ds(dsn, zosConnection.host, zosConnection.zosmfPort) - listener.logger.println(logMessage) - runMFTryCatchWrappedQuery(listener) { - val response = if (memberNotEmpty) { - isMemberNameValid(member) - ZosDsn(zosConnection).deleteDsn(dsn, member) - } - else ZosDsn(zosConnection).deleteDsn(dsn) - } - listener.logger.println(successMessage) - } - - private fun isMemberNameValid(member: String) { - if (member.length > 8 || member.isEmpty()) - throw Exception(zMessages.zdevops_member_name_invalid()) - } - - } -} diff --git a/src/main/kotlin/io/jenkins/plugins/zdevops/utils/SimpleMFExceptionMessageExtractor.kt b/src/main/kotlin/io/jenkins/plugins/zdevops/utils/SimpleMFExceptionMessageExtractor.kt deleted file mode 100644 index 09a2533..0000000 --- a/src/main/kotlin/io/jenkins/plugins/zdevops/utils/SimpleMFExceptionMessageExtractor.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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 - */ - -package io.jenkins.plugins.zdevops.utils - -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken -import hudson.model.TaskListener -import java.lang.NullPointerException - -inline fun runMFTryCatchWrappedQuery(listener: TaskListener, call: () -> R): Result { - try { - return Result.success(call()) - } catch (e: Exception) { - val responseMap: Map = Gson().fromJson(e.message, object : TypeToken>() {}.type) - var errorContent: Any - try { - errorContent = responseMap.get("details") as ArrayList<*> - errorContent.forEach {listener.logger.println(it)} - } catch (e: NullPointerException) { - errorContent = responseMap.get("message") as String - listener.logger.println(errorContent) - } - throw Exception(e) - } -} diff --git a/src/main/kotlin/org/zowe/zdevops/classic/AbstractBuildStep.kt b/src/main/kotlin/org/zowe/zdevops/classic/AbstractBuildStep.kt new file mode 100644 index 0000000..6aecdba --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/classic/AbstractBuildStep.kt @@ -0,0 +1,134 @@ +/* + * 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 + */ + +package org.zowe.zdevops.classic + +import hudson.Launcher +import hudson.model.AbstractBuild +import hudson.model.AbstractProject +import hudson.model.BuildListener +import hudson.tasks.BuildStepDescriptor +import hudson.tasks.Builder +import hudson.util.FormValidation +import hudson.util.ListBoxModel +import jenkins.model.GlobalConfiguration +import jenkins.tasks.SimpleBuildStep +import org.kohsuke.stapler.QueryParameter +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.Messages +import org.zowe.zdevops.config.ZOSConnectionList +import org.zowe.zdevops.utils.validateConnection +import java.io.IOException +import java.io.PrintWriter +import java.io.StringWriter +import java.net.URL +import javax.servlet.ServletException + +/** + * The AbstractBuildStep class is an abstract class that serves as the base for the plugin build steps in Jenkins. + */ +abstract class AbstractBuildStep(val connectionName: String) : Builder(), SimpleBuildStep { + /** + * Performs the build step. + * + * @param build The build object. + * @param launcher The launcher object. + * @param listener The build listener. + * @param zosConnection The connection to the z/OS system. + */ + abstract fun perform(build: AbstractBuild<*, *>, + launcher: Launcher, + listener: BuildListener, + zosConnection: ZOSConnection) + /** + * Performs the build step. + * + * @param build The build object. + * @param launcher The launcher object. + * @param listener The build listener. + * @return True if the build step is successful, false otherwise. + * @throws IllegalArgumentException if the z/OS connection cannot be resolved. + */ + override fun perform(build: AbstractBuild<*, *>, + launcher: Launcher, + listener: BuildListener): Boolean { + + val connection = ZOSConnectionList.resolve(connectionName) ?: let{ + val exception = IllegalArgumentException(Messages.zdevops_config_ZOSConnection_resolve_unknown(connectionName)) + val sw = StringWriter() + exception.printStackTrace(PrintWriter(sw)) + listener.logger.println(sw.toString()) + throw exception + } + val connURL = URL(connection.url) + val zosConnection = ZOSConnection( + connURL.host, connURL.port.toString(), connection.username, connection.password, connURL.protocol + ) + + validateConnection(zosConnection) + + perform(build, launcher, listener, zosConnection) + return true + } + + companion object { + /** + * The default descriptor for the plugin build steps. + * + * @param descriptorDisplayName The display name for the plugin build step + */ + open class DefaultBuildDescriptor(private val descriptorDisplayName: String = ""): BuildStepDescriptor() { + /** + * Gets the display name for the build step. + * + * @return The display name. + */ + override fun getDisplayName() = descriptorDisplayName + /** + * Checks if the build step is applicable to the specified job type + * + * @param jobType The type of the job. + * @return True if the build step is applicable, false otherwise. + */ + override fun isApplicable(jobType: Class>?) = true + + /** + * Fills the connection name items for the build step configuration. + * + * @return The ListBoxModel containing the connection name items. + */ + fun doFillConnectionNameItems(): ListBoxModel { + val result = ListBoxModel() + + GlobalConfiguration.all().get(ZOSConnectionList::class.java)?.connections?.forEach { + result.add("${it.name} - (${it.url})", it.name) + } + + return result + } + + /** + * Checks if the connection name is valid. + * + * @param connectionName The name of the z/OS connection. + * @return FormValidation.ok() if the connection name is valid, or an error message otherwise. + */ + @Throws(IOException::class, ServletException::class) + open fun doCheckConnectionName(@QueryParameter connectionName: String): FormValidation? { + val result = ListBoxModel() + GlobalConfiguration.all().get(ZOSConnectionList::class.java)?.connections?.forEach { + result.add("${it.name} - (${it.url})", it.name) + } + return if (result.isNotEmpty()) FormValidation.ok() else FormValidation.error(Messages.zdevops_config_ZOSConnectionList_validation_error()) + } + + } + } +} diff --git a/src/main/kotlin/org/zowe/zdevops/classic/steps/AllocateDatasetStep.kt b/src/main/kotlin/org/zowe/zdevops/classic/steps/AllocateDatasetStep.kt new file mode 100644 index 0000000..52639e9 --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/classic/steps/AllocateDatasetStep.kt @@ -0,0 +1,335 @@ +/* + * 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 + */ + +package org.zowe.zdevops.classic.steps + +import hudson.Extension +import hudson.Launcher +import hudson.model.AbstractBuild +import hudson.model.BuildListener +import hudson.util.FormValidation +import hudson.util.ListBoxModel +import org.kohsuke.stapler.DataBoundConstructor +import org.kohsuke.stapler.DataBoundSetter +import org.kohsuke.stapler.QueryParameter +import org.zowe.kotlinsdk.AllocationUnit +import org.zowe.kotlinsdk.DatasetOrganization +import org.zowe.kotlinsdk.DsnameType +import org.zowe.kotlinsdk.RecordFormat +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.Messages +import org.zowe.zdevops.classic.AbstractBuildStep +import org.zowe.zdevops.logic.allocateDataset +import org.zowe.zdevops.utils.validateDatasetName +import java.io.IOException +import javax.servlet.ServletException + +/** + * This class represents a build step that allocates a dataset + * + * @see org.zowe.zdevops.logic.AllocateOperation + */ +class AllocateDatasetStep +/** + * Constructs a new instance of AllocateDatasetStep + * + * @param connectionName The name of the z/OS connection + * @param dsn The name of the dataset to be allocated + * @param dsOrg The dataset organization + * @param primary The primary allocation size in cylinders or tracks + * @param secondary The secondary allocation size in cylinders or tracks + * @param recFm The record format + */ +@DataBoundConstructor +constructor( + connectionName: String, + val dsn: String, + val dsOrg: DatasetOrganization, + val primary: Int = 1, + var secondary: Int, + var recFm: RecordFormat, + var failOnExist: Boolean = false, +) : AbstractBuildStep(connectionName){ + + private var volser: String? = null + private var unit: String? = null + private var alcUnit : AllocationUnit? = null + private var dirBlk : Int? = null + private var blkSize: Int? = null + private var lrecl: Int? = 80 + private var storClass: String? = null + private var mgntClass: String? = null + private var dataClass: String? = null + private var avgBlk: Int? = null + private var dsnType: DsnameType? = null + private var dsModel: String? = null + + @DataBoundSetter + fun setVolser(volser: String?) { + this.volser = if (volser.isNullOrBlank()) null else volser + } + @DataBoundSetter + fun setUnit(unit: String?) { + this.unit = if (unit.isNullOrBlank()) null else unit + } + @DataBoundSetter + fun setAlcUnit(alcUnit: AllocationUnit) { this.alcUnit = alcUnit } + @DataBoundSetter + fun setDirBlk(dirBlk: Int?) { this.dirBlk = dirBlk } + @DataBoundSetter + fun setBlkSize(blkSize: Int?) { this.blkSize = blkSize } + @DataBoundSetter + fun setLrecl(lrecl: Int?) { this.lrecl = lrecl } + @DataBoundSetter + fun setStorClass(storClass: String?) { + this.storClass = if (storClass.isNullOrBlank()) null else storClass + } + @DataBoundSetter + fun setMgntClass(mgntClass: String?) { + this.mgntClass = if (mgntClass.isNullOrBlank()) null else mgntClass + } + @DataBoundSetter + fun setDataClass(dataClass: String?) { + this.dataClass = if (dataClass.isNullOrBlank()) null else dataClass + } + @DataBoundSetter + fun setAvgBlk(avgBlk: Int?) { this.avgBlk = avgBlk } + @DataBoundSetter + fun setDsnType(dsnType: DsnameType) { this.dsnType = dsnType } + @DataBoundSetter + fun setDsModel(dsModel: String?) { + this.dsModel = if (dsModel.isNullOrBlank()) null else dsModel + } + + fun getVolser(): String? { + return volser + } + fun getUnit(): String? { + return unit + } + fun getAlcUnit(): AllocationUnit? { + return alcUnit + } + fun getDirBlk(): Int? { + return dirBlk + } + fun getBlkSize(): Int? { + return blkSize + } + fun getLrecl(): Int? { + return lrecl + } + fun getStorClass(): String? { + return storClass + } + fun getMgntClass(): String? { + return mgntClass + } + fun getDataClass(): String? { + return dataClass + } + fun getAvgBlk(): Int? { + return avgBlk + } + fun getDsnType(): DsnameType? { + return dsnType + } + fun getDsModel(): String? { + return dsModel + } + + /** + * Performs the dataset allocation build step + * + * @param build The current build + * @param launcher The launcher + * @param listener The build listener + * @param zosConnection The z/OS connection + */ + override fun perform( + build: AbstractBuild<*, *>, + launcher: Launcher, + listener: BuildListener, + zosConnection: ZOSConnection + ) { + allocateDataset( + listener, + zosConnection, + dsn, + volser, + unit, + dsOrg, + alcUnit, + primary, + secondary, + dirBlk, + recFm, + blkSize, + lrecl, + storClass, + mgntClass, + dataClass, + avgBlk, + dsnType, + dsModel, + failOnExist, + ) + } + + /** + * The DescriptorImpl class represents the descriptor for the AllocateDatasetStep class + */ + @Extension + class DescriptorImpl : Companion.DefaultBuildDescriptor(Messages.zdevops_classic_allocateDatasetStep_display_name()) { + + /** + * Fills the allocation unit dropdown list with options + * + * @return The list box model for the allocation unit dropdown + */ + fun doFillAlcUnitItems(): ListBoxModel { + val result = ListBoxModel() + + result.add("CYL") + result.add("TRK") + + return result + } + + /** + * Fills the record format dropdown list with options + * + * @return The list box model for the record format dropdown + */ + fun doFillRecFmItems(): ListBoxModel { + val result = ListBoxModel() + + result.add("Fixed-length (F)", "F") + result.add("Fixed-length, blocked (FB)","FB") + result.add("Variable-length (V)", "V") + result.add("Variable-length, blocked (VB)","VB") + result.add("Undefined-length (U)", "U") + result.add("Variable-length, ASA print control characters (VA)","VA") + + return result + } + + /** + * Fills the dataset name type dropdown list with options + * + * @return The list box model for the dataset name type dropdown + */ + fun doFillDsnTypeItems(): ListBoxModel { + val result = ListBoxModel() + + result.add("BASIC") + result.add("LIBRARY") + result.add("HFS") + result.add("PDS") + result.add("LARGE") + result.add("EXTREQ") + result.add("EXTPREF") + + return result + } + + /** + * Fills the dataset organization dropdown list with options + * + * @return The list box model for the dataset organization dropdown + */ + fun doFillDsOrgItems(): ListBoxModel { + val result = ListBoxModel() + + result.add("Partitioned organized (PO)", "PO") + result.add("Partitioned Extended (POE)", "POE") + result.add("Physical sequentia (PS)", "PS") + + return result + } + + /** + * Validates the block size field + * + * @param lrecl The record length + * @param blkSize The block size + * @return The validation result + */ + @Throws(IOException::class, ServletException::class) + fun doCheckBlkSize(@QueryParameter lrecl: String, @QueryParameter blkSize: String): FormValidation? { + if (lrecl.isEmpty()) return FormValidation.ok() + try { + val lreclInt = Integer.parseInt(lrecl) + val blkSizeInt = Integer.parseInt(blkSize) + + if (lreclInt > blkSizeInt) return FormValidation.warning(Messages.zdevops_classic_allocateDatasetStep_blksize_smaller_than_lrecl_validation()) + return if (blkSizeInt % lreclInt == 0) FormValidation.ok() + else FormValidation.warning(Messages.zdevops_classic_allocateDatasetStep_blksize_validation_warning()) + } catch (e: NumberFormatException) { + return FormValidation.warning(Messages.zdevops_value_is_not_number_validation()) + } + } + + /** + * Validates the dataset name field + * + * @param dsn The dataset name + * @return The validation result + */ + fun doCheckDsn(@QueryParameter dsn: String): FormValidation? { + return validateDatasetName(dsn) + } + + /** + * Performs validation for the primary allocation size + * + * @param primary The value of the primary allocation size + * @return The validation result + */ + fun doCheckPrimary(@QueryParameter primary: String): FormValidation? { + if (primary.isEmpty()) return FormValidation.ok() + try { + val valueInt = primary.toInt() + if (valueInt == 0) return FormValidation.error(Messages.zdevops_classic_allocateDatasetStep_primary_is_zero_validation()) + } catch (e: NumberFormatException) { + return FormValidation.error(Messages.zdevops_value_is_not_number_validation()) + } + return convertStringAndValidateIntPositive(primary) + } + + /** + * Performs validation for the secondary allocation size + * + * @param secondary The value of the secondary allocation size + * @return The validation result + */ + fun doCheckSecondary(@QueryParameter secondary: String): FormValidation? { + return convertStringAndValidateIntPositive(secondary) + } + + /** + * Converts a string value to an integer and validates that it is a positive number + * + * @param value The value to be converted and validated + * @return The validation result as a FormValidation object + */ + fun convertStringAndValidateIntPositive(value: String): FormValidation? { + if (value.isEmpty()) return FormValidation.ok() + return try { + val valueInt = value.toInt() + return if (valueInt >= 0) FormValidation.ok() + else FormValidation.error(Messages.zdevops_value_must_be_positive_number_validation()) + } catch (e: NumberFormatException) { + FormValidation.error(Messages.zdevops_value_is_not_number_validation()) + } + } + + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetStep.kt b/src/main/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetStep.kt new file mode 100644 index 0000000..6974e54 --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetStep.kt @@ -0,0 +1,80 @@ +/* + * 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 + */ + +package org.zowe.zdevops.classic.steps + +import hudson.Extension +import hudson.Launcher +import hudson.model.AbstractBuild +import hudson.model.BuildListener +import hudson.util.FormValidation +import org.kohsuke.stapler.DataBoundConstructor +import org.kohsuke.stapler.QueryParameter +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.Messages +import org.zowe.zdevops.classic.AbstractBuildStep +import org.zowe.zdevops.logic.deleteDatasetOrMember +import org.zowe.zdevops.utils.validateDatasetName +import org.zowe.zdevops.utils.validateMemberName + +class DeleteDatasetStep +/** + * Constructs a new instance of the DeleteDatasetStep. + * + * @param connectionName The name of the z/OS connection to use for deleting the dataset. + * @param dsn The name of the dataset to delete. + * @param member The name of the member to delete. + */ +@DataBoundConstructor +constructor( + connectionName: String, + val dsn: String, + val member: String?, + val failOnNotExist: Boolean = false , +) : AbstractBuildStep(connectionName) { + + override fun perform( + build: AbstractBuild<*, *>, + launcher: Launcher, + listener: BuildListener, + zosConnection: ZOSConnection + ) { + deleteDatasetOrMember(dsn, member, zosConnection, listener, failOnNotExist) + } + + @Extension + class DescriptorImpl : + Companion.DefaultBuildDescriptor(Messages.zdevops_classic_deleteDatasetStep_display_name()) { + + /** + * Checks if the dataset name is valid + * + * @param dsn The dataset name + * @return FormValidation.ok() if the dataset name is valid, or an error message otherwise + */ + fun doCheckDsn(@QueryParameter dsn: String): FormValidation? { + return validateDatasetName(dsn) + } + + /** + * Checks if the member name is valid + * + * @param member The dataset member name + * @return FormValidation.ok() if either the member name is valid or is not provided, or an error message otherwise + */ + fun doCheckMember(@QueryParameter member: String): FormValidation? { + return if (member.isNotBlank()) { + validateMemberName(member) + } else { + FormValidation.ok() + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetsByMaskStep.kt b/src/main/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetsByMaskStep.kt new file mode 100644 index 0000000..ec82d84 --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetsByMaskStep.kt @@ -0,0 +1,49 @@ +/* + * 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 + */ + +package org.zowe.zdevops.classic.steps + +import hudson.Extension +import hudson.Launcher +import hudson.model.AbstractBuild +import hudson.model.BuildListener +import org.kohsuke.stapler.DataBoundConstructor +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.Messages +import org.zowe.zdevops.classic.AbstractBuildStep +import org.zowe.zdevops.logic.deleteDatasetsByMask + +class DeleteDatasetsByMaskStep +/** + * Constructs a new instance of the DeleteDatasetsByMaskStep. + * + * @param connectionName The name of the z/OS connection to use for deleting the dataset. + * @param dsnMask The name of a mask to find datasets for deletion. + */ +@DataBoundConstructor +constructor( + connectionName: String, + val dsnMask: String, + val failOnNotExist: Boolean = false, +) : AbstractBuildStep(connectionName) { + + override fun perform( + build: AbstractBuild<*, *>, + launcher: Launcher, + listener: BuildListener, + zosConnection: ZOSConnection + ) { + deleteDatasetsByMask(dsnMask, zosConnection, listener, failOnNotExist) + } + + @Extension + class DescriptorImpl : + Companion.DefaultBuildDescriptor(Messages.zdevops_classic_deleteDatasetsByMaskStep_display_name()) {} +} \ No newline at end of file diff --git a/src/main/kotlin/org/zowe/zdevops/classic/steps/DownloadDatasetStep.kt b/src/main/kotlin/org/zowe/zdevops/classic/steps/DownloadDatasetStep.kt new file mode 100644 index 0000000..fe1288d --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/classic/steps/DownloadDatasetStep.kt @@ -0,0 +1,104 @@ +/* + * 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 + */ + +package org.zowe.zdevops.classic.steps + +import hudson.Extension +import hudson.Launcher +import hudson.model.AbstractBuild +import hudson.model.BuildListener +import hudson.util.FormValidation +import org.kohsuke.stapler.DataBoundConstructor +import org.kohsuke.stapler.DataBoundSetter +import org.kohsuke.stapler.QueryParameter +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.Messages +import org.zowe.zdevops.classic.AbstractBuildStep +import org.zowe.zdevops.logic.downloadDSOrDSMemberByType +import org.zowe.zdevops.utils.validateDsnOrDsnMemberName + +/** + * The DownloadDatasetStep class is a build step that downloads a dataset or dataset members from a z/OS system. + */ +class DownloadDatasetStep +/** + * Constructs a new instance of the DownloadDatasetStep. + * + * @param connectionName The name of the z/OS connection to use for downloading the dataset. + * @param dsn The name of the dataset or the dataset member to download. + */ +@DataBoundConstructor +constructor( + connectionName: String, + val dsn: String +) :AbstractBuildStep(connectionName) { + + private var vol: String? = null + private var returnEtag: Boolean? = true + + @DataBoundSetter + fun setVol(vol: String) { + this.vol = if (vol.isNullOrBlank()) null else vol + } + + @DataBoundSetter + fun setReturnEtag(returnEtag: Boolean) { this.returnEtag = returnEtag } + + fun getVol(): String? = this.vol + + fun getReturnEtag(): Boolean? = this.returnEtag + + /** + * Performs the download a dataset or a dataset member step. + * + * @param build The build object. + * @param launcher The launcher object. + * @param listener The build listener. + * @param zosConnection The connection to the z/OS system. + */ + override fun perform( + build: AbstractBuild<*, *>, + launcher: Launcher, + listener: BuildListener, + zosConnection: ZOSConnection + ) { + val workspace = build.executor?.currentWorkspace!! + downloadDSOrDSMemberByType(dsn, vol, returnEtag, listener, zosConnection, workspace) + } + + + /** + * The descriptor for the DownloadDatasetStep class. + */ + @Extension + class DescriptorImpl : Companion.DefaultBuildDescriptor(Messages.zdevops_classic_downloadDatasetStep_display_name()) { + /** + * Checks if the dataset name is valid + * + * @param dsn The dataset name + * @return FormValidation.ok() if the dataset name is valid, or an error message otherwise + */ + fun doCheckDsn(@QueryParameter dsn: String): FormValidation? { + return validateDsnOrDsnMemberName(dsn) + } + + /** + * Checks if the volume name is valid. + * + * @param vol The volume name. + * @return FormValidation.ok() if the volume name is valid, or a warning message if it seems invalid. + */ + fun doCheckVol(@QueryParameter vol: String): FormValidation? { + return if (vol.length > 7) FormValidation.warning(Messages.zdevops_volume_name_is_invalid_validation()) + else FormValidation.ok() + } + + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/zowe/zdevops/classic/steps/PerformTsoCommandStep.kt b/src/main/kotlin/org/zowe/zdevops/classic/steps/PerformTsoCommandStep.kt new file mode 100644 index 0000000..57639a2 --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/classic/steps/PerformTsoCommandStep.kt @@ -0,0 +1,93 @@ +/* + * 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 + */ + +package org.zowe.zdevops.classic.steps + +import hudson.Extension +import hudson.Launcher +import hudson.model.AbstractBuild +import hudson.model.BuildListener +import hudson.util.FormValidation +import org.kohsuke.stapler.DataBoundConstructor +import org.kohsuke.stapler.QueryParameter +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.Messages +import org.zowe.zdevops.classic.AbstractBuildStep +import org.zowe.zdevops.logic.performTsoCommand +import org.zowe.zdevops.utils.validateFieldIsNotEmpty + + +/** + * A Jenkins Pipeline step for performing a TSO (Time Sharing Option) command on a z/OS system via freestyle job. + */ +class PerformTsoCommandStep +/** + * Data-bound constructor for the {@code PerformTsoCommandStep} step. + * + * @param connectionName The name of the z/OS connection to be used for executing the TSO command. + * @param acct The z/OS account number. + * @param command The TSO command to be executed. + */ +@DataBoundConstructor +constructor( + connectionName: String, + val acct: String, + val command: String, +) : AbstractBuildStep(connectionName) { + + /** + * Performs the TSO command execution step within a Jenkins Pipeline build. + * + * @param build The current Jenkins build. + * @param launcher The build launcher. + * @param listener The build listener. + * @param zosConnection The z/OS connection to execute the TSO command. + */ + override fun perform( + build: AbstractBuild<*, *>, + launcher: Launcher, + listener: BuildListener, + zosConnection: ZOSConnection + ) { + performTsoCommand(zosConnection, listener, acct, command) + } + + + /** + * Descriptor for the {@code PerformTsoCommandStep} step. + * + * This descriptor provides information about the step and makes it available for use + * within Jenkins Pipelines. + */ + @Extension + class DescriptorImpl : Companion.DefaultBuildDescriptor(Messages.zdevops_classic_performTsoCommandStep_display_name()) { + + /** + * Performs form validation for the 'acct' parameter to ensure it is not empty. + * + * @param acct The z/OS account number field value to validate. + * @return A {@link FormValidation} object indicating whether the field is valid or contains an error. + */ + fun doCheckAcct(@QueryParameter acct: String): FormValidation? { + return validateFieldIsNotEmpty(acct) + } + + /** + * Performs form validation for the 'command' parameter to ensure it is not empty. + * + * @param command The TSO command field value to validate. + * @return A {@link FormValidation} object indicating whether the field is valid or contains an error. + */ + fun doCheckCommand(@QueryParameter command: String): FormValidation? { + return validateFieldIsNotEmpty(command) + } + + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/zowe/zdevops/classic/steps/SubmitJobStep.kt b/src/main/kotlin/org/zowe/zdevops/classic/steps/SubmitJobStep.kt new file mode 100644 index 0000000..6a5e548 --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/classic/steps/SubmitJobStep.kt @@ -0,0 +1,70 @@ +/* + * 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 + */ + +package org.zowe.zdevops.classic.steps + +import hudson.AbortException +import hudson.Extension +import hudson.Launcher +import hudson.model.AbstractBuild +import hudson.model.BuildListener +import org.kohsuke.stapler.DataBoundConstructor +import org.kohsuke.stapler.bind.JavaScriptMethod +import org.zowe.zdevops.Messages +import org.zowe.zdevops.classic.AbstractBuildStep +import org.zowe.zdevops.logic.submitJob +import org.zowe.zdevops.logic.submitJobSync + +class SubmitJobStep +@DataBoundConstructor +constructor( + connectionName: String, + val jobName: String, + val sync: Boolean, + val checkRC: Boolean, +) : AbstractBuildStep(connectionName) { + override fun perform( + build: AbstractBuild<*, *>, + launcher: Launcher, + listener: BuildListener, + zosConnection: org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection + ) { + if (sync) { + val workspace = build.executor?.currentWorkspace!! + val linkBuilder: (String?, String, String) -> String = { jobUrl, jobName, jobId -> + "${jobUrl}ws/${jobName}.${jobId}/*view*/" + } + val jobResult = submitJobSync(jobName, zosConnection, listener, workspace, build.getEnvironment(listener)["JOB_URL"], linkBuilder) + if (checkRC && !jobResult.equals("CC 0000")) { + throw AbortException("Job RC code is not 0000") + } + } else { + submitJob(jobName, zosConnection, listener) + } + } + + + @Extension + class DescriptorImpl : Companion.DefaultBuildDescriptor(Messages.zdevops_classic_submitJobStep_display_name()) { + private var lastStepId = 0 + private val marker: String = "SJ" + + /** + * Creates a unique step ID + * + * @return The generated step ID + */ + @JavaScriptMethod + @Synchronized + fun createStepId(): String { + return marker + lastStepId++.toString() + } + } +} diff --git a/src/main/kotlin/org/zowe/zdevops/classic/steps/WriteFileToDatasetStep.kt b/src/main/kotlin/org/zowe/zdevops/classic/steps/WriteFileToDatasetStep.kt new file mode 100644 index 0000000..ca45d31 --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/classic/steps/WriteFileToDatasetStep.kt @@ -0,0 +1,188 @@ +/* + * 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 + */ + +package org.zowe.zdevops.classic.steps + +import hudson.AbortException +import hudson.Extension +import hudson.Launcher +import hudson.model.AbstractBuild +import hudson.model.BuildListener +import hudson.util.FormValidation +import hudson.util.ListBoxModel +import org.kohsuke.stapler.DataBoundConstructor +import org.kohsuke.stapler.DataBoundSetter +import org.kohsuke.stapler.QueryParameter +import org.kohsuke.stapler.bind.JavaScriptMethod +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.Messages +import org.zowe.zdevops.classic.AbstractBuildStep +import org.zowe.zdevops.logic.writeToDataset +import org.zowe.zdevops.utils.validateDatasetName +import org.zowe.zdevops.utils.validateFieldIsNotEmpty +import java.io.File + +/** + * A build step for writing a file to a dataset + * + * @see org.zowe.zdevops.logic.WriteOperation + */ +class WriteFileToDatasetStep +/** + * Constructs a new instance of WriteFileToDatasetStep. + * + * @param connectionName The name of the connection + * @param dsn The name of the dataset + * @param fileOption The option for selecting the file source (jenkins workspace file or local one) + */ +@DataBoundConstructor +constructor( + connectionName: String, + val dsn: String, + var fileOption: String?, +): AbstractBuildStep(connectionName) { + + private var localFilePath: String? = null + private var workspacePath: String? = null + + @DataBoundSetter + fun setLocalFilePath(localFilePath: String?) { + this.localFilePath = localFilePath + } + @DataBoundSetter + fun setWorkspacePath(workspacePath: String?) { + this.workspacePath = workspacePath + } + fun getLocalFilePath(): String? { + return this.localFilePath + } + fun getWorkspacePath(): String? { + return this.workspacePath + } + + /** + * Performs the write operation. + * + * @param build The build object + * @param launcher The launcher for executing commands + * @param listener The listener for logging messages + * @param zosConnection The ZOSConnection for interacting with z/OS + */ + override fun perform( + build: AbstractBuild<*, *>, + launcher: Launcher, + listener: BuildListener, + zosConnection: ZOSConnection + ) { + val workspace = build.executor?.currentWorkspace + val file = when (fileOption) { + DescriptorImpl().localFileOption -> localFilePath?.let { File(it) } + DescriptorImpl().workspaceFileOption -> { + val fileWorkspacePath = workspace?.remote + File.separator + workspacePath + File(fileWorkspacePath) + } + else -> throw AbortException(Messages.zdevops_classic_write_options_invalid()) + } + listener.logger.println(Messages.zdevops_declarative_writing_DS_from_file(dsn, file?.name, zosConnection.host, zosConnection.zosmfPort)) + val fileContent = file?.readText() + if (fileContent != null) { + writeToDataset(listener, zosConnection, dsn, fileContent) + } + } + + /** + * The descriptor for the WriteFileToDatasetStep + */ + @Extension + class DescriptorImpl : + Companion.DefaultBuildDescriptor(Messages.zdevops_classic_writeFileToDatasetStep_display_name()) { + + private var lastStepId = 0 + private val marker: String = "WFTD" + + val chooseFileOption = "choose" + val localFileOption = "local" + val workspaceFileOption = "workspace" + + /** + * Creates a unique step ID + * + * @return The generated step ID + */ + @JavaScriptMethod + @Synchronized + fun createStepId(): String { + return marker + lastStepId++.toString() + } + + /** + * Fills the file option items for the dropdown menu + * + * @return The ListBoxModel containing the file option items + */ + fun doFillFileOptionItems(): ListBoxModel { + val result = ListBoxModel() + + result.add(Messages.zdevops_classic_write_options_choose(), chooseFileOption) + result.add(Messages.zdevops_classic_write_options_local(), localFileOption) + result.add(Messages.zdevops_classic_write_options_workspace(), workspaceFileOption) + + return result + } + + /** + * Checks if the file option is valid + * + * @param fileOption The selected file option + * @return FormValidation.ok() if the file option is valid, or an error message otherwise + */ + fun doCheckFileOption(@QueryParameter fileOption: String): FormValidation? { + if (fileOption == chooseFileOption || fileOption.isEmpty()) return FormValidation.error(Messages.zdevops_classic_write_options_required()) + return FormValidation.ok() + } + + /** + * Checks if the dataset name is valid + * + * @param dsn The dataset name + * @return FormValidation.ok() if the dataset name is valid, or an error message otherwise + */ + fun doCheckDsn(@QueryParameter dsn: String): FormValidation? { + return validateDatasetName(dsn) + } + + /** + * Checks if the local file path not empty + * + * @param localFilePath The local file path + * @param fileOption The selected file option + * @return FormValidation.ok() if the local file path is not empty, or an error message otherwise + */ + fun doCheckLocalFilePath(@QueryParameter localFilePath: String, + @QueryParameter fileOption: String): FormValidation? { + return if (fileOption == localFileOption) validateFieldIsNotEmpty(localFilePath) + else FormValidation.ok() + } + + /** + * Checks if the workspace path is not empty + * + * @param workspacePath The workspace path + * @param fileOption The selected file option + * @return FormValidation.ok() if the workspace path is not empty, or an error message otherwise + */ + fun doCheckWorkspacePath(@QueryParameter workspacePath: String, + @QueryParameter fileOption: String): FormValidation? { + return if (fileOption == workspaceFileOption) validateFieldIsNotEmpty(workspacePath) + else FormValidation.ok() + } + + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/zowe/zdevops/classic/steps/WriteFileToFileStep.kt b/src/main/kotlin/org/zowe/zdevops/classic/steps/WriteFileToFileStep.kt new file mode 100644 index 0000000..1f7b427 --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/classic/steps/WriteFileToFileStep.kt @@ -0,0 +1,144 @@ +package org.zowe.zdevops.classic.steps + +import hudson.AbortException +import hudson.Extension +import hudson.Launcher +import hudson.model.AbstractBuild +import hudson.model.BuildListener +import hudson.util.FormValidation +import hudson.util.ListBoxModel +import org.kohsuke.stapler.DataBoundConstructor +import org.kohsuke.stapler.DataBoundSetter +import org.kohsuke.stapler.QueryParameter +import org.kohsuke.stapler.bind.JavaScriptMethod +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.Messages +import org.zowe.zdevops.classic.AbstractBuildStep +import org.zowe.zdevops.logic.writeToFile +import org.zowe.zdevops.utils.validateFieldIsNotEmpty +import java.io.File + +class WriteFileToFileStep +@DataBoundConstructor +constructor( + connectionName: String, + val filePathUSS: String, + var binary: Boolean = false, + var fileOption: String?, +) : AbstractBuildStep(connectionName) { + + private var localFilePath: String? = null + private var workspacePath: String? = null + + @DataBoundSetter + fun setLocalFilePath(localFilePath: String?) { + this.localFilePath = localFilePath + } + @DataBoundSetter + fun setWorkspacePath(workspacePath: String?) { + this.workspacePath = workspacePath + } + fun getLocalFilePath(): String? { + return this.localFilePath + } + fun getWorkspacePath(): String? { + return this.workspacePath + } + + override fun perform( + build: AbstractBuild<*, *>, + launcher: Launcher, + listener: BuildListener, + zosConnection: ZOSConnection + ) { + val sourcePath = if(workspacePath != null) workspacePath else localFilePath + listener.logger.println(Messages.zdevops_declarative_writing_file_from_file(filePathUSS, sourcePath, zosConnection.host, zosConnection.zosmfPort)) + val workspace = build.executor?.currentWorkspace + val file = when (fileOption) { + DescriptorImpl().localFileOption -> localFilePath?.let { File(it) } + DescriptorImpl().workspaceFileOption -> { + val fileWorkspacePath = workspace?.remote + File.separator + workspacePath + File(fileWorkspacePath) + } + else -> throw AbortException(Messages.zdevops_classic_write_options_invalid()) + } + val text = file?.readBytes() + if (text != null) { + writeToFile(listener, zosConnection, filePathUSS, text, binary) + } + } + + @Extension + class DescriptorImpl : + Companion.DefaultBuildDescriptor(Messages.zdevops_classic_writeFileToFileStep_display_name()) { + private var lastStepId = 0 + private val marker: String = "WFTF" + + val chooseFileOption = "choose" + val localFileOption = "local" + val workspaceFileOption = "workspace" + + /** + * Creates a unique step ID + * + * @return The generated step ID + */ + @JavaScriptMethod + @Synchronized + fun createStepId(): String { + return marker + lastStepId++.toString() + } + + /** + * Fills the file option items for the dropdown menu + * + * @return The ListBoxModel containing the file option items + */ + fun doFillFileOptionItems(): ListBoxModel { + val result = ListBoxModel() + + result.add(Messages.zdevops_classic_write_options_choose(), chooseFileOption) + result.add(Messages.zdevops_classic_write_options_local(), localFileOption) + result.add(Messages.zdevops_classic_write_options_workspace(), workspaceFileOption) + + return result + } + + /** + * Checks if the file option is valid + * + * @param fileOption The selected file option + * @return FormValidation.ok() if the file option is valid, or an error message otherwise + */ + fun doCheckFileOption(@QueryParameter fileOption: String): FormValidation? { + if (fileOption == chooseFileOption || fileOption.isEmpty()) return FormValidation.error(Messages.zdevops_classic_write_options_required()) + return FormValidation.ok() + } + + /** + * Checks if the local file path not empty + * + * @param localFilePath The local file path + * @param fileOption The selected file option + * @return FormValidation.ok() if the local file path is not empty, or an error message otherwise + */ + fun doCheckLocalFilePath(@QueryParameter localFilePath: String, + @QueryParameter fileOption: String): FormValidation? { + return if (fileOption == localFileOption) validateFieldIsNotEmpty(localFilePath) + else FormValidation.ok() + } + + /** + * Checks if the workspace path is not empty + * + * @param workspacePath The workspace path + * @param fileOption The selected file option + * @return FormValidation.ok() if the workspace path is not empty, or an error message otherwise + */ + fun doCheckWorkspacePath(@QueryParameter workspacePath: String, + @QueryParameter fileOption: String): FormValidation? { + return if (fileOption == workspaceFileOption) validateFieldIsNotEmpty(workspacePath) + else FormValidation.ok() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/zowe/zdevops/classic/steps/WriteFileToMemberStep.kt b/src/main/kotlin/org/zowe/zdevops/classic/steps/WriteFileToMemberStep.kt new file mode 100644 index 0000000..c9c35a8 --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/classic/steps/WriteFileToMemberStep.kt @@ -0,0 +1,201 @@ +/* + * 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 + */ + +package org.zowe.zdevops.classic.steps + +import hudson.AbortException +import hudson.Extension +import hudson.Launcher +import hudson.model.AbstractBuild +import hudson.model.BuildListener +import hudson.util.FormValidation +import hudson.util.ListBoxModel +import org.kohsuke.stapler.DataBoundConstructor +import org.kohsuke.stapler.DataBoundSetter +import org.kohsuke.stapler.QueryParameter +import org.kohsuke.stapler.bind.JavaScriptMethod +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.Messages +import org.zowe.zdevops.classic.AbstractBuildStep +import org.zowe.zdevops.logic.writeToMember +import org.zowe.zdevops.utils.validateDatasetName +import org.zowe.zdevops.utils.validateFieldIsNotEmpty +import org.zowe.zdevops.utils.validateMemberName +import java.io.File + +/** + * A build step for writing a file to a member in a dataset + * + * @see org.zowe.zdevops.logic.WriteOperation + */ +class WriteFileToMemberStep +/** + * Constructs a new instance of WriteFileToMemberStep. + * + * @param connectionName The name of the z/OS connection + * @param dsn The name of the dataset + * @param member The name of the member + * @param fileOption The option for selecting the file source + */ +@DataBoundConstructor +constructor( + connectionName: String, + val dsn: String, + val member: String, + var fileOption: String?, +): AbstractBuildStep(connectionName) { + + private var localFilePath: String? = null + private var workspacePath: String? = null + + @DataBoundSetter + fun setLocalFilePath(localFilePath: String?) { + this.localFilePath = localFilePath + } + @DataBoundSetter + fun setWorkspacePath(workspacePath: String?) { + this.workspacePath = workspacePath + } + fun getLocalFilePath(): String? { + return this.localFilePath + } + fun getWorkspacePath(): String? { + return this.workspacePath + } + + /** + * Performs the write operation + * + * @param build The build object + * @param launcher The launcher for executing commands + * @param listener The listener for logging messages + * @param zosConnection The ZOSConnection for interacting with z/OS + */ + override fun perform( + build: AbstractBuild<*, *>, + launcher: Launcher, + listener: BuildListener, + zosConnection: ZOSConnection, + ) { + val workspace = build.executor?.currentWorkspace + val file = when (fileOption) { + DescriptorImpl().localFileOption -> localFilePath?.let { File(it) } + DescriptorImpl().workspaceFileOption -> { + val fileWorkspacePath = workspace?.remote + '\\' + workspacePath + File(fileWorkspacePath) + } + else -> throw AbortException(Messages.zdevops_classic_write_options_invalid()) + } + listener.logger.println(Messages.zdevops_declarative_writing_DS_from_file(dsn, file?.name, zosConnection.host, zosConnection.zosmfPort)) + val fileContent = file?.readText() + if (fileContent != null) { + writeToMember(listener, zosConnection, dsn, member, fileContent) + } + } + + /** + * The descriptor for the WriteFileToMemberStep + */ + @Extension + class DescriptorImpl : + Companion.DefaultBuildDescriptor(Messages.zdevops_classic_writeFileToMemberStep_display_name()) { + + private var lastStepId = 0 + private val marker: String = "WFTM" + + val chooseFileOption = "choose" + val localFileOption = "local" + val workspaceFileOption = "workspace" + + /** + * Creates a unique step ID + * + * @return The generated step ID + */ + @JavaScriptMethod + @Synchronized + fun createStepId(): String { + return marker + lastStepId++.toString() + } + + /** + * Fills the file option items for the dropdown menu + * + * @return The ListBoxModel containing the file option items + */ + fun doFillFileOptionItems(): ListBoxModel { + val result = ListBoxModel() + + result.add(Messages.zdevops_classic_write_options_choose(), chooseFileOption) + result.add(Messages.zdevops_classic_write_options_local(), localFileOption) + result.add(Messages.zdevops_classic_write_options_workspace(), workspaceFileOption) + + return result + } + + /** + * Checks if the file option is valid + * + * @param fileOption The selected file option + * @return FormValidation.ok() if the file option is valid, or an error message otherwise + */ + fun doCheckFileOption(@QueryParameter fileOption: String): FormValidation? { + if (fileOption == chooseFileOption || fileOption.isEmpty()) return FormValidation.error(Messages.zdevops_classic_write_options_required()) + return FormValidation.ok() + } + + /** + * Checks if the dataset name is valid + * + * @param dsn The dataset name + * @return FormValidation.ok() if the dataset name is valid, or an error message otherwise + */ + fun doCheckDsn(@QueryParameter dsn: String): FormValidation? { + return validateDatasetName(dsn) + } + + /** + * Validates the member name + * + * @param member The member name + * @return FormValidation.ok() if the member name is valid, or an error message otherwise + */ + fun doCheckMember(@QueryParameter member: String): FormValidation? { + return validateMemberName(member)?: validateFieldIsNotEmpty(member) + } + + /** + * Checks if the local file path not empty + * + * @param localFilePath The local file path + * @param fileOption The selected file option + * @return FormValidation.ok() if the local file path is not empty, or an error message otherwise + */ + fun doCheckLocalFilePath(@QueryParameter localFilePath: String, + @QueryParameter fileOption: String): FormValidation? { + return if (fileOption == localFileOption) validateFieldIsNotEmpty(localFilePath) + else FormValidation.ok() + } + + /** + * Checks if the workspace path is not empty + * + * @param workspacePath The workspace path + * @param fileOption The selected file option + * @return FormValidation.ok() if the workspace path is not empty, or an error message otherwise + */ + fun doCheckWorkspacePath(@QueryParameter workspacePath: String, + @QueryParameter fileOption: String): FormValidation? { + return if (fileOption == workspaceFileOption) validateFieldIsNotEmpty(workspacePath) + else FormValidation.ok() + } + + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/zowe/zdevops/classic/steps/WriteToDatasetStep.kt b/src/main/kotlin/org/zowe/zdevops/classic/steps/WriteToDatasetStep.kt new file mode 100644 index 0000000..e1ce921 --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/classic/steps/WriteToDatasetStep.kt @@ -0,0 +1,89 @@ +/* + * 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 + */ + +package org.zowe.zdevops.classic.steps + +import hudson.Extension +import hudson.Launcher +import hudson.model.AbstractBuild +import hudson.model.BuildListener +import hudson.util.FormValidation +import org.kohsuke.stapler.DataBoundConstructor +import org.kohsuke.stapler.QueryParameter +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.Messages +import org.zowe.zdevops.classic.AbstractBuildStep +import org.zowe.zdevops.logic.writeToDataset +import org.zowe.zdevops.utils.validateDatasetName +import org.zowe.zdevops.utils.validateFieldIsNotEmpty + +/** + * A build step for writing text(string) to a dataset + */ +class WriteToDatasetStep +/** + * Constructs a new instance of WriteToDatasetStep + * + * @param connectionName The name of the connection + * @param dsn The name of the dataset + * @param text The text to write to the dataset + */ +@DataBoundConstructor +constructor( + connectionName: String, + val dsn: String, + val text: String +) : AbstractBuildStep(connectionName) { + + /** + * Performs the write operation + * + * @param build The build object + * @param launcher The launcher for executing commands + * @param listener The listener for logging messages + * @param zosConnection The ZOSConnection for interacting with z/OS + */ + override fun perform( + build: AbstractBuild<*, *>, + launcher: Launcher, + listener: BuildListener, + zosConnection: ZOSConnection + ) { + listener.logger.println(Messages.zdevops_declarative_writing_DS_from_input(dsn, zosConnection.host, zosConnection.zosmfPort)) + writeToDataset(listener, zosConnection, dsn, text) + } + + /** + * The descriptor for the WriteToDatasetStep + */ + @Extension + class DescriptorImpl : Companion.DefaultBuildDescriptor(Messages.zdevops_classic_writeToDSStep_display_name()) { + /** + * Validates the dataset name + * + * @param dsn The dataset name + * @return FormValidation.ok() if the dataset name is valid, or an error message otherwise + */ + fun doCheckDsn(@QueryParameter dsn: String): FormValidation? { + return validateDatasetName(dsn) + } + + /** + * Validates the text to write + * + * @param text The text to write + * @return FormValidation.ok() if the text is not empty, or an error message otherwise + */ + fun doCheckText(@QueryParameter text: String): FormValidation? { + return validateFieldIsNotEmpty(text) + } + + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/zowe/zdevops/classic/steps/WriteToFileStep.kt b/src/main/kotlin/org/zowe/zdevops/classic/steps/WriteToFileStep.kt new file mode 100644 index 0000000..a423d70 --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/classic/steps/WriteToFileStep.kt @@ -0,0 +1,90 @@ +/* + * 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 + */ + +package org.zowe.zdevops.classic.steps + +import hudson.Extension +import hudson.Launcher +import hudson.model.AbstractBuild +import hudson.model.BuildListener +import hudson.util.FormValidation +import org.kohsuke.stapler.DataBoundConstructor +import org.kohsuke.stapler.QueryParameter +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.Messages +import org.zowe.zdevops.classic.AbstractBuildStep +import org.zowe.zdevops.logic.writeToFile +import org.zowe.zdevops.utils.validateFieldIsNotEmpty + +/** + * A build step for writing text(string) to a USS file + */ +class WriteToFileStep +/** + * Constructs a new instance of WriteToFileStep. + * + * @param filePath The name of the z/OS connection + * @param text The name of the dataset + * @param binary The name of the member + */ +@DataBoundConstructor +constructor( + connectionName: String, + val filePath: String, + val text: String, + var binary: Boolean = false, +) : AbstractBuildStep(connectionName) { + + /** + * Performs the write operation + * + * @param build The build object + * @param launcher The launcher for executing commands + * @param listener The listener for logging messages + * @param zosConnection The ZOSConnection for interacting with z/OS + */ + override fun perform( + build: AbstractBuild<*, *>, + launcher: Launcher, + listener: BuildListener, + zosConnection: ZOSConnection + ) { + listener.logger.println(Messages.zdevops_declarative_writing_file_from_input(filePath, zosConnection.host, zosConnection.zosmfPort)) + val textBytes = text.toByteArray() + writeToFile(listener, zosConnection, filePath, textBytes, binary) + } + + /** + * The descriptor for the WriteToFileStep + */ + @Extension + class DescriptorImpl : Companion.DefaultBuildDescriptor(Messages.zdevops_classic_writeToFileStep_display_name()) { + /** + * Validates the file path + * + * @param filePath The file path to write to + * @return FormValidation.ok() if the path is not empty, or an error message otherwise + */ + fun doCheckFilePath(@QueryParameter filePath: String): FormValidation? { + return validateFieldIsNotEmpty(filePath) + } + + /** + * Validates the text to write + * + * @param text The text to write + * @return FormValidation.ok() if the text is not empty, or an error message otherwise + */ + fun doCheckText(@QueryParameter text: String): FormValidation? { + return validateFieldIsNotEmpty(text) + } + + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/zowe/zdevops/classic/steps/WriteToMemberStep.kt b/src/main/kotlin/org/zowe/zdevops/classic/steps/WriteToMemberStep.kt new file mode 100644 index 0000000..3a63a90 --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/classic/steps/WriteToMemberStep.kt @@ -0,0 +1,101 @@ +/* + * 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 + */ + +package org.zowe.zdevops.classic.steps + +import hudson.Extension +import hudson.Launcher +import hudson.model.AbstractBuild +import hudson.model.BuildListener +import hudson.util.FormValidation +import org.kohsuke.stapler.DataBoundConstructor +import org.kohsuke.stapler.QueryParameter +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.Messages +import org.zowe.zdevops.classic.AbstractBuildStep +import org.zowe.zdevops.logic.writeToMember +import org.zowe.zdevops.utils.validateDatasetName +import org.zowe.zdevops.utils.validateFieldIsNotEmpty +import org.zowe.zdevops.utils.validateMemberName + +/** + * A build step for writing text to a member of a dataset + */ +class WriteToMemberStep +/** + * Constructs a new instance of WriteToMemberStep + * + * @param connectionName The name of the connection + * @param dsn The name of the dataset + * @param member The name of the member within the dataset + * @param text The text to write to the member + */ +@DataBoundConstructor +constructor( + connectionName: String, + val dsn: String, + val member: String, + val text: String +) : AbstractBuildStep(connectionName) { + + /** + * Performs the write operation + * + * @param build The build object + * @param launcher The launcher for executing commands + * @param listener The listener for logging messages + * @param zosConnection The ZOSConnection for interacting with z/OS + */ + override fun perform( + build: AbstractBuild<*, *>, + launcher: Launcher, + listener: BuildListener, + zosConnection: ZOSConnection + ) { + listener.logger.println(Messages.zdevops_declarative_writing_DS_from_input(dsn, zosConnection.host, zosConnection.zosmfPort)) + writeToMember(listener, zosConnection, dsn, member, text) + } + + /** + * The descriptor for the WriteToMemberStep. + */ + @Extension + class DescriptorImpl : Companion.DefaultBuildDescriptor(Messages.zdevops_classic_writeToMemberStep_display_name()) { + /** + * Validates the dataset name + * + * @param dsn The dataset name + * @return FormValidation.ok() if the dataset name is valid, or an error message otherwise + */ + fun doCheckDsn(@QueryParameter dsn: String): FormValidation? { + return validateDatasetName(dsn) + } + + /** + * Validates the text to write + * + * @param text The text to write + * @return FormValidation.ok() if the text is not empty, or an error message otherwise + */ + fun doCheckText(@QueryParameter text: String): FormValidation? { + return validateFieldIsNotEmpty(text) + } + + /** + * Validates the member name + * + * @param member The member name + * @return FormValidation.ok() if the member name is valid, or an error message otherwise + */ + fun doCheckMember(@QueryParameter member: String): FormValidation? { + return validateMemberName(member) ?: validateFieldIsNotEmpty(member) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/jenkins/plugins/zdevops/config/ZOSConnection.kt b/src/main/kotlin/org/zowe/zdevops/config/ZOSConnection.kt similarity index 89% rename from src/main/kotlin/io/jenkins/plugins/zdevops/config/ZOSConnection.kt rename to src/main/kotlin/org/zowe/zdevops/config/ZOSConnection.kt index 13c2693..ececb4b 100644 --- a/src/main/kotlin/io/jenkins/plugins/zdevops/config/ZOSConnection.kt +++ b/src/main/kotlin/org/zowe/zdevops/config/ZOSConnection.kt @@ -8,7 +8,7 @@ * Copyright IBA Group 2022 */ -package io.jenkins.plugins.zdevops.config; +package org.zowe.zdevops.config import com.cloudbees.plugins.credentials.CredentialsMatchers import com.cloudbees.plugins.credentials.CredentialsProvider @@ -16,10 +16,6 @@ import com.cloudbees.plugins.credentials.common.StandardCredentials import com.cloudbees.plugins.credentials.common.StandardListBoxModel import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder -import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.ZosDsnList -import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.input.ListParams -import io.jenkins.plugins.zdevops.Messages -import io.jenkins.plugins.zdevops.declarative.jobs.zMessages import hudson.Extension import hudson.model.AbstractDescribableImpl import hudson.model.Descriptor @@ -32,6 +28,9 @@ import net.sf.json.JSONObject import org.kohsuke.stapler.DataBoundConstructor import org.kohsuke.stapler.QueryParameter import org.kohsuke.stapler.StaplerRequest +import org.zowe.zdevops.Messages +import org.zowe.zdevops.declarative.jobs.zMessages +import org.zowe.zdevops.utils.getTestDatasetList import java.io.IOException import java.io.ObjectInputStream import java.io.ObjectOutputStream @@ -119,9 +118,9 @@ constructor( val testConnection = org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection( connURL.host, connURL.port.toString(), credentials.username, credentials.password.plainText, connURL.protocol ) - ZosDsnList(testConnection).listDsn(zMessages.zdevops_config_ZOSConnection_validation_testDS(), ListParams()) + getTestDatasetList(testConnection) }.onFailure { - return FormValidation.error(zMessages.zdevops_config_ZOSConnection_validation_error()); + return FormValidation.error("${zMessages.zdevops_config_ZOSConnection_validation_error()}\n(${it.message})"); } return FormValidation.ok(zMessages.zdevops_config_ZOSConnection_validation_success()) } diff --git a/src/main/kotlin/io/jenkins/plugins/zdevops/config/ZOSConnectionList.kt b/src/main/kotlin/org/zowe/zdevops/config/ZOSConnectionList.kt similarity index 95% rename from src/main/kotlin/io/jenkins/plugins/zdevops/config/ZOSConnectionList.kt rename to src/main/kotlin/org/zowe/zdevops/config/ZOSConnectionList.kt index d155a71..2e2c3d4 100644 --- a/src/main/kotlin/io/jenkins/plugins/zdevops/config/ZOSConnectionList.kt +++ b/src/main/kotlin/org/zowe/zdevops/config/ZOSConnectionList.kt @@ -8,14 +8,14 @@ * Copyright IBA Group 2022 */ -package io.jenkins.plugins.zdevops.config; +package org.zowe.zdevops.config; import com.cloudbees.plugins.credentials.CredentialsMatchers import com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials import com.cloudbees.plugins.credentials.common.StandardCredentials import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder -import io.jenkins.plugins.zdevops.model.ResolvedZOSConnection +import org.zowe.zdevops.model.ResolvedZOSConnection import hudson.Extension import hudson.security.ACL import jenkins.model.GlobalConfiguration diff --git a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/AbstractZosmfAction.kt b/src/main/kotlin/org/zowe/zdevops/declarative/AbstractZosmfAction.kt similarity index 72% rename from src/main/kotlin/io/jenkins/plugins/zdevops/declarative/AbstractZosmfAction.kt rename to src/main/kotlin/org/zowe/zdevops/declarative/AbstractZosmfAction.kt index 118a588..e985c1e 100644 --- a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/AbstractZosmfAction.kt +++ b/src/main/kotlin/org/zowe/zdevops/declarative/AbstractZosmfAction.kt @@ -8,11 +8,8 @@ * Copyright IBA Group 2022 */ -package io.jenkins.plugins.zdevops.declarative +package org.zowe.zdevops.declarative -import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection -import io.jenkins.plugins.zdevops.Messages -import io.jenkins.plugins.zdevops.config.ZOSConnectionList import hudson.AbortException import hudson.EnvVars import hudson.FilePath @@ -23,9 +20,10 @@ import hudson.model.TaskListener import hudson.tasks.BuildStepDescriptor import hudson.tasks.Builder import jenkins.tasks.SimpleBuildStep +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.utils.getZoweZosConnection import java.io.PrintWriter import java.io.StringWriter -import java.net.URL import java.nio.charset.StandardCharsets abstract class AbstractZosmfAction : Builder(), SimpleBuildStep { @@ -38,18 +36,8 @@ abstract class AbstractZosmfAction : Builder(), SimpleBuildStep { override fun perform(run: Run<*, *>, workspace: FilePath, env: EnvVars, launcher: Launcher, listener: TaskListener) { val connectionName = workspace.read().readBytes().toString(StandardCharsets.UTF_8) - val connection = ZOSConnectionList.resolve(connectionName) ?: let { + val zoweConnection = getZoweZosConnection(connectionName, listener) - val exception = IllegalArgumentException(Messages.zdevops_config_ZOSConnection_resolve_unknown(connectionName)) - val sw = StringWriter() - exception.printStackTrace(PrintWriter(sw)) - listener.logger.println(sw.toString()) - throw exception - } - val connURL = URL(connection.url) - val zoweConnection = ZOSConnection( - connURL.host, connURL.port.toString(), connection.username, connection.password, connURL.protocol - ) runCatching { perform(run, workspace, env, launcher, listener, zoweConnection) }.onFailure { @@ -63,7 +51,7 @@ abstract class AbstractZosmfAction : Builder(), SimpleBuildStep { companion object { open class DefaultBuildDescriptor(private val descriptorDisplayName: String = ""): BuildStepDescriptor() { override fun getDisplayName() = descriptorDisplayName - override fun isApplicable(jobType: Class>?) = true + override fun isApplicable(jobType: Class>?) = false } } diff --git a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/ZosmfExecution.kt b/src/main/kotlin/org/zowe/zdevops/declarative/ZosmfExecution.kt similarity index 96% rename from src/main/kotlin/io/jenkins/plugins/zdevops/declarative/ZosmfExecution.kt rename to src/main/kotlin/org/zowe/zdevops/declarative/ZosmfExecution.kt index f072efc..b8ea61d 100644 --- a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/ZosmfExecution.kt +++ b/src/main/kotlin/org/zowe/zdevops/declarative/ZosmfExecution.kt @@ -8,7 +8,7 @@ * Copyright IBA Group 2022 */ -package io.jenkins.plugins.zdevops.declarative +package org.zowe.zdevops.declarative import hudson.FilePath import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback diff --git a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/ZosmfStepDeclarative.kt b/src/main/kotlin/org/zowe/zdevops/declarative/ZosmfStepDeclarative.kt similarity index 78% rename from src/main/kotlin/io/jenkins/plugins/zdevops/declarative/ZosmfStepDeclarative.kt rename to src/main/kotlin/org/zowe/zdevops/declarative/ZosmfStepDeclarative.kt index df68f00..75349ed 100644 --- a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/ZosmfStepDeclarative.kt +++ b/src/main/kotlin/org/zowe/zdevops/declarative/ZosmfStepDeclarative.kt @@ -8,22 +8,29 @@ * Copyright IBA Group 2022 */ -package io.jenkins.plugins.zdevops.declarative +package org.zowe.zdevops.declarative import hudson.EnvVars import hudson.Extension import hudson.FilePath -import hudson.model.* import hudson.model.Run +import hudson.model.TaskListener import org.jenkinsci.plugins.workflow.steps.Step import org.jenkinsci.plugins.workflow.steps.StepContext import org.jenkinsci.plugins.workflow.steps.StepDescriptor import org.jenkinsci.plugins.workflow.steps.StepExecution import org.kohsuke.stapler.DataBoundConstructor +import org.zowe.zdevops.utils.getZoweZosConnection +import org.zowe.zdevops.utils.validateConnection class ZosmfStepDeclarative @DataBoundConstructor constructor(private val connectionName: String) : Step() { override fun start(context: StepContext): StepExecution { + val listener: TaskListener? = context.get(TaskListener::class.java) + val zosConnection = getZoweZosConnection(connectionName, listener) + + validateConnection(zosConnection) + return ZosmfExecution(connectionName, context) } diff --git a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/AllocateDatasetDeclarative.kt b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/AllocateDatasetDeclarative.kt similarity index 63% rename from src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/AllocateDatasetDeclarative.kt rename to src/main/kotlin/org/zowe/zdevops/declarative/jobs/AllocateDatasetDeclarative.kt index 4db03a6..09c63ed 100644 --- a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/AllocateDatasetDeclarative.kt +++ b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/AllocateDatasetDeclarative.kt @@ -8,35 +8,52 @@ * Copyright IBA Group 2022 */ -package io.jenkins.plugins.zdevops.declarative.jobs +package org.zowe.zdevops.declarative.jobs -import org.zowe.kotlinsdk.* -import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection -import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.ZosDsn -import io.jenkins.plugins.zdevops.declarative.AbstractZosmfAction -import hudson.* +import hudson.EnvVars +import hudson.Extension import hudson.FilePath +import hudson.Launcher import hudson.model.Run import hudson.model.TaskListener import org.jenkinsci.Symbol import org.kohsuke.stapler.DataBoundConstructor import org.kohsuke.stapler.DataBoundSetter +import org.zowe.kotlinsdk.* +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.Messages +import org.zowe.zdevops.declarative.AbstractZosmfAction +import org.zowe.zdevops.logic.allocateDataset -class AllocateDatasetDeclarative @DataBoundConstructor constructor(private val dsn: String, - private val dsOrg: DatasetOrganization, - private val primary: Int, - private var secondary: Int, - private var recFm: RecordFormat) : +/** + * Represents an action for allocating a dataset in a declarative style + * + * @see org.zowe.zdevops.logic.AllocateOperation + */ +class AllocateDatasetDeclarative +/** + * Constructs a new instance of AllocateDatasetDeclarative + * + * @param dsn The name of the dataset to be allocated + * @param dsOrg The dataset organization + * @param primary The primary allocation size in cylinders or tracks + * @param secondary The secondary allocation size in cylinders or tracks + * @param recFm The record format + */ +@DataBoundConstructor +constructor( + private val dsn: String, + private val dsOrg: DatasetOrganization, + private val primary: Int, + private var secondary: Int, + private var recFm: RecordFormat, + private var failOnExist: Boolean = false) : AbstractZosmfAction() { private var volser: String? = null private var unit: String? = null -// private var dsOrg: DatasetOrganization? = null private var alcUnit : AllocationUnit? = null -// private var primary: Int? = null -// private var secondary: Int? = null private var dirBlk : Int? = null -// private var recFm: RecordFormat? = null private var blkSize: Int? = null private var lrecl: Int? = null private var storClass: String? = null @@ -50,19 +67,11 @@ class AllocateDatasetDeclarative @DataBoundConstructor constructor(private val d fun setVolser(volser: String) { this.volser = volser } @DataBoundSetter fun setUnit(unit: String) { this.unit = unit } -// @DataBoundSetter -// fun setDsOrg(dsOrg: DatasetOrganization) { this.dsOrg = dsOrg } @DataBoundSetter fun setAlcUnit(alcUnit: AllocationUnit) { this.alcUnit = alcUnit } -// @DataBoundSetter -// fun setPrimary(primary: Int) { this.primary = primary } -// @DataBoundSetter -// fun setSecondary(secondary: Int) { this.secondary = secondary } @DataBoundSetter fun setDirBlk(dirBlk: Int) { this.dirBlk = dirBlk } @DataBoundSetter -// fun setRecFm(recFm: RecordFormat) { this.recFm = recFm } -// @DataBoundSetter fun setBlkSize(blkSize: Int) { this.blkSize = blkSize } @DataBoundSetter fun setLrecl(lrecl: Int) { this.lrecl = lrecl } @@ -79,8 +88,18 @@ class AllocateDatasetDeclarative @DataBoundConstructor constructor(private val d @DataBoundSetter fun setDsModel(dsModel: String) { this.dsModel = dsModel } - override val exceptionMessage: String = zMessages.zdevops_declarative_DSN_allocated_fail(dsn) + override val exceptionMessage: String = Messages.zdevops_declarative_DSN_allocated_fail(dsn) + /** + * Performs the allocation of the dataset + * + * @param run The current build/run + * @param workspace The workspace where the build is being executed + * @param env The environment variables for the build + * @param launcher The launcher for executing commands + * @param listener The listener for logging messages + * @param zosConnection The ZOSConnection for interacting with z/OS + */ override fun perform( run: Run<*, *>, workspace: FilePath, @@ -89,8 +108,10 @@ class AllocateDatasetDeclarative @DataBoundConstructor constructor(private val d listener: TaskListener, zosConnection: ZOSConnection ) { - listener.logger.println(zMessages.zdevops_declarative_DSN_allocating(dsn, zosConnection.host, zosConnection.zosmfPort)) - val alcParms = CreateDataset( + allocateDataset( + listener, + zosConnection, + dsn, volser, unit, dsOrg, @@ -106,13 +127,15 @@ class AllocateDatasetDeclarative @DataBoundConstructor constructor(private val d dataClass, avgBlk, dsnType, - dsModel + dsModel, + failOnExist, ) - val allocatedDS = ZosDsn(zosConnection).createDsn(dsn, alcParms) - listener.logger.println(zMessages.zdevops_declarative_DSN_allocated_success(dsn)) } + /** + * The DescriptorImpl class represents the descriptor for the AllocateDatasetDeclarative class + */ @Symbol("allocateDS") @Extension class DescriptorImpl : Companion.DefaultBuildDescriptor("Allocate Dataset Declarative") diff --git a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/DeleteDatasetDeclarative.kt b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/DeleteDatasetDeclarative.kt similarity index 88% rename from src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/DeleteDatasetDeclarative.kt rename to src/main/kotlin/org/zowe/zdevops/declarative/jobs/DeleteDatasetDeclarative.kt index 0e65f85..fc72a4e 100644 --- a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/DeleteDatasetDeclarative.kt +++ b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/DeleteDatasetDeclarative.kt @@ -8,18 +8,17 @@ * Copyright IBA Group 2022 */ -package io.jenkins.plugins.zdevops.declarative.jobs +package org.zowe.zdevops.declarative.jobs -import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection -import io.jenkins.plugins.zdevops.declarative.AbstractZosmfAction -import io.jenkins.plugins.zdevops.logic.DeleteOperation import hudson.* -import hudson.FilePath import hudson.model.Run import hudson.model.TaskListener import org.jenkinsci.Symbol import org.kohsuke.stapler.DataBoundConstructor import org.kohsuke.stapler.DataBoundSetter +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.declarative.AbstractZosmfAction +import org.zowe.zdevops.logic.deleteDatasetOrMember /** * This class contains delete mainframe dataset operation description @@ -67,6 +66,7 @@ class DeleteDatasetDeclarative @DataBoundConstructor constructor( private var dsn: String = "" private var member: String = "" + private var failOnNotExist: Boolean = false @DataBoundSetter fun setDsn(dsn: String) { this.dsn = dsn } @@ -74,6 +74,9 @@ class DeleteDatasetDeclarative @DataBoundConstructor constructor( @DataBoundSetter fun setMember(member: String) { this.member = member } + @DataBoundSetter + fun setFailOnNotExist(failOnNotExist: Boolean) { this.failOnNotExist = failOnNotExist } + override val exceptionMessage: String = zMessages.zdevops_deleting_ds_fail() override fun perform( @@ -84,7 +87,7 @@ class DeleteDatasetDeclarative @DataBoundConstructor constructor( listener: TaskListener, zosConnection: ZOSConnection ) { - DeleteOperation.deleteDatasetOrMember(dsn, member, zosConnection, listener) + deleteDatasetOrMember(dsn, member, zosConnection, listener, failOnNotExist) } @Symbol("deleteDataset") diff --git a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/DeleteDatasetsByMaskDeclarative.kt b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/DeleteDatasetsByMaskDeclarative.kt similarity index 86% rename from src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/DeleteDatasetsByMaskDeclarative.kt rename to src/main/kotlin/org/zowe/zdevops/declarative/jobs/DeleteDatasetsByMaskDeclarative.kt index 3777030..e91566e 100644 --- a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/DeleteDatasetsByMaskDeclarative.kt +++ b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/DeleteDatasetsByMaskDeclarative.kt @@ -8,18 +8,17 @@ * Copyright IBA Group 2022 */ -package io.jenkins.plugins.zdevops.declarative.jobs +package org.zowe.zdevops.declarative.jobs -import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection -import io.jenkins.plugins.zdevops.declarative.AbstractZosmfAction -import io.jenkins.plugins.zdevops.logic.DeleteOperation import hudson.* -import hudson.FilePath import hudson.model.Run import hudson.model.TaskListener import org.jenkinsci.Symbol import org.kohsuke.stapler.DataBoundConstructor import org.kohsuke.stapler.DataBoundSetter +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.declarative.AbstractZosmfAction +import org.zowe.zdevops.logic.deleteDatasetsByMask /** * This class contains bulk delete mainframe datasets operation description @@ -57,10 +56,14 @@ class DeleteDatasetsByMaskDeclarative @DataBoundConstructor constructor( ) : AbstractZosmfAction() { private var mask: String = "" + private var failOnNotExist: Boolean = false @DataBoundSetter fun setMask(mask: String) { this.mask = mask } + @DataBoundSetter + fun setFailOnNotExist(failOnNotExist: Boolean) { this.failOnNotExist = failOnNotExist } + override val exceptionMessage: String = zMessages.zdevops_deleting_ds_fail() override fun perform( @@ -71,7 +74,7 @@ class DeleteDatasetsByMaskDeclarative @DataBoundConstructor constructor( listener: TaskListener, zosConnection: ZOSConnection ) { - DeleteOperation.deleteDatasetsByMask(mask, zosConnection,listener) + deleteDatasetsByMask(mask, zosConnection,listener, failOnNotExist) } diff --git a/src/main/kotlin/org/zowe/zdevops/declarative/jobs/DownloadFileDeclarative.kt b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/DownloadFileDeclarative.kt new file mode 100644 index 0000000..fbc61f7 --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/DownloadFileDeclarative.kt @@ -0,0 +1,57 @@ +/* + * 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 + */ + +package org.zowe.zdevops.declarative.jobs + +import hudson.* +import hudson.model.Run +import hudson.model.TaskListener +import org.jenkinsci.Symbol +import org.kohsuke.stapler.DataBoundConstructor +import org.kohsuke.stapler.DataBoundSetter +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.declarative.AbstractZosmfAction +import org.zowe.zdevops.logic.downloadDSOrDSMemberByType + +class DownloadFileDeclarative @DataBoundConstructor constructor(val dsn: String) : + AbstractZosmfAction() { + + private var vol: String? = null + private var returnEtag: Boolean? = null + + @DataBoundSetter + fun setVol(vol: String) { this.vol = vol } + + @DataBoundSetter + fun setReturnEtag(returnEtag: Boolean) { this.returnEtag = returnEtag } + + fun getVol(): String? = this.vol + + fun getReturnEtag(): Boolean? = this.returnEtag + + override val exceptionMessage: String = zMessages.zdevops_declarative_DSN_downloaded_fail(dsn) + + override fun perform( + run: Run<*, *>, + workspace: FilePath, + env: EnvVars, + launcher: Launcher, + listener: TaskListener, + zosConnection: ZOSConnection + ) { + val workspacePath = FilePath(null, workspace.remote.replace(workspace.name,"")) + downloadDSOrDSMemberByType(dsn, vol, returnEtag, listener, zosConnection, workspacePath) + } + + + @Symbol("downloadDS") + @Extension + class DescriptorImpl : Companion.DefaultBuildDescriptor("Download File Declarative") +} diff --git a/src/main/kotlin/org/zowe/zdevops/declarative/jobs/PerformTsoCommandDeclarative.kt b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/PerformTsoCommandDeclarative.kt new file mode 100644 index 0000000..060d546 --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/PerformTsoCommandDeclarative.kt @@ -0,0 +1,74 @@ +/* + * 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 + */ + +package org.zowe.zdevops.declarative.jobs + +import hudson.* +import hudson.model.Run +import hudson.model.TaskListener +import org.jenkinsci.Symbol +import org.kohsuke.stapler.DataBoundConstructor +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.declarative.AbstractZosmfAction +import org.zowe.zdevops.logic.performTsoCommand + +/** + * A Jenkins Pipeline step for performing a TSO (Time Sharing Option) command on a z/OS system + * using the Declarative Pipeline syntax. + * + * This step allows you to execute TSO commands on a z/OS system and provides integration with + * Jenkins Pipelines for mainframe automation. + */ +class PerformTsoCommandDeclarative +/** + * Data-bound constructor for the {@code PerformTsoCommandDeclarative} step. + * + * @param acct The z/OS account under which to run the TSO command. + * @param command The TSO command to be executed. + */ +@DataBoundConstructor +constructor( + val acct: String, + val command: String, +) : AbstractZosmfAction() { + + override val exceptionMessage: String = zMessages.zdevops_TSO_command_fail() + + /** + * Performs the TSO command execution step within a Jenkins Pipeline. + * + * @param run The current Jenkins build run. + * @param workspace The workspace where the build is executed. + * @param env The environment variables for the build. + * @param launcher The build launcher. + * @param listener The build listener. + * @param zosConnection The z/OS connection to execute the TSO command. + */ + override fun perform( + run: Run<*, *>, + workspace: FilePath, + env: EnvVars, + launcher: Launcher, + listener: TaskListener, + zosConnection: ZOSConnection + ) { + performTsoCommand(zosConnection, listener, acct, command) + } + + /** + * Descriptor for the {@code PerformTsoCommandDeclarative} step. + * + * This descriptor provides information about the step and makes it available for use + * within Jenkins Pipelines. + */ + @Symbol("performTsoCommand") + @Extension + class DescriptorImpl : Companion.DefaultBuildDescriptor("Perform TSO command Declarative") +} \ No newline at end of file diff --git a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/SubmitJobStepDeclarative.kt b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/SubmitJobStepDeclarative.kt similarity index 63% rename from src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/SubmitJobStepDeclarative.kt rename to src/main/kotlin/org/zowe/zdevops/declarative/jobs/SubmitJobStepDeclarative.kt index 4676ed5..0dcad5e 100644 --- a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/SubmitJobStepDeclarative.kt +++ b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/SubmitJobStepDeclarative.kt @@ -8,18 +8,18 @@ * Copyright IBA Group 2022 */ -package io.jenkins.plugins.zdevops.declarative.jobs +package org.zowe.zdevops.declarative.jobs -import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection -import org.zowe.kotlinsdk.zowe.client.sdk.zosjobs.SubmitJobs -import io.jenkins.plugins.zdevops.declarative.AbstractZosmfAction import hudson.* import hudson.model.Run import hudson.model.TaskListener import org.jenkinsci.Symbol import org.kohsuke.stapler.DataBoundConstructor +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.declarative.AbstractZosmfAction +import org.zowe.zdevops.logic.submitJob -typealias zMessages = io.jenkins.plugins.zdevops.Messages +typealias zMessages = org.zowe.zdevops.Messages class SubmitJobStepDeclarative @DataBoundConstructor constructor(private val fileToSubmit: String) : AbstractZosmfAction() { @@ -34,10 +34,8 @@ class SubmitJobStepDeclarative @DataBoundConstructor constructor(private val fil listener: TaskListener, zosConnection: ZOSConnection ) { - listener.logger.println(zMessages.zdevops_declarative_ZOSJobs_submitting(fileToSubmit, zosConnection.host, zosConnection.zosmfPort)) - val submitJobRsp = SubmitJobs(zosConnection).submitJob(fileToSubmit) - listener.logger.println(zMessages.zdevops_declarative_ZOSJobs_submitted_success(submitJobRsp.jobid, submitJobRsp.jobname, submitJobRsp.owner)) - } + submitJob(fileToSubmit, zosConnection, listener) + } @Symbol("submitJob") diff --git a/src/main/kotlin/org/zowe/zdevops/declarative/jobs/SubmitJobSyncStepDeclarative.kt b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/SubmitJobSyncStepDeclarative.kt new file mode 100644 index 0000000..9fc9df4 --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/SubmitJobSyncStepDeclarative.kt @@ -0,0 +1,45 @@ +/* + * 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 + */ + +package org.zowe.zdevops.declarative.jobs + +import hudson.* +import hudson.model.Run +import hudson.model.TaskListener +import org.jenkinsci.Symbol +import org.kohsuke.stapler.DataBoundConstructor +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.declarative.AbstractZosmfAction +import org.zowe.zdevops.logic.submitJobSync + +class SubmitJobSyncStepDeclarative @DataBoundConstructor constructor(private val fileToSubmit: String): + AbstractZosmfAction() { + + override val exceptionMessage: String = zMessages.zdevops_declarative_ZOSJobs_submitted_fail(fileToSubmit) + + override fun perform( + run: Run<*, *>, + workspace: FilePath, + env: EnvVars, + launcher: Launcher, + listener: TaskListener, + zosConnection: ZOSConnection + ) { + val workspacePath = FilePath(null, workspace.remote.replace(workspace.name,"")) + val linkBuilder: (String?, String, String) -> String = { buildUrl, jobName, jobId -> + "$buildUrl/execution/node/3/ws/${jobName}.${jobId}/*view*/" + } + submitJobSync(fileToSubmit, zosConnection, listener, workspacePath, env["BUILD_URL"], linkBuilder) + } + + @Symbol("submitJobSync") + @Extension + class DescriptorImpl : Companion.DefaultBuildDescriptor("Submit Job Synchronously Declarative") +} diff --git a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/WriteFIleToMemberDeclarative.kt b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/WriteFIleToMemberDeclarative.kt similarity index 72% rename from src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/WriteFIleToMemberDeclarative.kt rename to src/main/kotlin/org/zowe/zdevops/declarative/jobs/WriteFIleToMemberDeclarative.kt index 4cba4b5..36c3b87 100644 --- a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/WriteFIleToMemberDeclarative.kt +++ b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/WriteFIleToMemberDeclarative.kt @@ -8,11 +8,11 @@ * Copyright IBA Group 2022 */ -package io.jenkins.plugins.zdevops.declarative.jobs +package org.zowe.zdevops.declarative.jobs import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.ZosDsn -import io.jenkins.plugins.zdevops.declarative.AbstractZosmfAction +import org.zowe.zdevops.declarative.AbstractZosmfAction import hudson.* import hudson.FilePath import hudson.model.Run @@ -47,22 +47,17 @@ class WriteFIleToMemberDeclarative @DataBoundConstructor constructor(private val } val targetDS = ZosDsn(zosConnection).getDatasetInfo(dsn) - if (targetDS.recordLength == null) { - throw AbortException(zMessages.zdevops_declarative_writing_DS_no_info(dsn)) - } - var ineligibleStrings = 0 - textFile.readLines().forEach { - if (it.length > targetDS.recordLength!!) { - ineligibleStrings++ - } - } + val targetDSLRECL = targetDS.recordLength ?: throw AbortException(zMessages.zdevops_declarative_writing_DS_no_info(dsn)) + val ineligibleStrings = textFile + .readLines() + .map { it.length } + .fold(0) { result, currStrLength -> if (currStrLength > targetDSLRECL) result + 1 else result } if (ineligibleStrings > 0) { - throw AbortException(zMessages.zdevops_declarative_writing_DS_ineligible_strings(ineligibleStrings,dsn)) - } else { - val textString = textFile.readText().replace("\r","") - val writeToDS = ZosDsn(zosConnection).writeDsn(dsn, member, textString.toByteArray()) - listener.logger.println(zMessages.zdevops_declarative_writing_DS_success(dsn)) + throw AbortException(zMessages.zdevops_declarative_writing_DS_ineligible_strings(ineligibleStrings, dsn)) } + val textString = textFile.readText().replace("\r","") + ZosDsn(zosConnection).writeDsn(dsn, member, textString.toByteArray()) + listener.logger.println(zMessages.zdevops_declarative_writing_DS_success(dsn)) } diff --git a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/WriteFileToDatasetDeclarative.kt b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/WriteFileToDatasetDeclarative.kt similarity index 92% rename from src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/WriteFileToDatasetDeclarative.kt rename to src/main/kotlin/org/zowe/zdevops/declarative/jobs/WriteFileToDatasetDeclarative.kt index 94a2e1a..ef4ffe5 100644 --- a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/WriteFileToDatasetDeclarative.kt +++ b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/WriteFileToDatasetDeclarative.kt @@ -8,11 +8,11 @@ * Copyright IBA Group 2022 */ -package io.jenkins.plugins.zdevops.declarative.jobs +package org.zowe.zdevops.declarative.jobs import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.ZosDsn -import io.jenkins.plugins.zdevops.declarative.AbstractZosmfAction +import org.zowe.zdevops.declarative.AbstractZosmfAction import hudson.* import hudson.FilePath import hudson.model.Run @@ -59,7 +59,7 @@ class WriteFileToDatasetDeclarative @DataBoundConstructor constructor(private va throw AbortException(zMessages.zdevops_declarative_writing_DS_ineligible_strings(ineligibleStrings,dsn)) } else { val textString = textFile.readText().replace("\r","") - val writeToDS = ZosDsn(zosConnection).writeDsn(dsn, textString.toByteArray()) + ZosDsn(zosConnection).writeDsn(dsn, textString.toByteArray()) listener.logger.println(zMessages.zdevops_declarative_writing_DS_success(dsn)) } } diff --git a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/WriteFileToFileDeclarative.kt b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/WriteFileToFileDeclarative.kt similarity index 79% rename from src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/WriteFileToFileDeclarative.kt rename to src/main/kotlin/org/zowe/zdevops/declarative/jobs/WriteFileToFileDeclarative.kt index d2e8103..8e57f4e 100644 --- a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/WriteFileToFileDeclarative.kt +++ b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/WriteFileToFileDeclarative.kt @@ -8,18 +8,17 @@ * Copyright IBA Group 2022 */ -package io.jenkins.plugins.zdevops.declarative.jobs +package org.zowe.zdevops.declarative.jobs -import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection -import org.zowe.kotlinsdk.zowe.client.sdk.zosuss.ZosUssFile -import io.jenkins.plugins.zdevops.declarative.AbstractZosmfAction import hudson.* -import hudson.FilePath import hudson.model.Run import hudson.model.TaskListener import org.jenkinsci.Symbol import org.kohsuke.stapler.DataBoundConstructor import org.kohsuke.stapler.DataBoundSetter +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.declarative.AbstractZosmfAction +import org.zowe.zdevops.logic.writeToFile import java.io.File import java.nio.file.Paths @@ -32,6 +31,8 @@ class WriteFileToFileDeclarative @DataBoundConstructor constructor(private val d @DataBoundSetter fun setBinary(binary: Boolean) { this.binary = binary } + fun getBinary(): Boolean? { return binary } + override val exceptionMessage: String = zMessages.zdevops_declarative_writing_file_fail(destFile) override fun perform( @@ -52,12 +53,7 @@ class WriteFileToFileDeclarative @DataBoundConstructor constructor(private val d } val text = textFile.readBytes() - if (binary == true) { - ZosUssFile(zosConnection).writeToFileBin(destFile, text) - } else { - ZosUssFile(zosConnection).writeToFile(destFile, text) - } - listener.logger.println(zMessages.zdevops_declarative_writing_file_success(destFile)) + writeToFile(listener, zosConnection, destFile, text, binary) } diff --git a/src/main/kotlin/org/zowe/zdevops/declarative/jobs/WriteToDatasetDeclarative.kt b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/WriteToDatasetDeclarative.kt new file mode 100644 index 0000000..4b31e42 --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/WriteToDatasetDeclarative.kt @@ -0,0 +1,43 @@ +/* + * 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 + */ + +package org.zowe.zdevops.declarative.jobs + +import hudson.* +import hudson.model.Run +import hudson.model.TaskListener +import org.jenkinsci.Symbol +import org.kohsuke.stapler.DataBoundConstructor +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.declarative.AbstractZosmfAction +import org.zowe.zdevops.logic.writeToDataset + +class WriteToDatasetDeclarative @DataBoundConstructor constructor(private val dsn: String, + private val text: String) : + AbstractZosmfAction() { + + override val exceptionMessage: String = zMessages.zdevops_declarative_writing_DS_fail(dsn) + + override fun perform( + run: Run<*, *>, + workspace: FilePath, + env: EnvVars, + launcher: Launcher, + listener: TaskListener, + zosConnection: ZOSConnection + ) { + writeToDataset(listener, zosConnection, dsn, text) + } + + + @Symbol("writeToDS") + @Extension + class DescriptorImpl : Companion.DefaultBuildDescriptor("Write to Dataset Declarative") +} diff --git a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/WriteToFileDeclarative.kt b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/WriteToFileDeclarative.kt similarity index 62% rename from src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/WriteToFileDeclarative.kt rename to src/main/kotlin/org/zowe/zdevops/declarative/jobs/WriteToFileDeclarative.kt index 79f9d7b..e9b32f8 100644 --- a/src/main/kotlin/io/jenkins/plugins/zdevops/declarative/jobs/WriteToFileDeclarative.kt +++ b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/WriteToFileDeclarative.kt @@ -8,18 +8,18 @@ * Copyright IBA Group 2022 */ -package io.jenkins.plugins.zdevops.declarative.jobs +package org.zowe.zdevops.declarative.jobs -import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection -import org.zowe.kotlinsdk.zowe.client.sdk.zosuss.ZosUssFile -import io.jenkins.plugins.zdevops.declarative.AbstractZosmfAction import hudson.* -import hudson.FilePath import hudson.model.Run import hudson.model.TaskListener import org.jenkinsci.Symbol import org.kohsuke.stapler.DataBoundConstructor import org.kohsuke.stapler.DataBoundSetter +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.declarative.AbstractZosmfAction +import org.zowe.zdevops.logic.writeToFile + class WriteToFileDeclarative @DataBoundConstructor constructor(private val destFile: String, private val text: String) : @@ -40,17 +40,9 @@ class WriteToFileDeclarative @DataBoundConstructor constructor(private val destF listener: TaskListener, zosConnection: ZOSConnection ) { - if (text != "") { - listener.logger.println(zMessages.zdevops_declarative_writing_file_from_input(destFile, zosConnection.host, zosConnection.zosmfPort)) - if (binary == true) { - ZosUssFile(zosConnection).writeToFileBin(destFile, text.toByteArray()) - } else { - ZosUssFile(zosConnection).writeToFile(destFile, text.toByteArray()) - } - listener.logger.println(zMessages.zdevops_declarative_writing_file_success(destFile)) - } else { - listener.logger.println(zMessages.zdevops_declarative_writing_skip()) - } + listener.logger.println(zMessages.zdevops_declarative_writing_file_from_input(destFile, zosConnection.host, zosConnection.zosmfPort)) + val textBytes = text.toByteArray() + writeToFile(listener, zosConnection, destFile, textBytes, binary) } diff --git a/src/main/kotlin/org/zowe/zdevops/declarative/jobs/WriteToMemberDeclarative.kt b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/WriteToMemberDeclarative.kt new file mode 100644 index 0000000..fc2e833 --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/WriteToMemberDeclarative.kt @@ -0,0 +1,44 @@ +/* + * 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 + */ + +package org.zowe.zdevops.declarative.jobs + +import hudson.* +import hudson.model.Run +import hudson.model.TaskListener +import org.jenkinsci.Symbol +import org.kohsuke.stapler.DataBoundConstructor +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.declarative.AbstractZosmfAction +import org.zowe.zdevops.logic.writeToMember + +class WriteToMemberDeclarative @DataBoundConstructor constructor(private val dsn: String, + private val member: String, + private val text: String) : + AbstractZosmfAction() { + + override val exceptionMessage: String = zMessages.zdevops_declarative_writing_DS_fail(dsn) + + override fun perform( + run: Run<*, *>, + workspace: FilePath, + env: EnvVars, + launcher: Launcher, + listener: TaskListener, + zosConnection: ZOSConnection + ) { + writeToMember(listener, zosConnection, dsn, member, text) + } + + + @Symbol("writeToMember") + @Extension + class DescriptorImpl : Companion.DefaultBuildDescriptor("Write to Dataset Member Declarative") +} diff --git a/src/main/kotlin/org/zowe/zdevops/logic/AllocateOperation.kt b/src/main/kotlin/org/zowe/zdevops/logic/AllocateOperation.kt new file mode 100644 index 0000000..ca24225 --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/logic/AllocateOperation.kt @@ -0,0 +1,92 @@ +/* + * 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 + */ + +package org.zowe.zdevops.logic + +import hudson.model.TaskListener +import org.zowe.kotlinsdk.* +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.ZosDsn +import org.zowe.zdevops.Messages + + +/** + * Allocates a dataset with the specified parameters + * @param listener The TaskListener object for logging messages + * @param zosConnection The ZOSConnection object representing the connection to the z/OS system + * @param dsn The name of the dataset to be allocated + * @param volser The volume serial number where the dataset should be allocated + * @param unit The allocation unit for the dataset + * @param dsOrg The dataset organization + * @param alcUnit The allocation unit for the dataset allocation + * @param primary The primary allocation size in cylinders or tracks + * @param secondary The secondary allocation size in cylinders or tracks + * @param dirBlk The directory block size + * @param recFm The record format + * @param blkSize The block size in bytes + * @param lrecl The record length in bytes + * @param storClass The storage class + * @param mgntClass The management class + * @param dataClass The data class + * @param avgBlk The average block size + * @param dsnType The dataset name type + * @param dsModel The dataset model + */ +fun allocateDataset(listener: TaskListener, + zosConnection: ZOSConnection, + dsn: String, + volser: String?, + unit: String?, + dsOrg: DatasetOrganization, + alcUnit: AllocationUnit?, + primary: Int, + secondary: Int, + dirBlk: Int?, + recFm: RecordFormat, + blkSize: Int?, + lrecl: Int?, + storClass: String?, + mgntClass: String?, + dataClass: String?, + avgBlk: Int?, + dsnType: DsnameType?, + dsModel: String?, + failOnExist: Boolean, +) { + listener.logger.println(Messages.zdevops_declarative_DSN_allocating(dsn, zosConnection.host, zosConnection.zosmfPort)) + val alcParms = CreateDataset( + volser, + unit, + dsOrg, + alcUnit, + primary, + secondary, + dirBlk, + recFm, + blkSize, + lrecl, + storClass, + mgntClass, + dataClass, + avgBlk, + dsnType, + dsModel + ) + try { + ZosDsn(zosConnection).createDsn(dsn, alcParms) + listener.logger.println(Messages.zdevops_declarative_DSN_allocated_success(dsn)) + } catch (allocateDsEx: Exception) { + listener.logger.println("Dataset allocation failed. Reason: $allocateDsEx") + if(failOnExist) { + throw allocateDsEx + } + listener.logger.println("The `failOnExist` option is set to false. Continuing with execution.") + } +} diff --git a/src/main/kotlin/org/zowe/zdevops/logic/DeleteOperation.kt b/src/main/kotlin/org/zowe/zdevops/logic/DeleteOperation.kt new file mode 100644 index 0000000..6048c5f --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/logic/DeleteOperation.kt @@ -0,0 +1,102 @@ +/* + * 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 + */ + +package org.zowe.zdevops.logic + +import hudson.AbortException +import hudson.model.TaskListener +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.ZosDsn +import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.ZosDsnList +import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.input.ListParams +import org.zowe.zdevops.declarative.jobs.zMessages +import org.zowe.zdevops.utils.runMFTryCatchWrappedQuery + +private val successMessage: String = zMessages.zdevops_deleting_ds_success() + +/** + * Deletes datasets matching the provided mask + * + * @param mask The mask used to filter datasets. + * @param zosConnection The z/OS connection to be used for dataset deletion. + * @param listener The task listener to log information and handle exceptions. + * @throws AbortException If the mask is empty or no matching datasets are found. + */ +fun deleteDatasetsByMask(mask: String, zosConnection: ZOSConnection, listener: TaskListener, failOnNotExist: Boolean) { + if (mask.isEmpty()) { + throw AbortException(zMessages.zdevops_deleting_datasets_by_mask_but_mask_is_empty()) + } + listener.logger.println(zMessages.zdevops_deleting_ds_by_mask(mask)) + try { + val dsnList = ZosDsnList(zosConnection).listDsn(mask, ListParams()) + if (dsnList.items.isEmpty()) { + throw AbortException(zMessages.zdevops_deleting_ds_fail_no_matching_mask()) + } + dsnList.items.forEach { + runMFTryCatchWrappedQuery(listener) { + listener.logger.println(zMessages.zdevops_deleting_ds(it.name, zosConnection.host, zosConnection.zosmfPort)) + ZosDsn(zosConnection).deleteDsn(it.name) + } + } + listener.logger.println(successMessage) + } catch (doesNotExistEx: Exception) { + if(failOnNotExist) { + throw doesNotExistEx + } + listener.logger.println("Reason: $doesNotExistEx") + // TODO I wanna have the dataset name here - it's inside exception message? + listener.logger.println("Dataset deletion failed, but the `failOnNotExist` option is set to false. Continuing with execution.") + } +} + +/** + * Deletes a dataset or member + * + * @param dsn The dataset name. + * @param member The member name (optional). + * @param zosConnection The z/OS connection to be used for dataset deletion. + * @param listener The task listener to log information and handle exceptions. + * @throws AbortException If the dataset name is empty or the member name is invalid. + */ +fun deleteDatasetOrMember(dsn: String, member: String?, zosConnection: ZOSConnection, listener: TaskListener, failOnNotExist: Boolean) { + if (dsn.isEmpty()) { + throw AbortException(zMessages.zdevops_deleting_ds_fail_dsn_param_empty()) + } + val logMessage = if (!member.isNullOrEmpty()) zMessages.zdevops_deleting_ds_member(member, dsn, zosConnection.host, zosConnection.zosmfPort) + else zMessages.zdevops_deleting_ds(dsn, zosConnection.host, zosConnection.zosmfPort) + listener.logger.println(logMessage) + try { + if (!member.isNullOrEmpty()) { + isMemberNameValid(member) + ZosDsn(zosConnection).deleteDsn(dsn, member) + } else { + ZosDsn(zosConnection).deleteDsn(dsn) + } + listener.logger.println(successMessage) + } catch (doesNotExistEx: Exception) { + if(failOnNotExist) { + throw doesNotExistEx + } + listener.logger.println("Reason: $doesNotExistEx") + listener.logger.println("Dataset deletion failed, but the `failOnNotExist` option is set to false. Continuing with execution.") + } +} + +/** + * Validates a member name. + * + * @param member The member name to validate. + * @throws Exception If the member name is invalid. + */ +private fun isMemberNameValid(member: String) { + if (member.length > 8 || member.isEmpty()) + throw Exception(zMessages.zdevops_member_name_invalid()) +} + diff --git a/src/main/kotlin/org/zowe/zdevops/logic/DownloadOperation.kt b/src/main/kotlin/org/zowe/zdevops/logic/DownloadOperation.kt new file mode 100644 index 0000000..69edb90 --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/logic/DownloadOperation.kt @@ -0,0 +1,99 @@ +/* + * 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 + */ + +package org.zowe.zdevops.logic + +import hudson.AbortException +import hudson.FilePath +import hudson.model.TaskListener +import org.apache.commons.io.IOUtils +import org.zowe.kotlinsdk.DatasetOrganization +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.ZosDsnDownload +import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.ZosDsnList +import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.input.DownloadParams +import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.input.ListParams +import org.zowe.zdevops.Messages +import java.io.File +import java.io.InputStream +import java.io.StringWriter + + + +/** + * Downloads one PS dataset or a PDS member from the z/OS system. + * + * @param dsn The name of the dataset to download. + * @param vol The volume on which the dataset resides. + * @param returnEtag Specifies whether to return the ETag value for the dataset. If it is not present, the default is to only send an Etag for data sets smaller than a system determined length, which is at least 8 MB + * @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. + */ +fun downloadDS( + dsn: String, + vol: String?, + returnEtag: Boolean?, + zosConnection: ZOSConnection, + workspace: FilePath, + listener: TaskListener +) { + var downloadedDSN: InputStream? + try { + downloadedDSN = ZosDsnDownload(zosConnection).downloadDsn(dsn, DownloadParams(dsn, returnEtag, vol)) + } catch (e:Exception) { + throw AbortException("Can't download $dsn ${ if(vol.isNullOrBlank()) "on volume $vol" else ""} due to ${e.message}") + } + val writer = StringWriter() + 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)) +} + +/** + * Downloads a dataset or dataset members from the z/OS system. + * + * @param dsn The name of the dataset or dataset member to download. + * @param vol The volume on which the dataset resides. + * @param returnEtag Specifies whether to return the ETag value for the dataset. + * @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. + */ +fun downloadDSOrDSMemberByType( + dsn: String, + vol: String?, + returnEtag: Boolean?, + listener: TaskListener, + zosConnection: ZOSConnection, + workspace: FilePath +) { + 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) + } 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.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) + } + } + else -> listener.logger.println(Messages.zdevops_declarative_DSN_downloading_invalid_dsorg()) + } + } +} diff --git a/src/main/kotlin/org/zowe/zdevops/logic/PerformTsoCommandOperation.kt b/src/main/kotlin/org/zowe/zdevops/logic/PerformTsoCommandOperation.kt new file mode 100644 index 0000000..c6aeb07 --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/logic/PerformTsoCommandOperation.kt @@ -0,0 +1,48 @@ +/* + * 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 + */ + +package org.zowe.zdevops.logic + +import hudson.AbortException +import hudson.model.TaskListener +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.kotlinsdk.zowe.client.sdk.zostso.IssueTso +import org.zowe.kotlinsdk.zowe.client.sdk.zostso.input.StartTsoParams +import org.zowe.zdevops.Messages + +/** + * Executes a TSO (Time Sharing Option) command on a z/OS system using the provided z/OS connection. + * + * This function allows you to send a TSO command to a z/OS system, and capture the response + * + * @param zosConnection The z/OS connection through which the TSO command will be executed. + * @param listener The Jenkins build listener for logging and monitoring the execution. + * @param acct The z/OS account number. + * @param command The TSO command to be executed. + * + * @throws AbortException if the TSO command execution fails, with the error message indicating + * the reason for the failure. + */ +fun performTsoCommand( + zosConnection: ZOSConnection, + listener: TaskListener, + acct: String, + command: String, + ) { + listener.logger.println(Messages.zdevops_issue_TSO_command(command)) + try { + val tsoCommandResponse = IssueTso(zosConnection).issueTsoCommand(acct, command, StartTsoParams(), failOnPrompt = true) + listener.logger.println(tsoCommandResponse.commandResponses) + } catch (ex: Exception) { + listener.logger.println(Messages.zdevops_TSO_command_fail()) + throw ex + } + listener.logger.println(Messages.zdevops_TSO_command_success()) +} \ No newline at end of file diff --git a/src/main/kotlin/org/zowe/zdevops/logic/SubmitJobOperation.kt b/src/main/kotlin/org/zowe/zdevops/logic/SubmitJobOperation.kt new file mode 100644 index 0000000..420bec8 --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/logic/SubmitJobOperation.kt @@ -0,0 +1,107 @@ +/* + * 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 + */ + +package org.zowe.zdevops.logic + +import hudson.AbortException +import hudson.FilePath +import hudson.console.HyperlinkNote +import hudson.model.TaskListener +import org.zowe.kotlinsdk.Job +import org.zowe.kotlinsdk.SpoolFile +import org.zowe.kotlinsdk.SubmitJobRequest +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.kotlinsdk.zowe.client.sdk.zosjobs.GetJobs +import org.zowe.kotlinsdk.zowe.client.sdk.zosjobs.MonitorJobs +import org.zowe.kotlinsdk.zowe.client.sdk.zosjobs.SubmitJobs +import org.zowe.zdevops.Messages +import org.zowe.zdevops.utils.extractSubmitJobMessage +import org.zowe.zdevops.utils.runMFTryCatchWrappedQuery +import java.io.File + +/** + * Submits a z/OS job + * + * @param fileToSubmit The path to the job to submit. It may reside either in dataset or in USS file. + * @param zosConnection The z/OS connection to use for job submission. + * @param listener The task listener to log information and handle exceptions. + * @return The response containing information about the submitted job. + * @throws AbortException If job submission fails. + */ +fun submitJob( + fileToSubmit: String, + zosConnection: ZOSConnection, + listener: TaskListener, +): SubmitJobRequest? { + var submitJobRsp: SubmitJobRequest? = null + try { + listener.logger.println(Messages.zdevops_declarative_ZOSJobs_submitting(fileToSubmit, zosConnection.host, zosConnection.zosmfPort)) + submitJobRsp = SubmitJobs(zosConnection).submitJob(fileToSubmit) + listener.logger.println(Messages.zdevops_declarative_ZOSJobs_submitted_success(submitJobRsp.jobid, submitJobRsp.jobname, submitJobRsp.owner)) + } catch (e: Exception) { + listener.logger.println(e.message?.let { extractSubmitJobMessage(it) }) + throw AbortException(Messages.zdevops_classic_ZOSJobs_submitted_fail(fileToSubmit)) + } + return submitJobRsp +} + +/** + * Submits a z/OS job synchronously, monitors its execution, retrieves the job log, and returns the job's return code. + * + * @param fileToSubmit The path to the job to submit. It may reside either in dataset or in USS file. + * @param zosConnection The z/OS connection to use for job submission and monitoring. + * @param listener The task listener to log information and handle exceptions. + * @param workspacePath The workspace path to store the job log. + * @param buildUrl The URL of the build (optional). + * @param linkBuilder A function to build hyperlinks. + * @return The return code of the executed job. + * @throws IllegalStateException If necessary information is missing from the system response. + */ +fun submitJobSync( + fileToSubmit: String, + zosConnection: ZOSConnection, + listener: TaskListener, + workspacePath: FilePath, + buildUrl: String?, + linkBuilder: (String?, String, String) -> String +): String? { + val submitJobRsp = submitJob(fileToSubmit, zosConnection, listener) + listener.logger.println(Messages.zdevops_declarative_ZOSJobs_submitted_waiting()) + + val jobId = submitJobRsp?.jobid ?: throw IllegalStateException("System response doesn't contain JOB ID.") + val jobName = submitJobRsp.jobname ?: throw IllegalStateException("System response doesn't contain JOB name.") + lateinit var finalResult: Job + runMFTryCatchWrappedQuery(listener) { + finalResult = MonitorJobs(zosConnection).waitForJobOutputStatus(jobName, jobId) + } + listener.logger.println(Messages.zdevops_declarative_ZOSJobs_submitted_executed(finalResult.returnedCode)) + + listener.logger.println(Messages.zdevops_declarative_ZOSJobs_getting_log()) + lateinit var spoolFiles: List + runMFTryCatchWrappedQuery(listener) { + spoolFiles = GetJobs(zosConnection).getSpoolFilesForJob(finalResult) + } + if (spoolFiles.isNotEmpty()) { + val fullLog = spoolFiles.joinToString { GetJobs(zosConnection).getSpoolContent(it) } + val logPath = "$workspacePath/${finalResult.jobName}.${finalResult.jobId}" + val file = File(logPath) + file.writeText(fullLog) + listener.logger.println(Messages.zdevops_declarative_ZOSJobs_got_log( + HyperlinkNote.encodeTo( + linkBuilder(buildUrl, finalResult.jobName, finalResult.jobId), + "${finalResult.jobName}.${finalResult.jobId}" + ) + )) + } else { + listener.logger.println(Messages.zdevops_no_spool_files(submitJobRsp.jobid)) + } + + return finalResult?.returnedCode +} \ No newline at end of file diff --git a/src/main/kotlin/org/zowe/zdevops/logic/WriteOperation.kt b/src/main/kotlin/org/zowe/zdevops/logic/WriteOperation.kt new file mode 100644 index 0000000..128494a --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/logic/WriteOperation.kt @@ -0,0 +1,123 @@ +/* + * 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 + */ + +package org.zowe.zdevops.logic + +import hudson.AbortException +import hudson.model.TaskListener +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.ZosDsn +import org.zowe.kotlinsdk.zowe.client.sdk.zosuss.ZosUssFile +import org.zowe.zdevops.Messages +import org.zowe.zdevops.utils.runMFTryCatchWrappedQuery + + + +/** + * Validates the text to be written to a dataset + * + * @param listener The listener for logging messages + * @param zosConnection The ZOSConnection for interacting with z/OS + * @param dsn The name of the dataset + * @param text The text content to be written + * @throws AbortException if the text is empty or contains ineligible strings. + */ +private fun validateTextForDataset( + listener: TaskListener, + zosConnection: ZOSConnection, + dsn: String, + text: String, + ) { + if(text == "") { + listener.logger.println(Messages.zdevops_declarative_writing_skip()) + return + } + + val stringList = text.split('\n') + val targetDS = ZosDsn(zosConnection).getDatasetInfo(dsn) + if (targetDS.recordLength == null) { + throw AbortException(Messages.zdevops_declarative_writing_DS_no_info(dsn)) + } + var ineligibleStrings = 0 + stringList.forEach { + if (it.length > targetDS.recordLength!!) { + ineligibleStrings++ + } + } + if (ineligibleStrings > 0) { + throw AbortException(Messages.zdevops_declarative_writing_DS_ineligible_strings(ineligibleStrings,dsn)) + } +} + +/** + * Writes the text content to a dataset + * + * @param listener The listener for logging messages + * @param zosConnection The ZOSConnection for interacting with z/OS + * @param dsn The name of the dataset + * @param text The text content to be written + * @throws AbortException if the text is not valid for the dataset or an error occurs during the write operation + */ +fun writeToDataset(listener: TaskListener, + zosConnection: ZOSConnection, + dsn: String, + text: String, + ) { + validateTextForDataset(listener, zosConnection, dsn, text) + val textByteArray = text.replace("\r","").toByteArray() + runMFTryCatchWrappedQuery(listener) { + ZosDsn(zosConnection).writeDsn(dsn, textByteArray) + } + listener.logger.println(Messages.zdevops_declarative_writing_DS_success(dsn)) +} + + +/** + * Writes the text content to a member + * + * @param listener The listener for logging messages + * @param zosConnection The ZOSConnection for interacting with z/OS + * @param dsn The name of the dataset + * @param member The name of the member + * @param text The text content to be written + * @throws AbortException if the text is not valid for the dataset or an error occurs during the write operation + */ +fun writeToMember(listener: TaskListener, + zosConnection: ZOSConnection, + dsn: String, + member: String, + text: String,) { + validateTextForDataset(listener, zosConnection, dsn, text) + val textByteArray = text.replace("\r","").toByteArray() + runMFTryCatchWrappedQuery(listener) { + ZosDsn(zosConnection).writeDsn(dsn, member, textByteArray) + } + listener.logger.println(Messages.zdevops_declarative_writing_DS_success(dsn)) +} + +//TODO: docs +fun writeToFile(listener: TaskListener, + zosConnection: ZOSConnection, + destFile: String, + textBytes: ByteArray, + binary: Boolean?,) { + if (textBytes.isNotEmpty()) { + runMFTryCatchWrappedQuery(listener) { + if (binary == true) { + ZosUssFile(zosConnection).writeToFileBin(destFile, textBytes) + } else { + ZosUssFile(zosConnection).writeToFile(destFile, textBytes) + } + } + listener.logger.println(Messages.zdevops_declarative_writing_file_success(destFile)) + } else { + listener.logger.println(Messages.zdevops_declarative_writing_skip()) + } +} diff --git a/src/main/kotlin/io/jenkins/plugins/zdevops/model/ResolvedZOSConnection.kt b/src/main/kotlin/org/zowe/zdevops/model/ResolvedZOSConnection.kt similarity index 93% rename from src/main/kotlin/io/jenkins/plugins/zdevops/model/ResolvedZOSConnection.kt rename to src/main/kotlin/org/zowe/zdevops/model/ResolvedZOSConnection.kt index 7a32588..029c217 100644 --- a/src/main/kotlin/io/jenkins/plugins/zdevops/model/ResolvedZOSConnection.kt +++ b/src/main/kotlin/org/zowe/zdevops/model/ResolvedZOSConnection.kt @@ -8,7 +8,7 @@ * Copyright IBA Group 2022 */ -package io.jenkins.plugins.zdevops.model +package org.zowe.zdevops.model import java.io.Serializable diff --git a/src/main/kotlin/org/zowe/zdevops/utils/ConnectionValidation.kt b/src/main/kotlin/org/zowe/zdevops/utils/ConnectionValidation.kt new file mode 100644 index 0000000..110479f --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/utils/ConnectionValidation.kt @@ -0,0 +1,74 @@ +/* + * 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 2024 + */ + +package org.zowe.zdevops.utils + +import hudson.AbortException +import hudson.model.TaskListener +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.ZosDsnList +import org.zowe.kotlinsdk.zowe.client.sdk.zosfiles.input.ListParams +import org.zowe.zdevops.Messages +import org.zowe.zdevops.config.ZOSConnectionList +import java.io.PrintWriter +import java.io.StringWriter +import java.net.URL + + +/** + * Gets a list of datasets + * Calls the listDsn function of ZosDsnList to list data set names. + * Passes a test data set name ('HELLO.THERE'). + * + * @param zosConnection The ZOSConnection object representing the connection to the z/OS system. + */ +fun getTestDatasetList(zosConnection: ZOSConnection) { + ZosDsnList(zosConnection).listDsn(Messages.zdevops_config_ZOSConnection_validation_testDS(), ListParams()) +} + +/** + * Validates a z/OS connection. + * + * @param zosConnection The ZOSConnection object representing the connection to the z/OS system. + */ +fun validateConnection(zosConnection: ZOSConnection) { + try { + getTestDatasetList(zosConnection) + } catch (connectException: Exception) { + val connExMessage = "Failed to connect to z/OS (${zosConnection.user}@${zosConnection.host}:${zosConnection.zosmfPort}): ${connectException.message}" + throw AbortException(connExMessage) + } +} + +/** + * Retrieves zOS connection by its name from the zOS connection list in Jenkins configuration. + * + * This function attempts to resolve the connection from the ZOSConnectionList + * using the provided connection name. If the connection is not found in the list, an + * IllegalArgumentException is thrown with a detailed error message, and the stack trace + * is logged using the provided TaskListener. + * + * @param connectionName The name of the connection to resolve. + * @param listener The TaskListener used to log messages and exceptions. + * @return A ZOSConnection object containing the resolved connection details. + * @throws IllegalArgumentException If the connection configuration cannot be resolved. + */ +fun getZoweZosConnection(connectionName: String, listener: TaskListener?): ZOSConnection { + val connection = ZOSConnectionList.resolve(connectionName) ?: run { + val exception = IllegalArgumentException(Messages.zdevops_config_ZOSConnection_resolve_unknown(connectionName)) + val sw = StringWriter() + exception.printStackTrace(PrintWriter(sw)) + listener?.logger?.println(sw.toString()) + throw exception + } + + val connURL = URL(connection.url) + return ZOSConnection(connURL.host, connURL.port.toString(), connection.username, connection.password, connURL.protocol) +} diff --git a/src/main/kotlin/org/zowe/zdevops/utils/FieldsValidation.kt b/src/main/kotlin/org/zowe/zdevops/utils/FieldsValidation.kt new file mode 100644 index 0000000..41c9b18 --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/utils/FieldsValidation.kt @@ -0,0 +1,86 @@ +/* + * 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 + */ + +package org.zowe.zdevops.utils + +import hudson.util.FormValidation +import org.zowe.zdevops.Messages + +/** + * Validates a dataset name according to specific rules. + * + * @param dsn The dataset name to validate. + * @return A [FormValidation] result indicating the validation status. + */ +fun validateDatasetName(dsn: String): FormValidation? { + val dsnPattern = Regex("^[a-zA-Z#\$@][a-zA-Z0-9#\$@-]{0,7}([.][a-zA-Z#\$@][a-zA-Z0-9#\$@-]{0,7}){0,21}$") + + return if (dsn.isNotBlank()) { + if (!dsn.matches(dsnPattern)) { + FormValidation.warning(Messages.zdevops_dataset_name_is_invalid_validation()) + } else { + FormValidation.ok() + } + } else { + FormValidation.error(Messages.zdevops_value_must_not_be_empty_validation()) + } +} + +/** + * Validates a member name according to specific rules. + * + * @param member The member name to validate. + * @return A [FormValidation] result indicating the validation status. + */ +fun validateMemberName(member: String): FormValidation? { + val memberPattern = Regex("^(?:[A-Z#@\$][A-Z0-9#@\$]{0,7}|[a-z#@\$][a-zA-Z0-9#@\$]{0,7})\$") + + return if (member.length > 8 || member.isEmpty()) { + FormValidation.error(Messages.zdevops_value_up_to_eight_in_length_validation()) + } else if(!member.matches(memberPattern)) { + FormValidation.warning(Messages.zdevops_member_name_is_invalid_validation()) + } else { + FormValidation.ok() + } +} + +/** + * Validates a dataset name or dataset member name according to specific rules. + * + * @param dsnOrDsnMember The dataset name or dataset member name to validate. + * @return A [FormValidation] result indicating the validation status. + */ +fun validateDsnOrDsnMemberName(dsnOrDsnMember: String) : FormValidation? { + val dsnOrDsnMemberPattern = Regex("^[a-zA-Z#\$@][a-zA-Z0-9#\$@-]{0,7}([.][a-zA-Z#\$@][a-zA-Z0-9#\$@-]{0,7}){0,21}(?:[(](?:[A-Z#@\$][A-Z0-9#@\$]{0,7}|[a-z#@\$][a-zA-Z0-9#@\$]{0,7})[)]\$|)\$") + + return if (dsnOrDsnMember.isNotBlank()) { + if (!dsnOrDsnMember.matches(dsnOrDsnMemberPattern)) { + FormValidation.warning(Messages.zdevops_dataset_name_is_invalid_validation()) + } else { + FormValidation.ok() + } + } else { + FormValidation.error(Messages.zdevops_value_must_not_be_empty_validation()) + } +} + +/** + * Validates that a field value is not empty. + * + * @param value The field value to validate. + * @return A [FormValidation] result indicating the validation status. + */ +fun validateFieldIsNotEmpty(value: String): FormValidation? { + return if (value == "") { + FormValidation.error(Messages.zdevops_value_must_not_be_empty_validation()) + } else { + FormValidation.ok() + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/zowe/zdevops/utils/SimpleMFExceptionMessageExtractor.kt b/src/main/kotlin/org/zowe/zdevops/utils/SimpleMFExceptionMessageExtractor.kt new file mode 100644 index 0000000..e2fe159 --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/utils/SimpleMFExceptionMessageExtractor.kt @@ -0,0 +1,60 @@ +/* + * 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 + */ + +package org.zowe.zdevops.utils + +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import hudson.model.TaskListener + +/** + * A utility function for executing a try-catch block around a specified z/OSMF call and handling exceptions (e.g. getting log message). + * + * @param listener The task listener to log information and handle exceptions. + * @param call The lambda function to be executed. + * @return A [Result] containing the result of the call if successful, or an error message if an exception occurs. + */ +inline fun runMFTryCatchWrappedQuery(listener: TaskListener, call: () -> R): Result { + try { + return Result.success(call()) + } catch (e: Exception) { + lateinit var responseMap: Map + try { + responseMap = Gson().fromJson(e.message, object : TypeToken>() {}.type) + } catch (eInternal: Exception) { + if (eInternal.message?.contains(Regex("Expected .* but was STRING")) == true) { + listener.logger.println(e.message) + } else { + listener.logger.println(eInternal.message) + } + } + var errorContent: Any + try { + errorContent = responseMap.get("details") as ArrayList<*> + errorContent.forEach {listener.logger.println(it)} + } catch (e: NullPointerException) { + errorContent = responseMap.get("message") as String + listener.logger.println(errorContent) + } + throw Exception(e) + } +} + +/** + * Extracts a submit job message from HTTP JSON response. + * + * @param httpJson The HTTP JSON response containing the message. + * @return The extracted submit job message. + */ +fun extractSubmitJobMessage(httpJson: String) : String? { + val regex = Regex("message=(.*?)(\\\\n|\\z)") + val matchResult = regex.find(httpJson) + return matchResult?.groups?.get(1)?.value +} \ No newline at end of file diff --git a/src/main/resources/META-INF/hudson.remoting.ClassFilter b/src/main/resources/META-INF/hudson.remoting.ClassFilter index 45c9e8c..df095b2 100644 --- a/src/main/resources/META-INF/hudson.remoting.ClassFilter +++ b/src/main/resources/META-INF/hudson.remoting.ClassFilter @@ -1 +1,2 @@ org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +kotlin.collections.EmptyList \ No newline at end of file diff --git a/src/main/resources/index.jelly b/src/main/resources/index.jelly index c0ff9c2..d2a9447 100644 --- a/src/main/resources/index.jelly +++ b/src/main/resources/index.jelly @@ -1,4 +1,4 @@
- Mainframes z/OS automation plugin, working through z/OSMF REST API and using Zowe Kotlin SDK. -
\ No newline at end of file + Zowe mainframe z/OS automation plugin, working through z/OSMF REST API and using Zowe Kotlin SDK + diff --git a/src/main/resources/io/jenkins/plugins/zdevops/Messages.properties b/src/main/resources/org/zowe/zdevops/Messages.properties similarity index 58% rename from src/main/resources/io/jenkins/plugins/zdevops/Messages.properties rename to src/main/resources/org/zowe/zdevops/Messages.properties index a8d7338..767350b 100644 --- a/src/main/resources/io/jenkins/plugins/zdevops/Messages.properties +++ b/src/main/resources/org/zowe/zdevops/Messages.properties @@ -6,12 +6,46 @@ zdevops_config_ZOSConnection_validation_error=An error occurred while connecting zdevops_config_ZOSConnection_validation_wrong_credential_type=Wrong credential type. zdevops_config_ZOSConnection_validation_unknownName=Unknown z/OS Connection "{0}". Try to apply changes. zdevops_config_ZOSConnection_validation_testDS=HELLO.THERE +zdevops_config_ZOSConnectionList_validation_error=Connection list is empty. Please, create a connection.\n \ + It might be done the following way:\n \ + (1) Open "Manage Jenkins" page\n \ + (2) Go to Configure System\n \ + (3) At the bottom of the page there is "z/OS Connection List" section\n \ + (4) Click "Add", fill in the form and test the connection zdevops.classic.submitJobStep.display.name=[z/OS] - Submit job zdevops_classic_ZOSJobs_submitting=Submitting a JOB from file {0} with connection: {1}:{2} zdevops_classic_ZOSJobs_submitted_success=JOB submitted successfully. JOBID={0}, JOBNAME={1}, OWNER={2}. zdevops_classic_ZOSJobs_submitted_fail=Cannot submit or execute JOB from file {0}. +zdevops.classic.write.options.invalid=File option is invalid +zdevops.classic.write.options.choose=Choose file option +zdevops.classic.write.options.local=Choose Local File +zdevops.classic.write.options.workspace=Specify Workspace Path +zdevops.classic.write.options.required=Select field is required + +zdevops.classic.allocateDatasetStep.display.name=[z/OS] - Allocate dataset +zdevops.classic.allocateDatasetStep.blksize.validation.warning=Block size should be a multiple of the record length +zdevops.classic.allocateDatasetStep.blksize.smaller.than.lrecl.validation=BLKSIZE can't be smaller than LRECL +zdevops.classic.allocateDatasetStep.primary.is.zero.validation=Primary space value must be greater than 0 +zdevops.value.is.not.number.validation=Please enter a valid number in the field +zdevops.value.must.be.positive.number.validation=The field must be a positive number +zdevops.value.must.not.be.empty.validation=Field must not be empty +zdevops.value.up.to.eight.in.length.validation=The field must be between 1 and 8 characters in length +zdevops.dataset.name.is.invalid.validation=It seems the dataset name is invalid +zdevops.member.name.is.invalid.validation=It seems the member name is invalid +zdevops.volume.name.is.invalid.validation=It seems the volume name is invalid +zdevops.classic.writeToDSStep.display.name=[z/OS] - Write text to dataset +zdevops.classic.writeToFileStep.display.name=[z/OS] - Write text to USS file +zdevops.classic.writeToMemberStep.display.name=[z/OS] - Write text to member +zdevops.classic.writeFileToMemberStep.display.name=[z/OS] - Write file to member +zdevops.classic.writeFileToDatasetStep.display.name=[z/OS] - Write file to dataset +zdevops.classic.writeFileToFileStep.display.name=[z/OS] - Write file to USS file +zdevops.classic.downloadDatasetStep.display.name=[z/OS] - Download dataset/member +zdevops.classic.deleteDatasetStep.display.name=[z/OS] - Delete dataset/member +zdevops.classic.deleteDatasetsByMaskStep.display.name=[z/OS] - Delete datasets by mask +zdevops.classic.performTsoCommandStep.display.name=[z/OS] - Perform TSO command + zdevops.declarative.ZOSJobs.submitting=Submitting a JOB from file {0} with connection: {1}:{2} zdevops.declarative.ZOSJobs.submitted.success=JOB submitted successfully. JOBID={0}, JOBNAME={1}, OWNER={2}. zdevops.declarative.ZOSJobs.submitted.waiting=Waiting for a JOB finish... @@ -32,7 +66,7 @@ zdevops_declarative_DSN_allocated_fail=Cannot allocate dataset {0}. zdevops_declarative_writing_DS_from_file=Writing to dataset {0} from file {1} with connection: {2}:{3} zdevops_declarative_writing_DS_success=Data has been written to dataset {0} successfully. zdevops_declarative_writing_DS_from_input=Writing to dataset {0} from input string with connection: {1}:{2} -zdevops_declarative_writing_skip=No data was provided. Add file=... or text=... to your pipeline call. +zdevops_declarative_writing_skip=No data was provided. Add non-empty datasource to your pipeline call/step. zdevops_declarative_writing_DS_no_info=Cannot get info about target dataset {0} zdevops_declarative_writing_DS_ineligible_strings=There is(are) {0} string(s) in your input text that are larger than record length of {1}\nWriting has been stopped. zdevops_declarative_writing_DS_fail=Cannot write to dataset {0} @@ -49,4 +83,8 @@ zdevops_deleting_ds_by_mask=Deleting datasets that match the mask "{0}" zdevops_deleting_ds_fail_no_matching_mask=No data sets matching the mask were found zdevops_deleting_ds_success=Successfully deleted zdevops_deleting_datasets_by_mask_but_mask_is_empty=Unable to delete: mask is empty -zdevops_member_name_invalid=Invalid member name: must be 1-8 characters \ No newline at end of file +zdevops_member_name_invalid=Invalid member name: must be 1-8 characters + +zdevops.issue.TSO.command=[Perform TSO command] - Issuing command "{0}" +zdevops.TSO.command.fail=[Perform TSO command] - TSO command execution failed +zdevops.TSO.command.success=[Perform TSO command] - The command has been successfully executed diff --git a/src/main/resources/org/zowe/zdevops/classic/steps/AllocateDatasetStep/config.jelly b/src/main/resources/org/zowe/zdevops/classic/steps/AllocateDatasetStep/config.jelly new file mode 100644 index 0000000..adca495 --- /dev/null +++ b/src/main/resources/org/zowe/zdevops/classic/steps/AllocateDatasetStep/config.jelly @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ + + + + + + + +
+ + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + + + + +
+ + + + + + + + +
+ + + + + + + +
+ + + +
\ No newline at end of file diff --git a/src/main/resources/org/zowe/zdevops/classic/steps/AllocateDatasetStep/config.properties b/src/main/resources/org/zowe/zdevops/classic/steps/AllocateDatasetStep/config.properties new file mode 100644 index 0000000..aa3e16c --- /dev/null +++ b/src/main/resources/org/zowe/zdevops/classic/steps/AllocateDatasetStep/config.properties @@ -0,0 +1,23 @@ +zdevops.classic.connection.title=z/OS connection +zdevops.classic.dsn.title=Data Set Name +zdevops.classic.mgntClass.title=Management class +zdevops.classic.mgntClass.description=Blank for default management class +zdevops.classic.storClass.title=Storage class +zdevops.classic.storClass.description=Blank for default storage class +zdevops.classic.dataClass.title=Data class +zdevops.classic.dataClass.description=Blank for default data class +zdevops.classic.volser.title=Volume serial +zdevops.classic.volser.description=Blank for system default volume +zdevops.classic.dsOrg.title=Data Set Organization (DSORG) +zdevops.classic.dsnType.title=Data set name type +zdevops.classic.alcUnit.title=Space units +zdevops.classic.primary.title=Primary quantity +zdevops.classic.primary.description=In above units +zdevops.classic.secondary.title=Secondary quantity +zdevops.classic.secondary.description=In above units +zdevops.classic.dirBlk.title=Directory blocks +zdevops.classic.dirBlk.description=0 for sequential data set +zdevops.classic.recFm.title=Record format +zdevops.classic.lrecl.title=Record length +zdevops.classic.blkSize.title=Block size + diff --git a/src/main/resources/org/zowe/zdevops/classic/steps/DeleteDatasetStep/config.jelly b/src/main/resources/org/zowe/zdevops/classic/steps/DeleteDatasetStep/config.jelly new file mode 100644 index 0000000..aa711c2 --- /dev/null +++ b/src/main/resources/org/zowe/zdevops/classic/steps/DeleteDatasetStep/config.jelly @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/org/zowe/zdevops/classic/steps/DeleteDatasetStep/config.properties b/src/main/resources/org/zowe/zdevops/classic/steps/DeleteDatasetStep/config.properties new file mode 100644 index 0000000..ada18a6 --- /dev/null +++ b/src/main/resources/org/zowe/zdevops/classic/steps/DeleteDatasetStep/config.properties @@ -0,0 +1,3 @@ +zdevops.classic.connection.title=z/OS connection +zdevops.classic.dsn.title=Dataset +zdevops.classic.member.title=Member (optional) diff --git a/src/main/resources/org/zowe/zdevops/classic/steps/DeleteDatasetsByMaskStep/config.jelly b/src/main/resources/org/zowe/zdevops/classic/steps/DeleteDatasetsByMaskStep/config.jelly new file mode 100644 index 0000000..6fabee9 --- /dev/null +++ b/src/main/resources/org/zowe/zdevops/classic/steps/DeleteDatasetsByMaskStep/config.jelly @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/org/zowe/zdevops/classic/steps/DeleteDatasetsByMaskStep/config.properties b/src/main/resources/org/zowe/zdevops/classic/steps/DeleteDatasetsByMaskStep/config.properties new file mode 100644 index 0000000..756aa7e --- /dev/null +++ b/src/main/resources/org/zowe/zdevops/classic/steps/DeleteDatasetsByMaskStep/config.properties @@ -0,0 +1,2 @@ +zdevops.classic.connection.title=z/OS connection +zdevops.classic.dsnMask.title=Mask to filter datasets diff --git a/src/main/resources/io/jenkins/plugins/zdevops/classic/steps/SubmitJobStep/config.jelly b/src/main/resources/org/zowe/zdevops/classic/steps/DownloadDatasetStep/config.jelly similarity index 63% rename from src/main/resources/io/jenkins/plugins/zdevops/classic/steps/SubmitJobStep/config.jelly rename to src/main/resources/org/zowe/zdevops/classic/steps/DownloadDatasetStep/config.jelly index 0da3dde..3480140 100644 --- a/src/main/resources/io/jenkins/plugins/zdevops/classic/steps/SubmitJobStep/config.jelly +++ b/src/main/resources/org/zowe/zdevops/classic/steps/DownloadDatasetStep/config.jelly @@ -6,7 +6,10 @@ - + + + + \ No newline at end of file diff --git a/src/main/resources/org/zowe/zdevops/classic/steps/DownloadDatasetStep/config.properties b/src/main/resources/org/zowe/zdevops/classic/steps/DownloadDatasetStep/config.properties new file mode 100644 index 0000000..5ff34d5 --- /dev/null +++ b/src/main/resources/org/zowe/zdevops/classic/steps/DownloadDatasetStep/config.properties @@ -0,0 +1,3 @@ +zdevops.classic.connection.title=z/OS connection +zdevops.classic.dsn.title=Dataset or dataset member to download +zdevops.classic.vol.title=Volume (optional) diff --git a/src/main/resources/org/zowe/zdevops/classic/steps/PerformTsoCommandStep/config.jelly b/src/main/resources/org/zowe/zdevops/classic/steps/PerformTsoCommandStep/config.jelly new file mode 100644 index 0000000..647f850 --- /dev/null +++ b/src/main/resources/org/zowe/zdevops/classic/steps/PerformTsoCommandStep/config.jelly @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/org/zowe/zdevops/classic/steps/PerformTsoCommandStep/config.properties b/src/main/resources/org/zowe/zdevops/classic/steps/PerformTsoCommandStep/config.properties new file mode 100644 index 0000000..1ec285d --- /dev/null +++ b/src/main/resources/org/zowe/zdevops/classic/steps/PerformTsoCommandStep/config.properties @@ -0,0 +1,3 @@ +zdevops.classic.connection.title=z/OS connection +zdevops.classic.acct.title=TSO Account number +zdevops.classic.command.title=TSO command to be executed diff --git a/src/main/resources/org/zowe/zdevops/classic/steps/SubmitJobStep/config.jelly b/src/main/resources/org/zowe/zdevops/classic/steps/SubmitJobStep/config.jelly new file mode 100644 index 0000000..5f79f1b --- /dev/null +++ b/src/main/resources/org/zowe/zdevops/classic/steps/SubmitJobStep/config.jelly @@ -0,0 +1,37 @@ + + + + + + + +

${stepId}

+ + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/src/main/resources/io/jenkins/plugins/zdevops/classic/steps/SubmitJobStep/config.properties b/src/main/resources/org/zowe/zdevops/classic/steps/SubmitJobStep/config.properties similarity index 71% rename from src/main/resources/io/jenkins/plugins/zdevops/classic/steps/SubmitJobStep/config.properties rename to src/main/resources/org/zowe/zdevops/classic/steps/SubmitJobStep/config.properties index 2f70a5b..7e459ee 100644 --- a/src/main/resources/io/jenkins/plugins/zdevops/classic/steps/SubmitJobStep/config.properties +++ b/src/main/resources/org/zowe/zdevops/classic/steps/SubmitJobStep/config.properties @@ -1,9 +1,9 @@ zdevops.classic.connection.title=z/OS connection zdevops.classic.jobName.title=Job name to submit -zdevops.classic.jobName.description=Enter job name in the following format: \ - (1) Partitioned data set (fully qualified) - //\u02C8MYJOBS.TEST.CNTL(TESTJOBX)\u02C8. \ - (2) Partitioned data set (non-fully qualified) - //TEST.CNTL(TESTJOBX). \ - (3) Sequential data set - //\u02C8MYJOBS.TEST.JOB1\u02C8. \ +zdevops.classic.jobName.description=Enter job name in the following format:
\ + (1) Partitioned data set (fully qualified) - //\u02C8MYJOBS.TEST.CNTL(TESTJOBX)\u02C8.
\ + (2) Partitioned data set (non-fully qualified) - //TEST.CNTL(TESTJOBX).
\ + (3) Sequential data set - //\u02C8MYJOBS.TEST.JOB1\u02C8.
\ (4) UNIX file - /u/myjobs/job1. diff --git a/src/main/resources/org/zowe/zdevops/classic/steps/WriteFileToDatasetStep/config.jelly b/src/main/resources/org/zowe/zdevops/classic/steps/WriteFileToDatasetStep/config.jelly new file mode 100644 index 0000000..c549491 --- /dev/null +++ b/src/main/resources/org/zowe/zdevops/classic/steps/WriteFileToDatasetStep/config.jelly @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/org/zowe/zdevops/classic/steps/WriteFileToDatasetStep/config.properties b/src/main/resources/org/zowe/zdevops/classic/steps/WriteFileToDatasetStep/config.properties new file mode 100644 index 0000000..7bba5ba --- /dev/null +++ b/src/main/resources/org/zowe/zdevops/classic/steps/WriteFileToDatasetStep/config.properties @@ -0,0 +1,8 @@ +zdevops.classic.connection.title=z/OS connection +zdevops.classic.datasetName.title=Dataset name +zdevops.classic.fileOption.title=File option +zdevops.classic.fileOption.description=Specify either path to a file on your computer or path to a file in workspace +zdevops.classic.localFilePath.title=File location +zdevops.classic.localFilePath.description=Specify absolute path to a file on your computer + +zdevops.classic.workspacePath.title=Workspace File Path \ No newline at end of file diff --git a/src/main/resources/org/zowe/zdevops/classic/steps/WriteFileToFileStep/config.jelly b/src/main/resources/org/zowe/zdevops/classic/steps/WriteFileToFileStep/config.jelly new file mode 100644 index 0000000..6b162c9 --- /dev/null +++ b/src/main/resources/org/zowe/zdevops/classic/steps/WriteFileToFileStep/config.jelly @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/org/zowe/zdevops/classic/steps/WriteFileToFileStep/config.properties b/src/main/resources/org/zowe/zdevops/classic/steps/WriteFileToFileStep/config.properties new file mode 100644 index 0000000..2736958 --- /dev/null +++ b/src/main/resources/org/zowe/zdevops/classic/steps/WriteFileToFileStep/config.properties @@ -0,0 +1,8 @@ +zdevops.classic.connection.title=z/OS connection +zdevops.classic.filePathUSS.title=USS File Path +zdevops.classic.fileOption.title=File option +zdevops.classic.fileOption.description=Specify either path to a file on your computer or path to a file in workspace +zdevops.classic.localFilePath.title=File location +zdevops.classic.localFilePath.description=Specify absolute path to a file on your computer +zdevops.classic.binary.title=Binary? +zdevops.classic.workspacePath.title=Workspace File Path \ No newline at end of file diff --git a/src/main/resources/org/zowe/zdevops/classic/steps/WriteFileToMemberStep/config.jelly b/src/main/resources/org/zowe/zdevops/classic/steps/WriteFileToMemberStep/config.jelly new file mode 100644 index 0000000..ca04f62 --- /dev/null +++ b/src/main/resources/org/zowe/zdevops/classic/steps/WriteFileToMemberStep/config.jelly @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/org/zowe/zdevops/classic/steps/WriteFileToMemberStep/config.properties b/src/main/resources/org/zowe/zdevops/classic/steps/WriteFileToMemberStep/config.properties new file mode 100644 index 0000000..25f2c72 --- /dev/null +++ b/src/main/resources/org/zowe/zdevops/classic/steps/WriteFileToMemberStep/config.properties @@ -0,0 +1,9 @@ +zdevops.classic.connection.title=z/OS connection +zdevops.classic.datasetName.title=Dataset name +zdevops.classic.member.title=Member +zdevops.classic.fileOption.title=File option +zdevops.classic.fileOption.description=Specify either path to a file on your computer or path to a file in workspace +zdevops.classic.localFilePath.title=File location +zdevops.classic.localFilePath.description=Specify absolute path to a file on your computer + +zdevops.classic.workspacePath.title=Workspace File Path \ No newline at end of file diff --git a/src/main/resources/org/zowe/zdevops/classic/steps/WriteToDatasetStep/config.jelly b/src/main/resources/org/zowe/zdevops/classic/steps/WriteToDatasetStep/config.jelly new file mode 100644 index 0000000..790c607 --- /dev/null +++ b/src/main/resources/org/zowe/zdevops/classic/steps/WriteToDatasetStep/config.jelly @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/org/zowe/zdevops/classic/steps/WriteToDatasetStep/config.properties b/src/main/resources/org/zowe/zdevops/classic/steps/WriteToDatasetStep/config.properties new file mode 100644 index 0000000..46d0354 --- /dev/null +++ b/src/main/resources/org/zowe/zdevops/classic/steps/WriteToDatasetStep/config.properties @@ -0,0 +1,3 @@ +zdevops.classic.connection.title=z/OS connection +zdevops.classic.datasetName.title=Dataset name +zdevops.classic.text.title=Text diff --git a/src/main/resources/org/zowe/zdevops/classic/steps/WriteToFileStep/config.jelly b/src/main/resources/org/zowe/zdevops/classic/steps/WriteToFileStep/config.jelly new file mode 100644 index 0000000..ff3bdaa --- /dev/null +++ b/src/main/resources/org/zowe/zdevops/classic/steps/WriteToFileStep/config.jelly @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/org/zowe/zdevops/classic/steps/WriteToFileStep/config.properties b/src/main/resources/org/zowe/zdevops/classic/steps/WriteToFileStep/config.properties new file mode 100644 index 0000000..e09f81f --- /dev/null +++ b/src/main/resources/org/zowe/zdevops/classic/steps/WriteToFileStep/config.properties @@ -0,0 +1,4 @@ +zdevops.classic.connection.title=z/OS connection +zdevops.classic.filePath.title=USS File path +zdevops.classic.text.title=Text +zdevops.classic.binary.title=Binary? diff --git a/src/main/resources/org/zowe/zdevops/classic/steps/WriteToMemberStep/config.jelly b/src/main/resources/org/zowe/zdevops/classic/steps/WriteToMemberStep/config.jelly new file mode 100644 index 0000000..28010fd --- /dev/null +++ b/src/main/resources/org/zowe/zdevops/classic/steps/WriteToMemberStep/config.jelly @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/org/zowe/zdevops/classic/steps/WriteToMemberStep/config.properties b/src/main/resources/org/zowe/zdevops/classic/steps/WriteToMemberStep/config.properties new file mode 100644 index 0000000..64c87b4 --- /dev/null +++ b/src/main/resources/org/zowe/zdevops/classic/steps/WriteToMemberStep/config.properties @@ -0,0 +1,4 @@ +zdevops.classic.connection.title=z/OS connection +zdevops.classic.datasetName.title=Dataset name +zdevops.classic.member.title=Member +zdevops.classic.text.title=Text diff --git a/src/main/resources/io/jenkins/plugins/zdevops/config/ZOSConnection/config.jelly b/src/main/resources/org/zowe/zdevops/config/ZOSConnection/config.jelly similarity index 91% rename from src/main/resources/io/jenkins/plugins/zdevops/config/ZOSConnection/config.jelly rename to src/main/resources/org/zowe/zdevops/config/ZOSConnection/config.jelly index 414507a..8280a21 100644 --- a/src/main/resources/io/jenkins/plugins/zdevops/config/ZOSConnection/config.jelly +++ b/src/main/resources/org/zowe/zdevops/config/ZOSConnection/config.jelly @@ -7,7 +7,7 @@ - + diff --git a/src/main/resources/io/jenkins/plugins/zdevops/config/ZOSConnection/config.properties b/src/main/resources/org/zowe/zdevops/config/ZOSConnection/config.properties similarity index 100% rename from src/main/resources/io/jenkins/plugins/zdevops/config/ZOSConnection/config.properties rename to src/main/resources/org/zowe/zdevops/config/ZOSConnection/config.properties diff --git a/src/main/resources/io/jenkins/plugins/zdevops/config/ZOSConnectionList/config.jelly b/src/main/resources/org/zowe/zdevops/config/ZOSConnectionList/config.jelly similarity index 100% rename from src/main/resources/io/jenkins/plugins/zdevops/config/ZOSConnectionList/config.jelly rename to src/main/resources/org/zowe/zdevops/config/ZOSConnectionList/config.jelly diff --git a/src/main/resources/io/jenkins/plugins/zdevops/config/ZOSConnectionList/config.properties b/src/main/resources/org/zowe/zdevops/config/ZOSConnectionList/config.properties similarity index 100% rename from src/main/resources/io/jenkins/plugins/zdevops/config/ZOSConnectionList/config.properties rename to src/main/resources/org/zowe/zdevops/config/ZOSConnectionList/config.properties diff --git a/src/test/kotlin/org/zowe/zdevops/MockResponseDispatcher.kt b/src/test/kotlin/org/zowe/zdevops/MockResponseDispatcher.kt new file mode 100644 index 0000000..1fc1dd2 --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/MockResponseDispatcher.kt @@ -0,0 +1,58 @@ +/* + * 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 + */ + +package org.zowe.zdevops + +import okhttp3.mockwebserver.Dispatcher +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.RecordedRequest + +data class ValidationListElem( + val name: String, + val validator: (RecordedRequest?) -> Boolean, + val handler: (RecordedRequest?) -> MockResponse +) + +class MockResponseDispatcher : Dispatcher() { + + private var validationList = mutableListOf() + + private fun getResourceText(resourcePath: String): String? { + return javaClass.classLoader.getResource(resourcePath)?.readText() + } + + fun readMockJson(mockFilePath: String): String? { + return getResourceText("mock/${mockFilePath}.json") + } + + fun injectEndpoint( + name: String, + validator: (RecordedRequest?) -> Boolean, + handler: (RecordedRequest?) -> MockResponse + ) { + validationList.add(ValidationListElem(name, validator, handler)) + } + + fun removeEndpoint(name: String) { + validationList.removeAll { it.name == name } + } + + fun removeAllEndpoints() { + validationList.clear() + } + + override fun dispatch(request: RecordedRequest): MockResponse { + return validationList + .firstOrNull { it.validator(request) } + ?.handler + ?.let { it(request) } + ?: MockResponse().setBody("Response is not implemented").setResponseCode(404) + } +} diff --git a/src/test/kotlin/org/zowe/zdevops/MockServerFactory.kt b/src/test/kotlin/org/zowe/zdevops/MockServerFactory.kt new file mode 100644 index 0000000..e565f39 --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/MockServerFactory.kt @@ -0,0 +1,43 @@ +/* + * 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 + */ + +package org.zowe.zdevops + +import okhttp3.mockwebserver.MockWebServer +import okhttp3.tls.HandshakeCertificates +import okhttp3.tls.HeldCertificate +import java.net.InetAddress +import java.util.concurrent.TimeUnit + +class MockServerFactory { + lateinit var mockServer: MockWebServer + lateinit var responseDispatcher: MockResponseDispatcher + + fun startMockServer (host: String): MockWebServer { + val localhost = InetAddress.getByName(host).canonicalHostName + val localhostCertificate = HeldCertificate.Builder() + .addSubjectAlternativeName(localhost) + .duration(10, TimeUnit.MINUTES) + .build() + val serverCertificates = HandshakeCertificates.Builder() + .heldCertificate(localhostCertificate) + .build() + mockServer = MockWebServer() + responseDispatcher = MockResponseDispatcher() + mockServer.dispatcher = responseDispatcher + mockServer.useHttps(serverCertificates.sslSocketFactory(), false) + mockServer.start() + return mockServer + } + + fun stopMockServer() { + mockServer.shutdown() + } +} \ No newline at end of file diff --git a/src/test/kotlin/org/zowe/zdevops/classic/steps/AllocateDatasetStepSpec.kt b/src/test/kotlin/org/zowe/zdevops/classic/steps/AllocateDatasetStepSpec.kt new file mode 100644 index 0000000..1b2324f --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/classic/steps/AllocateDatasetStepSpec.kt @@ -0,0 +1,233 @@ +/* + * 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 + */ + +package org.zowe.zdevops.classic.steps + +import hudson.model.Item +import hudson.util.FormValidation +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.fail +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.engine.spec.tempdir +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.zowe.kotlinsdk.AllocationUnit +import org.zowe.kotlinsdk.DatasetOrganization +import org.zowe.kotlinsdk.DsnameType +import org.zowe.kotlinsdk.RecordFormat +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.MOCK_SERVER_HOST +import org.zowe.zdevops.Messages +import org.zowe.zdevops.MockResponseDispatcher +import org.zowe.zdevops.MockServerFactory +import java.io.File +import java.io.PrintStream +import java.nio.file.Paths + +class AllocateDatasetStepSpec : ShouldSpec({ + lateinit var mockServer: MockWebServer + lateinit var responseDispatcher: MockResponseDispatcher + val mockServerFactory = MockServerFactory() + + beforeSpec { + mockServer = mockServerFactory.startMockServer(MOCK_SERVER_HOST) + responseDispatcher = mockServerFactory.responseDispatcher + } + afterSpec { + mockServerFactory.stopMockServer() + } + context("classic/steps module: AllocateDatasetStep") { + val rootDir = Paths.get("").toAbsolutePath().toString() + val trashDir = tempdir() + val itemGroup = object : TestItemGroup() { + override fun getRootDirFor(child: Item?): File { + return trashDir + } + } + val project = TestProject(itemGroup, "test") + val build = TestBuild(project) + val virtualChannel = TestVirtualChannel() + val zosConnection = ZOSConnection(mockServer.hostName, mockServer.port.toString(), "test", "test", "https") + + afterEach { + responseDispatcher.removeAllEndpoints() + } + should("perform AllocateDatasetStep operation") { + var isDatasetAllocating = false + var isDatasetAllocated = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Allocating dataset")) { + isDatasetAllocating = true + } else if (firstArg().contains("has been allocated successfully")) { + isDatasetAllocated = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + + responseDispatcher.injectEndpoint( + this.testCase.name.testName, + { it?.requestLine?.contains("/zosmf/restfiles/ds/") ?: false }, + { MockResponse().setBody("{}") } + ) + + val allocateDatasetStepInst = spyk( + AllocateDatasetStep( + "test", + "TEST.IJMP.DATASET1", + DatasetOrganization.PS, + 1, + 0, + RecordFormat.F + ) + ) + allocateDatasetStepInst.setAlcUnit(AllocationUnit.CYL) + allocateDatasetStepInst.setStorClass("") + allocateDatasetStepInst.setStorClass("TEST") + allocateDatasetStepInst.setMgntClass("") + allocateDatasetStepInst.setMgntClass("TEST") + allocateDatasetStepInst.setDataClass("") + allocateDatasetStepInst.setDataClass("TEST") + allocateDatasetStepInst.setVolser("") + allocateDatasetStepInst.setVolser("TEST") + allocateDatasetStepInst.setUnit("") + allocateDatasetStepInst.setUnit("TEST") + allocateDatasetStepInst.setLrecl(3120) + allocateDatasetStepInst.setBlkSize(3120) + allocateDatasetStepInst.setDsnType(DsnameType.BASIC) + allocateDatasetStepInst.perform( + build, + launcher, + taskListener, + zosConnection + ) + assertSoftly { isDatasetAllocating shouldBe true } + assertSoftly { isDatasetAllocated shouldBe true } + } + + should("fail as such dataset already exists and failOnExist is set to true") { + var isDatasetAllocating = false + var isFailedToAllocate = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Allocating dataset")) { + isDatasetAllocating = true + } else if (firstArg().contains("Dataset allocation failed.")) { + isFailedToAllocate = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + + responseDispatcher.injectEndpoint( + this.testCase.name.testName, + { it?.requestLine?.contains("/zosmf/restfiles/ds/") ?: false }, + { MockResponse().setResponseCode(409) } + ) + + val allocateDatasetStepInst = spyk( + AllocateDatasetStep( + "test", + "TEST.IJMP.DATASET1", + DatasetOrganization.PS, + 1, + 0, + RecordFormat.F, + failOnExist = true + ) + ) + allocateDatasetStepInst.setAlcUnit(AllocationUnit.CYL) + allocateDatasetStepInst.setStorClass("") + allocateDatasetStepInst.setStorClass("TEST") + allocateDatasetStepInst.setMgntClass("") + allocateDatasetStepInst.setMgntClass("TEST") + allocateDatasetStepInst.setDataClass("") + allocateDatasetStepInst.setDataClass("TEST") + allocateDatasetStepInst.setVolser("") + allocateDatasetStepInst.setVolser("TEST") + allocateDatasetStepInst.setUnit("") + allocateDatasetStepInst.setUnit("TEST") + allocateDatasetStepInst.setLrecl(3120) + allocateDatasetStepInst.setBlkSize(3120) + allocateDatasetStepInst.setDsnType(DsnameType.BASIC) + try { + allocateDatasetStepInst.perform( + build, + launcher, + taskListener, + zosConnection + ) + } catch (ex: Exception) { + isFailedToAllocate = true + } + assertSoftly { isDatasetAllocating shouldBe true } + assertSoftly { isFailedToAllocate shouldBe true } + } + + } + + val descriptor = AllocateDatasetStep.DescriptorImpl() + context("classic/steps module: AllocateDatasetStepDescriptor") { + should("validate primary allocation size") { + descriptor.doCheckPrimary("") shouldBe FormValidation.ok() + descriptor.doCheckPrimary("100") shouldBe FormValidation.ok() + descriptor.doCheckPrimary("0") shouldBe FormValidation.error(Messages.zdevops_classic_allocateDatasetStep_primary_is_zero_validation()) + descriptor.doCheckPrimary("abc") shouldBe FormValidation.error(Messages.zdevops_value_is_not_number_validation()) + } + + should("validate secondary allocation size") { + descriptor.doCheckSecondary("") shouldBe FormValidation.ok() + descriptor.doCheckSecondary("200") shouldBe FormValidation.ok() + descriptor.doCheckSecondary("xyz") shouldBe FormValidation.error(Messages.zdevops_value_is_not_number_validation()) + } + + should("validate block size") { + descriptor.doCheckBlkSize("", "") shouldBe FormValidation.ok() + descriptor.doCheckBlkSize("80", "240") shouldBe FormValidation.ok() + descriptor.doCheckBlkSize("240","80") shouldBe FormValidation.warning(Messages.zdevops_classic_allocateDatasetStep_blksize_smaller_than_lrecl_validation()) + descriptor.doCheckBlkSize("80","abc") shouldBe FormValidation.warning(Messages.zdevops_value_is_not_number_validation()) + descriptor.doCheckBlkSize("80","200") shouldBe FormValidation.warning(Messages.zdevops_classic_allocateDatasetStep_blksize_validation_warning()) + } + + should("validate dataset name") { + descriptor.doCheckDsn("") shouldBe FormValidation.error(Messages.zdevops_value_must_not_be_empty_validation()) + descriptor.doCheckDsn("MY_DATASET") shouldBe FormValidation.error(Messages.zdevops_dataset_name_is_invalid_validation()) + } + + should("convert string and validate positive integer") { + descriptor.convertStringAndValidateIntPositive("") shouldBe FormValidation.ok() + descriptor.convertStringAndValidateIntPositive("100") shouldBe FormValidation.ok() + descriptor.convertStringAndValidateIntPositive("0") shouldBe FormValidation.ok() + descriptor.convertStringAndValidateIntPositive("-10") shouldBe FormValidation.error(Messages.zdevops_value_must_be_positive_number_validation()) + descriptor.convertStringAndValidateIntPositive("abc") shouldBe FormValidation.error(Messages.zdevops_value_is_not_number_validation()) + } + } +}) \ No newline at end of file diff --git a/src/test/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetStepSpec.kt b/src/test/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetStepSpec.kt new file mode 100644 index 0000000..f110b9d --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetStepSpec.kt @@ -0,0 +1,162 @@ +package org.zowe.zdevops.classic.steps + +import hudson.FilePath +import hudson.model.Executor +import hudson.model.Item +import hudson.util.FormValidation +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.fail +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.engine.spec.tempdir +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.MOCK_SERVER_HOST +import org.zowe.zdevops.Messages +import org.zowe.zdevops.MockResponseDispatcher +import org.zowe.zdevops.MockServerFactory +import org.zowe.zdevops.declarative.jobs.TestBuildListener +import org.zowe.zdevops.declarative.jobs.TestLauncher +import java.io.File +import java.io.PrintStream + +class DeleteDatasetStepSpec : ShouldSpec({ + lateinit var mockServer: MockWebServer + lateinit var responseDispatcher: MockResponseDispatcher + val mockServerFactory = MockServerFactory() + + beforeSpec { + mockServer = mockServerFactory.startMockServer(MOCK_SERVER_HOST) + responseDispatcher = mockServerFactory.responseDispatcher + } + afterSpec { + mockServerFactory.stopMockServer() + } + context("classic/steps module: DeleteDatasetStep") { + val virtualChannel = TestVirtualChannel() + val zosConnection = ZOSConnection(mockServer.hostName, mockServer.port.toString(), "test", "test", "https") + val trashDir = tempdir() + val itemGroup = object : TestItemGroup() { + override fun getRootDirFor(child: Item?): File { + return trashDir + } + } + val project = TestProject(itemGroup, "test") + val build = object:TestBuild(project) { + override fun getExecutor(): Executor { + val mockInstance = mockk() + val mockDir = tempdir() + every { mockInstance.currentWorkspace } returns FilePath(virtualChannel, mockDir.absolutePath) + return mockInstance + } + } + + afterEach { + responseDispatcher.removeAllEndpoints() + } + should("perform DeleteDatasetStep operation that deletes a dataset") { + var isDeletingDataset = false + var isSuccessfullyDeleted = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Deleting dataset")) { + isDeletingDataset = true + } else if (firstArg().contains("Successfully deleted")) { + isSuccessfullyDeleted = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + + responseDispatcher.injectEndpoint( + this.testCase.name.testName, + { it?.requestLine?.contains(Regex("DELETE /zosmf/restfiles/ds/.* HTTP/.*")) ?: false }, + { MockResponse().setBody("{}") } + ) + + val deleteDatasetStep = spyk( + DeleteDatasetStep("test", "TEST.IJMP.DATASET1", member = null) + ) + deleteDatasetStep.perform( + build, + launcher, + taskListener, + zosConnection + ) + + assertSoftly { isDeletingDataset shouldBe true } + assertSoftly { isSuccessfullyDeleted shouldBe true } + } + + should("succeed as there is no such dataset, but failOnNotExist is set to false") { + var isDeletingDataset = false + var isContinuingExecution = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Deleting dataset")) { + isDeletingDataset = true + } else if (firstArg().contains("Dataset deletion failed, but the `failOnNotExist` option is set to false.") + || firstArg().contains("Reason")) { + isContinuingExecution = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + + responseDispatcher.injectEndpoint( + this.testCase.name.testName, + { it?.requestLine?.contains(Regex("DELETE /zosmf/restfiles/ds/.* HTTP/.*")) ?: false }, + { MockResponse().setBody("{}").setResponseCode(404) } + ) + + val deleteDatasetStep = spyk( + DeleteDatasetStep("test", "TEST.IJMP.DATASET1", member = null, failOnNotExist = false) + ) + deleteDatasetStep.perform( + build, + launcher, + taskListener, + zosConnection + ) + + assertSoftly { isDeletingDataset shouldBe true } + assertSoftly { isContinuingExecution shouldBe true } + } + } + + + val descriptor = DeleteDatasetStep.DescriptorImpl() + context("classic/steps module: DeleteDatasetStep.DescriptorImpl") { + + should("validate dataset name") { + descriptor.doCheckDsn("") shouldBe FormValidation.error(Messages.zdevops_value_must_not_be_empty_validation()) + descriptor.doCheckDsn("MY_DATASET") shouldBe FormValidation.error(Messages.zdevops_dataset_name_is_invalid_validation()) + } + + should("validate member name") { + descriptor.doCheckMember("") shouldBe FormValidation.ok() + descriptor.doCheckMember("@MY_DS") shouldBe FormValidation.warning(Messages.zdevops_member_name_is_invalid_validation()) + descriptor.doCheckMember("DSNAME") shouldBe FormValidation.ok() + } + } +}) \ No newline at end of file diff --git a/src/test/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetsByMaskStepSpec.kt b/src/test/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetsByMaskStepSpec.kt new file mode 100644 index 0000000..87f0ea8 --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetsByMaskStepSpec.kt @@ -0,0 +1,148 @@ +package org.zowe.zdevops.classic.steps + +import hudson.AbortException +import hudson.FilePath +import hudson.model.Executor +import hudson.model.Item +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.fail +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.engine.spec.tempdir +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.MOCK_SERVER_HOST +import org.zowe.zdevops.MockResponseDispatcher +import org.zowe.zdevops.MockServerFactory +import org.zowe.zdevops.declarative.jobs.TestBuildListener +import org.zowe.zdevops.declarative.jobs.TestLauncher +import java.io.File +import java.io.PrintStream +import java.nio.file.Paths + +class DeleteDatasetsByMaskStepSpec : ShouldSpec({ + lateinit var mockServer: MockWebServer + lateinit var responseDispatcher: MockResponseDispatcher + val mockServerFactory = MockServerFactory() + + beforeSpec { + mockServer = mockServerFactory.startMockServer(MOCK_SERVER_HOST) + responseDispatcher = mockServerFactory.responseDispatcher + } + afterSpec { + mockServerFactory.stopMockServer() + } + context("classic/steps module: DeleteDatasetStep") { + val virtualChannel = TestVirtualChannel() + val zosConnection = ZOSConnection(mockServer.hostName, mockServer.port.toString(), "test", "test", "https") + val rootDir = Paths.get("").toAbsolutePath().toString() + val trashDir = tempdir() + val itemGroup = object : TestItemGroup() { + override fun getRootDirFor(child: Item?): File { + return trashDir + } + } + val project = TestProject(itemGroup, "test") + val build = object:TestBuild(project) { + override fun getExecutor(): Executor { + val mockInstance = mockk() + val mockDir = Paths.get(rootDir, "src", "test", "resources", "mock", "test_file.txt").toString() + every { mockInstance.currentWorkspace } returns FilePath(virtualChannel, mockDir) + return mockInstance + } + } + + afterEach { + responseDispatcher.removeAllEndpoints() + } + should("perform DeleteDatasetsByMaskStep operation that deletes datasets") { + var isDeletingDatasets = false + var isSuccessfullyDeleted = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Deleting dataset")) { + isDeletingDatasets = true + } else if (firstArg().contains("Successfully deleted")) { + isSuccessfullyDeleted = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_listDataSets", + { it?.requestLine?.contains("zosmf/restfiles/ds?dslevel") ?: false }, + { MockResponse().setBody(responseDispatcher.readMockJson("listDataSets") ?: "") } + ) + + responseDispatcher.injectEndpoint( + this.testCase.name.testName, + { it?.requestLine?.contains(Regex("DELETE /zosmf/restfiles/ds/.* HTTP/.*")) ?: false }, + { MockResponse().setBody("{}") } + ) + + val deleteDatasetDecl = spyk( + DeleteDatasetsByMaskStep("test", "TEST.IJMP.DATASET%") + ) + deleteDatasetDecl.perform( + build, + launcher, + taskListener, + zosConnection + ) + + assertSoftly { isDeletingDatasets shouldBe true } + assertSoftly { isSuccessfullyDeleted shouldBe true } + } + should("throw AbortException if Dataset List is empty") { + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers {} + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_listDataSets", + { it?.requestLine?.contains("zosmf/restfiles/ds?dslevel") ?: false }, + { MockResponse().setBody(responseDispatcher.readMockJson("emptyDataSetsList") ?: "") } + ) + + responseDispatcher.injectEndpoint( + this.testCase.name.testName, + { it?.requestLine?.contains(Regex("DELETE /zosmf/restfiles/ds/test HTTP/.*")) ?: false }, + { MockResponse().setBody("{}") } + ) + + val deleteDatasetDecl = spyk( + DeleteDatasetsByMaskStep("test", "TEST.IJMP.DATASET%.NONE", failOnNotExist = true) + ) + shouldThrow { + deleteDatasetDecl.perform( + build, + launcher, + taskListener, + zosConnection + ) + } + + } + } +}) \ No newline at end of file diff --git a/src/test/kotlin/org/zowe/zdevops/classic/steps/DownloadDatasetStepSpec.kt b/src/test/kotlin/org/zowe/zdevops/classic/steps/DownloadDatasetStepSpec.kt new file mode 100644 index 0000000..7bf225c --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/classic/steps/DownloadDatasetStepSpec.kt @@ -0,0 +1,292 @@ +/* + * 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 + */ + +package org.zowe.zdevops.classic.steps + +import hudson.AbortException +import hudson.FilePath +import hudson.model.Executor +import hudson.model.Item +import hudson.util.FormValidation +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.fail +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.engine.spec.tempdir +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.MOCK_SERVER_HOST +import org.zowe.zdevops.Messages +import org.zowe.zdevops.MockResponseDispatcher +import org.zowe.zdevops.MockServerFactory +import org.zowe.zdevops.declarative.jobs.TestBuildListener +import org.zowe.zdevops.declarative.jobs.TestItemGroup +import org.zowe.zdevops.declarative.jobs.TestLauncher +import org.zowe.zdevops.declarative.jobs.TestVirtualChannel +import java.io.File +import java.io.PrintStream + + +class DownloadDatasetStepSpec : ShouldSpec({ + lateinit var mockServer: MockWebServer + lateinit var responseDispatcher: MockResponseDispatcher + val mockServerFactory = MockServerFactory() + + beforeSpec { + mockServer = mockServerFactory.startMockServer(MOCK_SERVER_HOST) + responseDispatcher = mockServerFactory.responseDispatcher + } + afterSpec { + mockServerFactory.stopMockServer() + } + context("classic/steps module: DownloadDatasetStep") { + val virtualChannel = TestVirtualChannel() + val zosConnection = ZOSConnection(mockServer.hostName, mockServer.port.toString(), "test", "test", "https") + val trashDir = tempdir() + val itemGroup = object : TestItemGroup() { + override fun getRootDirFor(child: Item?): File { + return trashDir + } + } + val project = TestProject(itemGroup, "test") + val build = object:TestBuild(project) { + override fun getExecutor(): Executor { + val mockInstance = mockk() + val mockDir = tempdir() + every { mockInstance.currentWorkspace } returns FilePath(virtualChannel, mockDir.absolutePath) + return mockInstance + } + } + + afterEach { + responseDispatcher.removeAllEndpoints() + } + should("perform DownloadDatasetStep operation to download sequential dataset") { + var isDownloadDatasetStarted = false + var isDownloaded = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Downloading dataset")) { + isDownloadDatasetStarted = true + } else if (firstArg().contains("has been downloaded successfully")) { + isDownloaded = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_listDataSets", + { it?.requestLine?.contains("zosmf/restfiles/ds?dslevel") ?: false }, + { MockResponse().setBody(responseDispatcher.readMockJson("listDataSetsPS") ?: "") } + ) + val retrieveDatasetContentResp = javaClass.classLoader.getResource("mock/retrieveDatasetContentResponse.txt")?.readText() + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_retrieveDatasetContent", + { it?.requestLine?.contains("/zosmf/restfiles/ds/") ?: false }, + { MockResponse().setBody(retrieveDatasetContentResp ?: "") } + ) + + val downloadFileDecl = spyk( + DownloadDatasetStep("test", "TEST") + ) + downloadFileDecl.setVol("TEST") + downloadFileDecl.setReturnEtag(false) + downloadFileDecl.perform( + build, + launcher, + taskListener, + zosConnection + ) + + assertSoftly { isDownloadDatasetStarted shouldBe true } + assertSoftly { isDownloaded shouldBe true } + } + + should("perform DownloadDatasetStep operation to download library dataset") { + var isDownloadDatasetStarted = false + var isDownloaded = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Starting to download") || firstArg().contains("Downloading dataset")) { + isDownloadDatasetStarted = true + } else if (firstArg().contains("has been downloaded successfully")) { + isDownloaded = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_listDataSets", + { it?.requestLine?.contains("zosmf/restfiles/ds?dslevel") ?: false }, + { MockResponse().setBody(responseDispatcher.readMockJson("listDataSets") ?: "") } + ) + + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_listDataSetMembers", + { it?.requestLine?.contains(Regex("zosmf/restfiles/ds/.*\\/member")) ?: false }, + { MockResponse().setBody(responseDispatcher.readMockJson("listDataSetMembers") ?: "") } + ) + + val retrieveDatasetContentResp = javaClass.classLoader.getResource("mock/retrieveDatasetContentResponse.txt")?.readText() + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_retrieveDatasetContent", + { it?.requestLine?.contains("/zosmf/restfiles/ds/") ?: false }, + { MockResponse().setBody(retrieveDatasetContentResp ?: "") } + ) + + + val downloadFileDecl = spyk( + DownloadDatasetStep("test", "TEST.IJMP.DATASET1") + ) +// downloadFileDecl.setVol("TESTVOL") + downloadFileDecl.setReturnEtag(false) + downloadFileDecl.perform( + build, + launcher, + taskListener, + zosConnection + ) + + assertSoftly { isDownloadDatasetStarted shouldBe true } + assertSoftly { isDownloaded shouldBe true } + } + + should("perform DownloadDatasetStep operation to download library member") { + var isDownloadDatasetStarted = false + var isDownloaded = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Downloading dataset")) { + isDownloadDatasetStarted = true + } else if (firstArg().contains("has been downloaded successfully")) { + isDownloaded = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_listDataSets", + { it?.requestLine?.contains("zosmf/restfiles/ds?dslevel") ?: false }, + { MockResponse().setBody(responseDispatcher.readMockJson("listDataSets") ?: "") } + ) + val retrieveDatasetContentResp = javaClass.classLoader.getResource("mock/retrieveDatasetContentResponse.txt")?.readText() + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_retrieveDatasetContent", + { it?.requestLine?.contains("/zosmf/restfiles/ds/") ?: false }, + { MockResponse().setBody(retrieveDatasetContentResp ?: "") } + ) + + val downloadFileDecl = spyk( + DownloadDatasetStep("test", "TEST(TEST)") + ) + downloadFileDecl.setVol("TEST") + downloadFileDecl.setReturnEtag(false) + downloadFileDecl.perform( + build, + launcher, + taskListener, + zosConnection + ) + + assertSoftly { isDownloadDatasetStarted shouldBe true } + assertSoftly { isDownloaded shouldBe true } + } + + should("throw an AbortException as there is no such dataset") { + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers {} + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_listDataSets", + { it?.requestLine?.contains("zosmf/restfiles/ds?dslevel") ?: false }, + { MockResponse().setBody(responseDispatcher.readMockJson("emptyDataSetsList") ?: "") } + ) + + val downloadFileDecl = spyk( + DownloadDatasetStep("test", "TEST") + ) + downloadFileDecl.setVol("TEST") + downloadFileDecl.setReturnEtag(false) + shouldThrow { + downloadFileDecl.perform( + build, + launcher, + taskListener, + zosConnection + ) + } + + } + + + + } + + val descriptor = DownloadDatasetStep.DescriptorImpl() + context("classic/steps module: DownloadDatasetStepDescriptor") { + + should("validate dataset name using doCheckDsn") { + val validDsn = "test.test.test" + val invalidDsn = "INVALID_DATASET@" + + descriptor.doCheckDsn(validDsn) shouldBe FormValidation.ok() + descriptor.doCheckDsn(invalidDsn) shouldBe FormValidation.warning(Messages.zdevops_dataset_name_is_invalid_validation()) + descriptor.doCheckDsn("") shouldBe FormValidation.error(Messages.zdevops_value_must_not_be_empty_validation()) + } + + should("validate volume name using doCheckVol") { + val validVol = "VOLUME" + val invalidVol = "INVALID_VOLUME_NAME" + + descriptor.doCheckVol(validVol) shouldBe FormValidation.ok() + descriptor.doCheckVol(invalidVol) shouldBe FormValidation.warning(Messages.zdevops_volume_name_is_invalid_validation()) + } + } + +}) diff --git a/src/test/kotlin/org/zowe/zdevops/classic/steps/PerformTsoCommandStepSpec.kt b/src/test/kotlin/org/zowe/zdevops/classic/steps/PerformTsoCommandStepSpec.kt new file mode 100644 index 0000000..ff95f15 --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/classic/steps/PerformTsoCommandStepSpec.kt @@ -0,0 +1,177 @@ +/* + * 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 + */ + +package org.zowe.zdevops.classic.steps + +import hudson.model.Item +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.fail +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.engine.spec.tempdir +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.MOCK_SERVER_HOST +import org.zowe.zdevops.MockResponseDispatcher +import org.zowe.zdevops.MockServerFactory +import java.io.File +import java.io.PrintStream + +class PerformTsoCommandStepSpec : ShouldSpec({ + lateinit var mockServer: MockWebServer + lateinit var responseDispatcher: MockResponseDispatcher + val mockServerFactory = MockServerFactory() + + beforeSpec { + mockServer = mockServerFactory.startMockServer(MOCK_SERVER_HOST) + responseDispatcher = mockServerFactory.responseDispatcher + } + afterSpec { + mockServerFactory.stopMockServer() + } + context("classic/steps module: PerformTsoCommandStep") { + val trashDir = tempdir() + val itemGroup = object : TestItemGroup() { + override fun getRootDirFor(child: Item?): File { + return trashDir + } + } + val project = TestProject(itemGroup, "test") + val virtualChannel = TestVirtualChannel() + val build = TestBuild(project) + val zosConnection = ZOSConnection(mockServer.hostName, mockServer.port.toString(), "test", "test", "https") + + afterEach { + responseDispatcher.removeAllEndpoints() + } + should("perform PerformTsoCommandStep operation") { + var isPreExecuteStage = false + var isCommandExecuted = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Issuing command")) { + isPreExecuteStage = true + } else if (firstArg().contains("The command has been successfully executed")) { + isCommandExecuted = true + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + + responseDispatcher.injectEndpoint( + this.testCase.name.testName, + { it?.requestLine?.contains("POST /zosmf/tsoApp/tso") ?: false }, + { MockResponse() + .setResponseCode(200) + .setBody(responseDispatcher.readMockJson("startTsoResponse") ?: "") } + ) + responseDispatcher.injectEndpoint( + this.testCase.name.testName, + { it?.requestLine?.contains("GET /zosmf/tsoApp/tso/") ?: false }, + { MockResponse() + .setResponseCode(200) + .setBody(responseDispatcher.readMockJson("getTsoResponse") ?: "") } + ) + responseDispatcher.injectEndpoint( + this.testCase.name.testName, + { it?.requestLine?.contains("PUT /zosmf/tsoApp/tso/") ?: false }, + { MockResponse() + .setResponseCode(200) + .setBody(responseDispatcher.readMockJson("sendTsoResponse") ?: "") } + ) + responseDispatcher.injectEndpoint( + this.testCase.name.testName, + { it?.requestLine?.contains("DELETE /zosmf/tsoApp/tso/") ?: false }, + { MockResponse() + .setResponseCode(200) + .setBody(responseDispatcher.readMockJson("endTsoResponse") ?: "") } + ) + + val performTsoCommandInst = spyk( + PerformTsoCommandStep( + "test", + "test", + "TIME" + ) + ) + performTsoCommandInst.perform( + build, + launcher, + taskListener, + zosConnection + ) + assertSoftly { isPreExecuteStage shouldBe true } + assertSoftly { isCommandExecuted shouldBe true } + } + should("fail PerformTsoCommand operation") { + var isPreExecuteStage = false + var isExecuteCommandFailLogged = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Issuing command")) { + isPreExecuteStage = true + } else if (firstArg().contains("TSO command execution failed")) { + isExecuteCommandFailLogged = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + + responseDispatcher.injectEndpoint( + this.testCase.name.testName, + { it?.requestLine?.contains("zosmf/tsoApp/tso/") ?: false }, + { MockResponse() + .setResponseCode(500) + .setBody("") } + ) + + val performTsoCommandStepInst = spyk( + PerformTsoCommandStep( + "test", + "test", + "123", + ) + ) + runCatching { + performTsoCommandStepInst.perform( + build, + launcher, + taskListener, + zosConnection + ) + } + .onSuccess { + fail("The 'perform' operation will fail") + } + .onFailure { + assertSoftly { isPreExecuteStage shouldBe true } + assertSoftly { isExecuteCommandFailLogged shouldBe true } + } + } + } +}) \ No newline at end of file diff --git a/src/test/kotlin/org/zowe/zdevops/classic/steps/SubmitJobStepSpec.kt b/src/test/kotlin/org/zowe/zdevops/classic/steps/SubmitJobStepSpec.kt new file mode 100644 index 0000000..63b5a17 --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/classic/steps/SubmitJobStepSpec.kt @@ -0,0 +1,249 @@ +/* + * 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 + */ + +package org.zowe.zdevops.classic.steps + +import hudson.EnvVars +import hudson.FilePath +import hudson.model.Executor +import hudson.model.Item +import hudson.model.TaskListener +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.fail +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.engine.spec.tempdir +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.MOCK_SERVER_HOST +import org.zowe.zdevops.MockResponseDispatcher +import org.zowe.zdevops.MockServerFactory +import java.io.File +import java.io.PrintStream + +class SubmitJobStepSpec : ShouldSpec({ + lateinit var mockServer: MockWebServer + lateinit var responseDispatcher: MockResponseDispatcher + val mockServerFactory = MockServerFactory() + + beforeSpec { + mockServer = mockServerFactory.startMockServer(MOCK_SERVER_HOST) + responseDispatcher = mockServerFactory.responseDispatcher + } + afterSpec { + mockServerFactory.stopMockServer() + } + context("classic/steps module: SubmitJobStep") { + val trashDir = tempdir() + val itemGroup = object : TestItemGroup() { + override fun getRootDirFor(child: Item?): File { + return trashDir + } + } + val project = TestProject(itemGroup, "test") + val virtualChannel = TestVirtualChannel() + val build = object:TestBuild(project) { + override fun getExecutor(): Executor { + val mockInstance = mockk() + val mockDir = tempdir() + every { mockInstance.currentWorkspace } returns FilePath(virtualChannel, mockDir.absolutePath) + return mockInstance + } + + override fun getEnvironment(log: TaskListener): EnvVars { + val env: EnvVars = EnvVars() + env["BUILD_URL"] = "" + return env + } + } + val zosConnection = ZOSConnection(mockServer.hostName, mockServer.port.toString(), "test", "test", "https") + + afterEach { + responseDispatcher.removeAllEndpoints() + } + should("perform SubmitJobStep operation") { + var isJobSubmitting = false + var isJobSubmitted = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Submitting a JOB")) { + isJobSubmitting = true + } else if (firstArg().contains("JOB submitted successfully")) { + isJobSubmitted = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + + responseDispatcher.injectEndpoint( + this.testCase.name.testName, + { it?.requestLine?.contains("zosmf/restjobs/jobs") ?: false }, + { MockResponse().setBody(responseDispatcher.readMockJson("submitJobResponse") ?: "") } + ) + + val submitJobStepInst = spyk( + SubmitJobStep( + "test", + "test", + sync = false, + checkRC = false + ) + ) + submitJobStepInst.perform( + build, + launcher, + taskListener, + zosConnection + ) + assertSoftly { isJobSubmitting shouldBe true } + assertSoftly { isJobSubmitted shouldBe true } + } + should("perform SubmitJobStep operation without spool files") { + var isJobSubmitting = false + var isJobSubmitted = false + var isWaitingJobFinish = false + var isJobFinished = false + var isDownloadingExecutionLog = false + var isNoSpoolLogs = false + + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Submitting a JOB")) { + isJobSubmitting = true + } else if (firstArg().contains("JOB submitted successfully")) { + isJobSubmitted = true + } else if (firstArg().contains("Waiting for a JOB finish")) { + isWaitingJobFinish = true + } else if (firstArg().contains("JOB was finished. Returned code")) { + isJobFinished = true + } else if (firstArg().contains("Downloading execution log")) { + isDownloadingExecutionLog = true + } else if (firstArg().contains("There are no logs for")) { + isNoSpoolLogs = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_submitJob", + { it?.requestLine?.matches(Regex("PUT /zosmf/restjobs/jobs HTTP/.*")) == true }, + { MockResponse().setBody(responseDispatcher.readMockJson("submitJobResponse") ?: "") } + ) + val getJobsRegex = Regex("GET /zosmf/restjobs/jobs/(?!.*files).* HTTP/.*") + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_getJob", + { it?.requestLine?.matches(getJobsRegex) == true }, + { MockResponse().setBody(responseDispatcher.readMockJson("getJobResponse") ?: "") } + ) + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_getJobSpoolFiles", + { it?.requestLine?.matches(Regex("GET /zosmf/restjobs/jobs/.*/files HTTP/.*")) == true }, + { MockResponse().setBody("[]") } + ) + + val submitJobStepInst = spyk( + SubmitJobStep( + "test", + "test", + sync = true, + checkRC = true + ) + ) + submitJobStepInst.perform( + build, + launcher, + taskListener, + zosConnection + ) + + assertSoftly { isJobSubmitting shouldBe true } + assertSoftly { isJobSubmitted shouldBe true } + assertSoftly { isWaitingJobFinish shouldBe true } + assertSoftly { isJobFinished shouldBe true } + assertSoftly { isDownloadingExecutionLog shouldBe true } + assertSoftly { isNoSpoolLogs shouldBe true } + } + should("fail SubmitJobStep operation") { + var isJobSubmitting = false + var isJobFailLogged = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Submitting a JOB")) { + isJobSubmitting = true + } else if (firstArg().contains("Job input was not recognized by system as a job")) { + isJobFailLogged = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + + responseDispatcher.injectEndpoint( + this.testCase.name.testName, + { it?.requestLine?.contains("zosmf/restjobs/jobs") ?: false }, + { MockResponse() + .setResponseCode(500) + .setBody(responseDispatcher.readMockJson("submitJobFailResponse") ?: "") } + ) + + val submitJobStepInst = spyk( + SubmitJobStep( + "test", + "test", + sync = false, + checkRC = false + ) + ) + runCatching { + submitJobStepInst.perform( + build, + launcher, + taskListener, + zosConnection + ) + } + .onSuccess { + fail("The 'perform' operation will fail") + } + .onFailure { + assertSoftly { isJobSubmitting shouldBe true } + assertSoftly { isJobFailLogged shouldBe true } + } + } + } +}) diff --git a/src/test/kotlin/org/zowe/zdevops/classic/steps/WriteFileToDatasetStepSpec.kt b/src/test/kotlin/org/zowe/zdevops/classic/steps/WriteFileToDatasetStepSpec.kt new file mode 100644 index 0000000..65517ac --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/classic/steps/WriteFileToDatasetStepSpec.kt @@ -0,0 +1,139 @@ +/* + * 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 + */ + +package org.zowe.zdevops.classic.steps + +import hudson.FilePath +import hudson.model.Executor +import hudson.model.Item +import hudson.util.FormValidation +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.fail +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.engine.spec.tempdir +import io.kotest.matchers.shouldBe +import io.mockk.* +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.MOCK_SERVER_HOST +import org.zowe.zdevops.Messages +import org.zowe.zdevops.MockResponseDispatcher +import org.zowe.zdevops.MockServerFactory +import java.io.File +import java.io.PrintStream +import java.nio.file.Paths + +class WriteFileToDatasetStepSpec : ShouldSpec({ + lateinit var mockServer: MockWebServer + lateinit var responseDispatcher: MockResponseDispatcher + val mockServerFactory = MockServerFactory() + + beforeSpec { + mockServer = mockServerFactory.startMockServer(MOCK_SERVER_HOST) + responseDispatcher = mockServerFactory.responseDispatcher + } + afterSpec { + mockServerFactory.stopMockServer() + clearAllMocks() + } + context("classic/steps module: WriteFileToDatasetStep") { + val virtualChannel = TestVirtualChannel() + val zosConnection = ZOSConnection(mockServer.hostName, mockServer.port.toString(), "test", "test", "https") + val rootDir = Paths.get("").toAbsolutePath().toString() + val trashDir = tempdir() + val itemGroup = object : TestItemGroup() { + override fun getRootDirFor(child: Item?): File { + return trashDir + } + } + val project = TestProject(itemGroup, "test") + val build = object:TestBuild(project) { + override fun getExecutor(): Executor { + val mockInstance = mockk() + val mockDir = Paths.get(rootDir, "src", "test", "resources", "mock").toString() + every { mockInstance.currentWorkspace } returns FilePath(virtualChannel, mockDir) + return mockInstance + } + } + + afterEach { + responseDispatcher.removeAllEndpoints() + } + should("perform WriteFileToDatasetStep operation to write a file to a dataset") { + var isWritingToDataset = false + var isWritten = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Writing to dataset")) { + isWritingToDataset = true + } else if (firstArg().contains("Data has been written to dataset")) { + isWritten = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_listDataSets", + { it?.requestLine?.contains("zosmf/restfiles/ds") ?: false }, + { MockResponse().setBody(responseDispatcher.readMockJson("listDataSets") ?: "") } + ) + + val writeFileToDatasetDecl = spyk( + WriteFileToDatasetStep("test", "TEST.IJMP.DATASET1", "workspace") + ) + writeFileToDatasetDecl.setWorkspacePath("test_file.txt") + writeFileToDatasetDecl.perform( + build, + launcher, + taskListener, + zosConnection + ) + assertSoftly { isWritingToDataset shouldBe true } + assertSoftly { isWritten shouldBe true } + } + } + + val descriptor = WriteFileToDatasetStep.DescriptorImpl() + context("classic/steps module: WriteFileToDatasetStep.DescriptorImpl") { + + should("validate dataset name") { + descriptor.doCheckDsn("") shouldBe FormValidation.error(Messages.zdevops_value_must_not_be_empty_validation()) + descriptor.doCheckDsn("MY_DATASET") shouldBe FormValidation.error(Messages.zdevops_dataset_name_is_invalid_validation()) + } + + should("validate file option") { + descriptor.doCheckFileOption("") shouldBe FormValidation.error(Messages.zdevops_classic_write_options_required()) + descriptor.doCheckFileOption(descriptor.localFileOption) shouldBe FormValidation.ok() + } + + should("validate local file path") { + descriptor.doCheckLocalFilePath("", fileOption = descriptor.localFileOption) shouldBe FormValidation.error( + Messages.zdevops_value_must_not_be_empty_validation()) + descriptor.doCheckLocalFilePath("D:\\file.txt", fileOption = descriptor.localFileOption) shouldBe FormValidation.ok() + descriptor.doCheckLocalFilePath("", fileOption = descriptor.chooseFileOption) shouldBe FormValidation.ok() + } + + should("validate workspace file path") { + descriptor.doCheckWorkspacePath("", fileOption = descriptor.workspaceFileOption) shouldBe FormValidation.error( + Messages.zdevops_value_must_not_be_empty_validation()) + descriptor.doCheckWorkspacePath("D:\\file.txt", fileOption = descriptor.workspaceFileOption) shouldBe FormValidation.ok() + descriptor.doCheckWorkspacePath("", fileOption = descriptor.chooseFileOption) shouldBe FormValidation.ok() + } + } +}) diff --git a/src/test/kotlin/org/zowe/zdevops/classic/steps/WriteFileToFileStepSpec.kt b/src/test/kotlin/org/zowe/zdevops/classic/steps/WriteFileToFileStepSpec.kt new file mode 100644 index 0000000..18e3843 --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/classic/steps/WriteFileToFileStepSpec.kt @@ -0,0 +1,135 @@ +/* + * 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 + */ + +package org.zowe.zdevops.classic.steps + +import hudson.FilePath +import hudson.model.Executor +import hudson.model.Item +import hudson.util.FormValidation +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.fail +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.engine.spec.tempdir +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.MOCK_SERVER_HOST +import org.zowe.zdevops.Messages +import org.zowe.zdevops.MockResponseDispatcher +import org.zowe.zdevops.MockServerFactory +import java.io.File +import java.io.PrintStream +import java.nio.file.Paths + +class WriteFileToFileStepSpec : ShouldSpec({ + lateinit var mockServer: MockWebServer + lateinit var responseDispatcher: MockResponseDispatcher + val mockServerFactory = MockServerFactory() + + beforeSpec { + mockServer = mockServerFactory.startMockServer(MOCK_SERVER_HOST) + responseDispatcher = mockServerFactory.responseDispatcher + } + afterSpec { + mockServerFactory.stopMockServer() + } + context("classic/steps module: WriteFileToFileStep") { + val virtualChannel = TestVirtualChannel() + val zosConnection = ZOSConnection(mockServer.hostName, mockServer.port.toString(), "test", "test", "https") + val rootDir = Paths.get("").toAbsolutePath().toString() + val trashDir = tempdir() + val itemGroup = object : TestItemGroup() { + override fun getRootDirFor(child: Item?): File { + return trashDir + } + } + val project = TestProject(itemGroup, "test") + val build = object:TestBuild(project) { + override fun getExecutor(): Executor { + val mockInstance = mockk() + val mockDir = Paths.get(rootDir, "src", "test", "resources", "mock").toString() + every { mockInstance.currentWorkspace } returns FilePath(virtualChannel, mockDir) + return mockInstance + } + } + + afterEach { + responseDispatcher.removeAllEndpoints() + } + should("perform WriteFileToFileStep operation to write a local file to a USS file") { + var isWritingToFile = false + var isWritten = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Writing to Unix file")) { + isWritingToFile = true + } else if (firstArg().contains("Data has been written to Unix file")) { + isWritten = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_UssFile", + { it?.requestLine?.contains("zosmf/restfiles/fs") ?: false }, + { MockResponse().setBody("") } + ) + + val writeFileToFileDecl = spyk( + WriteFileToFileStep("test", "/u/TEST/test.txt", false, "workspace") + ) + writeFileToFileDecl.setWorkspacePath("test_file.txt") + writeFileToFileDecl.perform( + build, + launcher, + taskListener, + zosConnection + ) + assertSoftly { isWritingToFile shouldBe true } + assertSoftly { isWritten shouldBe true } + } + } + + val descriptor = WriteFileToFileStep.DescriptorImpl() + context("classic/steps module: WriteFileToFileStep.DescriptorImpl") { + + should("validate file option") { + descriptor.doCheckFileOption("") shouldBe FormValidation.error(Messages.zdevops_classic_write_options_required()) + descriptor.doCheckFileOption(descriptor.localFileOption) shouldBe FormValidation.ok() + } + + should("validate local file path") { + descriptor.doCheckLocalFilePath("", fileOption = descriptor.localFileOption) shouldBe FormValidation.error( + Messages.zdevops_value_must_not_be_empty_validation()) + descriptor.doCheckLocalFilePath("D:\\file.txt", fileOption = descriptor.localFileOption) shouldBe FormValidation.ok() + descriptor.doCheckLocalFilePath("", fileOption = descriptor.chooseFileOption) shouldBe FormValidation.ok() + } + + should("validate workspace file path") { + descriptor.doCheckWorkspacePath("", fileOption = descriptor.workspaceFileOption) shouldBe FormValidation.error( + Messages.zdevops_value_must_not_be_empty_validation()) + descriptor.doCheckWorkspacePath("D:\\file.txt", fileOption = descriptor.workspaceFileOption) shouldBe FormValidation.ok() + descriptor.doCheckWorkspacePath("", fileOption = descriptor.chooseFileOption) shouldBe FormValidation.ok() + } + } +}) \ No newline at end of file diff --git a/src/test/kotlin/org/zowe/zdevops/classic/steps/WriteFileToMemberStepSpec.kt b/src/test/kotlin/org/zowe/zdevops/classic/steps/WriteFileToMemberStepSpec.kt new file mode 100644 index 0000000..160c276 --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/classic/steps/WriteFileToMemberStepSpec.kt @@ -0,0 +1,136 @@ +/* + * 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 + */ + +package org.zowe.zdevops.classic.steps + +import hudson.model.Item +import hudson.util.FormValidation +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.fail +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.engine.spec.tempdir +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.MOCK_SERVER_HOST +import org.zowe.zdevops.Messages +import org.zowe.zdevops.MockResponseDispatcher +import org.zowe.zdevops.MockServerFactory +import java.io.File +import java.io.PrintStream +import java.nio.file.Paths + +class WriteFileToMemberStepSpec : ShouldSpec({ + lateinit var mockServer: MockWebServer + lateinit var responseDispatcher: MockResponseDispatcher + val mockServerFactory = MockServerFactory() + + beforeSpec { + mockServer = mockServerFactory.startMockServer(MOCK_SERVER_HOST) + responseDispatcher = mockServerFactory.responseDispatcher + } + afterSpec { + mockServerFactory.stopMockServer() + } + context("classic/steps module: WriteFileToMemberStep") { + val virtualChannel = TestVirtualChannel() + val zosConnection = ZOSConnection(mockServer.hostName, mockServer.port.toString(), "test", "test", "https") + val rootDir = Paths.get("").toAbsolutePath().toString() + val trashDir = tempdir() + val mockDir = Paths.get(rootDir, "src", "test", "resources", "mock").toString() + val itemGroup = object : TestItemGroup() { + override fun getRootDirFor(child: Item?): File { + return trashDir + } + } + val project = TestProject(itemGroup, "test") + val build = TestBuild(project) + + afterEach { + responseDispatcher.removeAllEndpoints() + } + should("perform WriteFileToMemberStep operation to write a file to a member") { + var isWritingToDataset = false + var isWritten = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Writing to dataset")) { + isWritingToDataset = true + } else if (firstArg().contains("Data has been written to dataset")) { + isWritten = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_listDataSets", + { it?.requestLine?.contains("zosmf/restfiles/ds") ?: false }, + { MockResponse().setBody(responseDispatcher.readMockJson("listDataSets") ?: "") } + ) + + val writeFileToMemberDecl = spyk( + WriteFileToMemberStep("test", "TEST.IJMP.DATASET1", "#1", "local") + ) + writeFileToMemberDecl.setLocalFilePath(mockDir + "/test_file.txt") + writeFileToMemberDecl.perform( + build, + launcher, + taskListener, + zosConnection + ) + assertSoftly { isWritingToDataset shouldBe true } + assertSoftly { isWritten shouldBe true } + } + } + + val descriptor = WriteFileToMemberStep.DescriptorImpl() + context("classic/steps module: WriteFileToMemberStep.DescriptorImpl") { + + should("validate dataset name") { + descriptor.doCheckDsn("") shouldBe FormValidation.error(Messages.zdevops_value_must_not_be_empty_validation()) + descriptor.doCheckDsn("MY_DATASET") shouldBe FormValidation.error(Messages.zdevops_dataset_name_is_invalid_validation()) + } + + should("validate member name") { + descriptor.doCheckMember("") shouldBe FormValidation.error(Messages.zdevops_value_up_to_eight_in_length_validation()) + descriptor.doCheckMember("@MY_DS") shouldBe FormValidation.warning(Messages.zdevops_member_name_is_invalid_validation()) + descriptor.doCheckMember("DSNAME") shouldBe FormValidation.ok() + } + + should("validate file option") { + descriptor.doCheckFileOption("") shouldBe FormValidation.error(Messages.zdevops_classic_write_options_required()) + descriptor.doCheckFileOption(descriptor.localFileOption) shouldBe FormValidation.ok() + } + + should("validate local file path") { + descriptor.doCheckLocalFilePath("", fileOption = descriptor.localFileOption) shouldBe FormValidation.error(Messages.zdevops_value_must_not_be_empty_validation()) + descriptor.doCheckLocalFilePath("D:\\file.txt", fileOption = descriptor.localFileOption) shouldBe FormValidation.ok() + descriptor.doCheckLocalFilePath("", fileOption = descriptor.chooseFileOption) shouldBe FormValidation.ok() + } + + should("validate workspace file path") { + descriptor.doCheckWorkspacePath("", fileOption = descriptor.workspaceFileOption) shouldBe FormValidation.error(Messages.zdevops_value_must_not_be_empty_validation()) + descriptor.doCheckWorkspacePath("D:\\file.txt", fileOption = descriptor.workspaceFileOption) shouldBe FormValidation.ok() + descriptor.doCheckWorkspacePath("", fileOption = descriptor.chooseFileOption) shouldBe FormValidation.ok() + } + } +}) \ No newline at end of file diff --git a/src/test/kotlin/org/zowe/zdevops/classic/steps/WriteToDatasetStepSpec.kt b/src/test/kotlin/org/zowe/zdevops/classic/steps/WriteToDatasetStepSpec.kt new file mode 100644 index 0000000..09f4e3b --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/classic/steps/WriteToDatasetStepSpec.kt @@ -0,0 +1,102 @@ +/* + * 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 + */ + +package org.zowe.zdevops.classic.steps + +import hudson.model.Item +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.fail +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.engine.spec.tempdir +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.MOCK_SERVER_HOST +import org.zowe.zdevops.MockResponseDispatcher +import org.zowe.zdevops.MockServerFactory +import org.zowe.zdevops.declarative.jobs.TestBuildListener +import org.zowe.zdevops.declarative.jobs.TestItemGroup +import org.zowe.zdevops.declarative.jobs.TestLauncher +import org.zowe.zdevops.declarative.jobs.TestVirtualChannel +import java.io.File +import java.io.PrintStream + +class WriteToDatasetStepSpec : ShouldSpec({ + lateinit var mockServer: MockWebServer + lateinit var responseDispatcher: MockResponseDispatcher + val mockServerFactory = MockServerFactory() + + beforeSpec { + mockServer = mockServerFactory.startMockServer(MOCK_SERVER_HOST) + responseDispatcher = mockServerFactory.responseDispatcher + } + afterSpec { + mockServerFactory.stopMockServer() + } + context("classic/steps module: WriteToDatasetStep") { + val virtualChannel = TestVirtualChannel() + val zosConnection = ZOSConnection(mockServer.hostName, mockServer.port.toString(), "test", "test", "https") + val trashDir = tempdir() + val itemGroup = object : TestItemGroup() { + override fun getRootDirFor(child: Item?): File { + return trashDir + } + } + val project = TestProject(itemGroup, "test") + val build = TestBuild(project) + + afterEach { + responseDispatcher.removeAllEndpoints() + } + should("perform WriteToDatasetStep operation to write text to a dataset") { + var isWritingToDataset = false + var isWritten = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Writing to dataset")) { + isWritingToDataset = true + } else if (firstArg().contains("Data has been written to dataset")) { + isWritten = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_listDataSets", + { it?.requestLine?.contains("zosmf/restfiles/ds") ?: false }, + { MockResponse().setBody(responseDispatcher.readMockJson("listDataSets") ?: "") } + ) + + val writeTextToDatasetDecl = spyk( + WriteToDatasetStep("test", "TEST.IJMP.DATASET1", "TEXT TO WRITE") + ) + writeTextToDatasetDecl.perform( + build, + launcher, + taskListener, + zosConnection + ) + assertSoftly { isWritingToDataset shouldBe true } + assertSoftly { isWritten shouldBe true } + } + } +}) \ No newline at end of file diff --git a/src/test/kotlin/org/zowe/zdevops/classic/steps/WriteToFileStepSpec.kt b/src/test/kotlin/org/zowe/zdevops/classic/steps/WriteToFileStepSpec.kt new file mode 100644 index 0000000..8c27957 --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/classic/steps/WriteToFileStepSpec.kt @@ -0,0 +1,103 @@ +/* + * 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 + */ + +package org.zowe.zdevops.classic.steps + +import hudson.model.Item +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.fail +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.engine.spec.tempdir +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.MOCK_SERVER_HOST +import org.zowe.zdevops.MockResponseDispatcher +import org.zowe.zdevops.MockServerFactory +import org.zowe.zdevops.declarative.jobs.TestBuildListener +import org.zowe.zdevops.declarative.jobs.TestItemGroup +import org.zowe.zdevops.declarative.jobs.TestLauncher +import org.zowe.zdevops.declarative.jobs.TestVirtualChannel +import java.io.File +import java.io.PrintStream + +class WriteToFileStepSpec : ShouldSpec({ + lateinit var mockServer: MockWebServer + lateinit var responseDispatcher: MockResponseDispatcher + val mockServerFactory = MockServerFactory() + + beforeSpec { + mockServer = mockServerFactory.startMockServer(MOCK_SERVER_HOST) + responseDispatcher = mockServerFactory.responseDispatcher + } + afterSpec { + mockServerFactory.stopMockServer() + } + context("classic/steps module: WriteToFileStep") { + val virtualChannel = TestVirtualChannel() + val zosConnection = ZOSConnection(mockServer.hostName, mockServer.port.toString(), "test", "test", "https") + val tempDir = tempdir() + val itemGroup = object : TestItemGroup() { + override fun getRootDirFor(child: Item?): File { + return tempDir + } + } + val project = TestProject(itemGroup, "test") + val build = TestBuild(project) + + afterEach { + responseDispatcher.removeAllEndpoints() + } + should("perform WriteToFileStep operation to write text to a USS file") { + var isWritingToFile = false + var isWritten = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Writing to Unix file")) { + isWritingToFile = true + } else if (firstArg().contains("Data has been written to Unix file")) { + isWritten = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val filePath = "/u/TEST/test.txt" + val launcher = TestLauncher(taskListener, virtualChannel) + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_UssFile", + { it?.requestLine?.contains("zosmf/restfiles/fs${filePath}") ?: false }, + { MockResponse().setBody("") } + ) + + val writeTextToFileDecl = spyk( + WriteToFileStep("test", filePath, "TEXT TO WRITE") + ) + writeTextToFileDecl.perform( + build, + launcher, + taskListener, + zosConnection + ) + assertSoftly { isWritingToFile shouldBe true } + assertSoftly { isWritten shouldBe true } + } + } +}) \ No newline at end of file diff --git a/src/test/kotlin/org/zowe/zdevops/classic/steps/WriteToMemberStepSpec.kt b/src/test/kotlin/org/zowe/zdevops/classic/steps/WriteToMemberStepSpec.kt new file mode 100644 index 0000000..fdac307 --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/classic/steps/WriteToMemberStepSpec.kt @@ -0,0 +1,121 @@ +/* + * 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 + */ + +package org.zowe.zdevops.classic.steps + +import hudson.model.Item +import hudson.util.FormValidation +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.fail +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.engine.spec.tempdir +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.MOCK_SERVER_HOST +import org.zowe.zdevops.Messages +import org.zowe.zdevops.MockResponseDispatcher +import org.zowe.zdevops.MockServerFactory +import java.io.File +import java.io.PrintStream + + +class WriteToMemberStepSpec : ShouldSpec({ + lateinit var mockServer: MockWebServer + lateinit var responseDispatcher: MockResponseDispatcher + val mockServerFactory = MockServerFactory() + + beforeSpec { + mockServer = mockServerFactory.startMockServer(MOCK_SERVER_HOST) + responseDispatcher = mockServerFactory.responseDispatcher + } + afterSpec { + mockServerFactory.stopMockServer() + } + context("classic/steps module: WriteToMemberStep") { + val virtualChannel = TestVirtualChannel() + val zosConnection = ZOSConnection(mockServer.hostName, mockServer.port.toString(), "test", "test", "https") + val trashDir = tempdir() + val itemGroup = object : TestItemGroup() { + override fun getRootDirFor(child: Item?): File { + return trashDir + } + } + val project = TestProject(itemGroup, "test") + val build = TestBuild(project) + + afterEach { + responseDispatcher.removeAllEndpoints() + } + should("perform WriteToMemberStep operation to write text to a member") { + var isWritingToDataset = false + var isWritten = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Writing to dataset")) { + isWritingToDataset = true + } else if (firstArg().contains("Data has been written to dataset")) { + isWritten = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_listDataSets", + { it?.requestLine?.contains("zosmf/restfiles/ds") ?: false }, + { MockResponse().setBody(responseDispatcher.readMockJson("listDataSets") ?: "") } + ) + + val writeTextToDatasetDecl = spyk( + WriteToMemberStep("test", "TEST.IJMP.DATASET1", "#1", "TEXT") + ) + writeTextToDatasetDecl.perform( + build, + launcher, + taskListener, + zosConnection + ) + assertSoftly { isWritingToDataset shouldBe true } + assertSoftly { isWritten shouldBe true } + } + } + + val descriptor = WriteToMemberStep.DescriptorImpl() + context("classic/steps module: WriteToMemberStep.DescriptorImpl") { + + should("validate dataset name") { + descriptor.doCheckDsn("") shouldBe FormValidation.error(Messages.zdevops_value_must_not_be_empty_validation()) + descriptor.doCheckDsn("MY_DATASET") shouldBe FormValidation.error(Messages.zdevops_dataset_name_is_invalid_validation()) + } + + should("validate member name") { + descriptor.doCheckMember("") shouldBe FormValidation.error(Messages.zdevops_value_up_to_eight_in_length_validation()) + descriptor.doCheckMember("@MY_DS") shouldBe FormValidation.warning(Messages.zdevops_member_name_is_invalid_validation()) + descriptor.doCheckMember("DSNAME") shouldBe FormValidation.ok() + } + + should("validate text") { + descriptor.doCheckText("") shouldBe FormValidation.error(Messages.zdevops_value_must_not_be_empty_validation()) + descriptor.doCheckText("text") shouldBe FormValidation.ok() + } + } +}) \ No newline at end of file diff --git a/src/test/kotlin/org/zowe/zdevops/classic/steps/dummyClasses.kt b/src/test/kotlin/org/zowe/zdevops/classic/steps/dummyClasses.kt new file mode 100644 index 0000000..99c1b50 --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/classic/steps/dummyClasses.kt @@ -0,0 +1,141 @@ +/* + * 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 + */ + +package org.zowe.zdevops.classic.steps + +import hudson.FilePath +import hudson.Launcher +import hudson.Proc +import hudson.model.* +import hudson.remoting.Callable +import hudson.remoting.Channel +import hudson.remoting.Future +import hudson.remoting.VirtualChannel +import java.io.File +import java.io.OutputStream +import java.io.PrintStream + +open class TestProject(parent: ItemGroup<*>?, name: String?) : Project(parent, name) { + override fun getBuildClass(): Class { + TODO("Not yet implemented") + } + +} + +open class TestBuild(project: TestProject) : Build(project) { + override fun run() { + TODO("Not yet implemented") + } + +} + +open class TestItemGroup : ItemGroup { + override fun save() { + TODO("Not yet implemented") + } + + override fun getRootDir(): File { + TODO("Not yet implemented") + } + + override fun getDisplayName(): String { + TODO("Not yet implemented") + } + + override fun getFullName(): String { + TODO("Not yet implemented") + } + + override fun getFullDisplayName(): String { + TODO("Not yet implemented") + } + + override fun getItems(): MutableCollection { + TODO("Not yet implemented") + } + + override fun getUrl(): String { + TODO("Not yet implemented") + } + + override fun getUrlChildPrefix(): String { + TODO("Not yet implemented") + } + + override fun getItem(name: String?): Item? { + TODO("Not yet implemented") + } + + override fun onDeleted(item: Item?) { + TODO("Not yet implemented") + } + + override fun getRootDirFor(child: Item?): File { + TODO("Not yet implemented") + } + +} + +open class TestVirtualChannel : VirtualChannel { + override fun call(callable: Callable?): V { + TODO("Not yet implemented") + } + + override fun callAsync(callable: Callable?): Future { + TODO("Not yet implemented") + } + + override fun close() { + TODO("Not yet implemented") + } + + override fun join() { + TODO("Not yet implemented") + } + + override fun join(timeout: Long) { + TODO("Not yet implemented") + } + + override fun export(type: Class?, instance: T?): T? { + TODO("Not yet implemented") + } + + override fun syncLocalIO() { + TODO("Not yet implemented") + } + +} + +open class TestBuildListener : BuildListener { + override fun getLogger(): PrintStream { + TODO("Not yet implemented") + } + +} + +open class TestLauncher(taskListener: TaskListener, virtualChannel: VirtualChannel) : Launcher(taskListener, virtualChannel) { + override fun launch(starter: ProcStarter): Proc { + TODO("Not yet implemented") + } + + override fun launchChannel( + cmd: Array, + out: OutputStream, + workDir: FilePath?, + envVars: MutableMap + ): Channel { + TODO("Not yet implemented") + } + + override fun kill(modelEnvVars: MutableMap?) { + TODO("Not yet implemented") + } +} diff --git a/src/test/kotlin/org/zowe/zdevops/declarative/jobs/AllocateDatasetDeclarativeSpec.kt b/src/test/kotlin/org/zowe/zdevops/declarative/jobs/AllocateDatasetDeclarativeSpec.kt new file mode 100644 index 0000000..3e260d4 --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/declarative/jobs/AllocateDatasetDeclarativeSpec.kt @@ -0,0 +1,111 @@ +/* + * 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 + */ + +package org.zowe.zdevops.declarative.jobs + +import hudson.EnvVars +import hudson.FilePath +import hudson.model.Item +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.fail +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.engine.spec.tempdir +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.zowe.kotlinsdk.DatasetOrganization +import org.zowe.kotlinsdk.RecordFormat +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.MOCK_SERVER_HOST +import org.zowe.zdevops.MockResponseDispatcher +import org.zowe.zdevops.MockServerFactory +import java.io.File +import java.io.PrintStream +import java.nio.file.Paths + +class AllocateDatasetDeclarativeSpec : ShouldSpec({ + lateinit var mockServer: MockWebServer + lateinit var responseDispatcher: MockResponseDispatcher + val mockServerFactory = MockServerFactory() + + beforeSpec { + mockServer = mockServerFactory.startMockServer(MOCK_SERVER_HOST) + responseDispatcher = mockServerFactory.responseDispatcher + } + afterSpec { + mockServerFactory.stopMockServer() + } + context("declarative/jobs module: AllocateDatasetDeclarative") { + val virtualChannel = TestVirtualChannel() + val zosConnection = ZOSConnection(mockServer.hostName, mockServer.port.toString(), "test", "test", "https") + val rootDir = Paths.get("").toAbsolutePath().toString() + val trashDir = tempdir() + val itemGroup = object : TestItemGroup() { + override fun getRootDirFor(child: Item?): File { + return trashDir + } + } + val job = TestJob(itemGroup, "test") + val run = TestRun(job) + val mockDir = Paths.get(rootDir, "src", "test", "resources", "mock", "here").toString() + val workspace = FilePath(File(mockDir)) + val env = EnvVars() + + afterEach { + responseDispatcher.removeAllEndpoints() + } + should("perform AllocateDatasetDeclarative operation to allocate a PDS dataset") { + var isAllocatingDS = false + var isAllocatedSucc = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Allocating dataset")) { + isAllocatingDS = true + } else if (firstArg().contains("has been allocated successfully")) { + isAllocatedSucc = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + + responseDispatcher.injectEndpoint( + this.testCase.name.testName, + { it?.requestLine?.contains("/zosmf/restfiles/ds/") ?: false }, + { MockResponse().setBody("{}") } + ) + + val allocateDSDecl = spyk( + AllocateDatasetDeclarative("TEST.IJMP.DATASET1", DatasetOrganization.PO, 1, 1, RecordFormat.F) + ) + allocateDSDecl.perform( + run, + workspace, + env, + launcher, + taskListener, + zosConnection + ) + + assertSoftly { isAllocatingDS shouldBe true } + assertSoftly { isAllocatedSucc shouldBe true } + } + } +}) diff --git a/src/test/kotlin/org/zowe/zdevops/declarative/jobs/DeleteDatasetDeclarativeSpec.kt b/src/test/kotlin/org/zowe/zdevops/declarative/jobs/DeleteDatasetDeclarativeSpec.kt new file mode 100644 index 0000000..e3dab24 --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/declarative/jobs/DeleteDatasetDeclarativeSpec.kt @@ -0,0 +1,233 @@ +/* + * 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 + */ + +package org.zowe.zdevops.declarative.jobs + +import hudson.EnvVars +import hudson.FilePath +import hudson.model.Item +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.fail +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.engine.spec.tempdir +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.MOCK_SERVER_HOST +import org.zowe.zdevops.MockResponseDispatcher +import org.zowe.zdevops.MockServerFactory +import java.io.File +import java.io.PrintStream + +class DeleteDatasetDeclarativeSpec : ShouldSpec({ + lateinit var mockServer: MockWebServer + lateinit var responseDispatcher: MockResponseDispatcher + val mockServerFactory = MockServerFactory() + + beforeSpec { + mockServer = mockServerFactory.startMockServer(MOCK_SERVER_HOST) + responseDispatcher = mockServerFactory.responseDispatcher + } + afterSpec { + mockServerFactory.stopMockServer() + } + context("declarative/jobs module: DeleteDatasetDeclarative") { + val virtualChannel = TestVirtualChannel() + val zosConnection = ZOSConnection(mockServer.hostName, mockServer.port.toString(), "test", "test", "https") + val trashDir = tempdir() + val itemGroup = object : TestItemGroup() { + override fun getRootDirFor(child: Item?): File { + return trashDir + } + } + val job = TestJob(itemGroup, "test") + val run = TestRun(job) + val workspace = FilePath(File("")) + val env = EnvVars() + + afterEach { + responseDispatcher.removeAllEndpoints() + } + should("fails to perform DeleteDatasetDeclarative operation as no dataset name is provided") { + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + fail("Unexpected logger message: ${firstArg()}") + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + + responseDispatcher.injectEndpoint( + this.testCase.name.testName, + { it?.requestLine?.contains("zosmf/restjobs/jobs") ?: false }, + { MockResponse().setBody(responseDispatcher.readMockJson("submitJobResponse") ?: "") } + ) + + val deleteDatasetDecl = spyk( + DeleteDatasetDeclarative() + ) + runCatching { + deleteDatasetDecl.perform( + run, + workspace, + env, + launcher, + taskListener, + zosConnection + ) + } + .onSuccess { fail("The function should throw an error") } + .onFailure { + assertSoftly { it.message shouldContain "Unable to delete: no dsn keyword present" } + } + + } + should("perform DeleteDatasetDeclarative operation that deletes a dataset") { + var isDeletingDataset = false + var isSuccessfullyDeleted = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Deleting dataset")) { + isDeletingDataset = true + } else if (firstArg().contains("Successfully deleted")) { + isSuccessfullyDeleted = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + + responseDispatcher.injectEndpoint( + this.testCase.name.testName, + { it?.requestLine?.contains(Regex("DELETE /zosmf/restfiles/ds/test HTTP/.*")) ?: false }, + { MockResponse().setBody("{}") } + ) + + val deleteDatasetDecl = spyk( + DeleteDatasetDeclarative() + ) + deleteDatasetDecl.setDsn("test") + deleteDatasetDecl.perform( + run, + workspace, + env, + launcher, + taskListener, + zosConnection + ) + + assertSoftly { isDeletingDataset shouldBe true } + assertSoftly { isSuccessfullyDeleted shouldBe true } + } + should("perform DeleteDatasetDeclarative operation that deletes a member") { + var isDeletingMember = false + var isSuccessfullyDeleted = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Deleting member")) { + isDeletingMember = true + } else if (firstArg().contains("Successfully deleted")) { + isSuccessfullyDeleted = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + + responseDispatcher.injectEndpoint( + this.testCase.name.testName, + { it?.requestLine?.contains("DELETE /zosmf/restfiles/ds/test(test)") ?: false }, + { MockResponse().setBody("{}") } + ) + + val deleteDatasetDecl = spyk( + DeleteDatasetDeclarative() + ) + deleteDatasetDecl.setDsn("test") + deleteDatasetDecl.setMember("test") + deleteDatasetDecl.perform( + run, + workspace, + env, + launcher, + taskListener, + zosConnection + ) + + assertSoftly { isDeletingMember shouldBe true } + assertSoftly { isSuccessfullyDeleted shouldBe true } + } + should("fails to perform DeleteDatasetDeclarative operation as the member name is not valid") { + var isDeletingMember = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Deleting member")) { + isDeletingMember = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + + val deleteDatasetDecl = spyk( + DeleteDatasetDeclarative() + ) + deleteDatasetDecl.setDsn("test") + deleteDatasetDecl.setMember("testlongmembername") + runCatching { + deleteDatasetDecl.perform( + run, + workspace, + env, + launcher, + taskListener, + zosConnection + ) + } + .onSuccess { fail("The function should throw an error") } + .onFailure { + assertSoftly { it.message shouldContain "Invalid member name: must be 1-8 characters" } + } + + assertSoftly { isDeletingMember shouldBe true } + } + } +}) diff --git a/src/test/kotlin/org/zowe/zdevops/declarative/jobs/DownloadFileDeclarativeSpec.kt b/src/test/kotlin/org/zowe/zdevops/declarative/jobs/DownloadFileDeclarativeSpec.kt new file mode 100644 index 0000000..0d97e72 --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/declarative/jobs/DownloadFileDeclarativeSpec.kt @@ -0,0 +1,116 @@ +/* + * 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 + */ + +package org.zowe.zdevops.declarative.jobs + +import hudson.EnvVars +import hudson.FilePath +import hudson.model.Item +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.fail +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.engine.spec.tempdir +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.MOCK_SERVER_HOST +import org.zowe.zdevops.MockResponseDispatcher +import org.zowe.zdevops.MockServerFactory +import java.io.File +import java.io.PrintStream +import java.nio.file.Paths + +class DownloadFileDeclarativeSpec : ShouldSpec({ + lateinit var mockServer: MockWebServer + lateinit var responseDispatcher: MockResponseDispatcher + val mockServerFactory = MockServerFactory() + + beforeSpec { + mockServer = mockServerFactory.startMockServer(MOCK_SERVER_HOST) + responseDispatcher = mockServerFactory.responseDispatcher + } + afterSpec { + mockServerFactory.stopMockServer() + } + context("declarative/jobs module: DownloadFileDeclarative") { + val virtualChannel = TestVirtualChannel() + val zosConnection = ZOSConnection(mockServer.hostName, mockServer.port.toString(), "test", "test", "https") + val trashDir = tempdir() + val itemGroup = object : TestItemGroup() { + override fun getRootDirFor(child: Item?): File { + return trashDir + } + } + val job = TestJob(itemGroup, "test") + val run = TestRun(job) + val trashDirWithInternal = Paths.get(trashDir.absolutePath, "test_name").toString() + val workspace = FilePath(File(trashDirWithInternal)) + val env = EnvVars() + + afterEach { + responseDispatcher.removeAllEndpoints() + } + should("perform DownloadFileDeclarative operation to download sequential dataset") { + var isDownloadDatasetStarted = false + var isDownloaded = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Downloading dataset")) { + isDownloadDatasetStarted = true + } else if (firstArg().contains("has been downloaded successfully")) { + isDownloaded = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_listDataSets", + { it?.requestLine?.contains("zosmf/restfiles/ds?dslevel") ?: false }, + { MockResponse().setBody(responseDispatcher.readMockJson("listDataSetsPS") ?: "") } + ) + val retrieveDatasetContentResp = javaClass.classLoader.getResource("mock/retrieveDatasetContentResponse.txt")?.readText() + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_retrieveDatasetContent", + { it?.requestLine?.contains("/zosmf/restfiles/ds/") ?: false }, + { MockResponse().setBody(retrieveDatasetContentResp ?: "") } + ) + + val downloadFileDecl = spyk( + DownloadFileDeclarative("TEST") + ) + downloadFileDecl.setVol("TEST") + downloadFileDecl.setReturnEtag(false) + downloadFileDecl.perform( + run, + workspace, + env, + launcher, + taskListener, + zosConnection + ) + + assertSoftly { isDownloadDatasetStarted shouldBe true } + assertSoftly { isDownloaded shouldBe true } + } + } +}) diff --git a/src/test/kotlin/org/zowe/zdevops/declarative/jobs/SubmitJobStepDeclarativeSpec.kt b/src/test/kotlin/org/zowe/zdevops/declarative/jobs/SubmitJobStepDeclarativeSpec.kt new file mode 100644 index 0000000..a3e7d3a --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/declarative/jobs/SubmitJobStepDeclarativeSpec.kt @@ -0,0 +1,106 @@ +/* + * 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 + */ + +package org.zowe.zdevops.declarative.jobs + +import hudson.EnvVars +import hudson.FilePath +import hudson.model.Item +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.fail +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.engine.spec.tempdir +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.MOCK_SERVER_HOST +import org.zowe.zdevops.MockResponseDispatcher +import org.zowe.zdevops.MockServerFactory +import java.io.File +import java.io.PrintStream + +class SubmitJobStepDeclarativeSpec : ShouldSpec({ + lateinit var mockServer: MockWebServer + lateinit var responseDispatcher: MockResponseDispatcher + val mockServerFactory = MockServerFactory() + + beforeSpec { + mockServer = mockServerFactory.startMockServer(MOCK_SERVER_HOST) + responseDispatcher = mockServerFactory.responseDispatcher + } + afterSpec { + mockServerFactory.stopMockServer() + } + context("declarative/jobs module: SubmitJobStep") { + val virtualChannel = TestVirtualChannel() + val zosConnection = ZOSConnection(mockServer.hostName, mockServer.port.toString(), "test", "test", "https") + + afterEach { + responseDispatcher.removeAllEndpoints() + } + should("perform SubmitJobStepDeclarative operation") { + var isJobSubmitting = false + var isJobSubmitted = false + val trashDir = tempdir() + val itemGroup = object : TestItemGroup() { + override fun getRootDirFor(child: Item?): File { + return trashDir + } + } + val job = TestJob(itemGroup, "test") + val run = TestRun(job) + val workspace = FilePath(File("")) + val env = EnvVars() + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Submitting a JOB")) { + isJobSubmitting = true + } else if (firstArg().contains("JOB submitted successfully")) { + isJobSubmitted = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + + responseDispatcher.injectEndpoint( + this.testCase.name.testName, + { it?.requestLine?.contains("zosmf/restjobs/jobs") ?: false }, + { MockResponse().setBody(responseDispatcher.readMockJson("submitJobResponse") ?: "") } + ) + + val submitJobStepDeclInst = spyk( + SubmitJobStepDeclarative("test") + ) + submitJobStepDeclInst.perform( + run, + workspace, + env, + launcher, + taskListener, + zosConnection + ) + + assertSoftly { isJobSubmitting shouldBe true } + assertSoftly { isJobSubmitted shouldBe true } + } + } +}) diff --git a/src/test/kotlin/org/zowe/zdevops/declarative/jobs/SubmitJobSyncStepDeclarativeSpec.kt b/src/test/kotlin/org/zowe/zdevops/declarative/jobs/SubmitJobSyncStepDeclarativeSpec.kt new file mode 100644 index 0000000..c7c5f3f --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/declarative/jobs/SubmitJobSyncStepDeclarativeSpec.kt @@ -0,0 +1,213 @@ +/* + * 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 + */ + +package org.zowe.zdevops.declarative.jobs + +import hudson.EnvVars +import hudson.FilePath +import hudson.model.Item +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.fail +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.engine.spec.tempdir +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.MOCK_SERVER_HOST +import org.zowe.zdevops.MockResponseDispatcher +import org.zowe.zdevops.MockServerFactory +import java.io.File +import java.io.PrintStream +import java.nio.file.Paths + +class SubmitJobSyncStepDeclarativeSpec : ShouldSpec({ + lateinit var mockServer: MockWebServer + lateinit var responseDispatcher: MockResponseDispatcher + val mockServerFactory = MockServerFactory() + + beforeSpec { + mockServer = mockServerFactory.startMockServer(MOCK_SERVER_HOST) + responseDispatcher = mockServerFactory.responseDispatcher + } + afterSpec { + mockServerFactory.stopMockServer() + } + context("declarative/jobs module: SubmitJobStep") { + val virtualChannel = TestVirtualChannel() + val zosConnection = ZOSConnection(mockServer.hostName, mockServer.port.toString(), "test", "test", "https") + val trashDir = tempdir() + val itemGroup = object : TestItemGroup() { + override fun getRootDirFor(child: Item?): File { + return trashDir + } + } + val job = TestJob(itemGroup, "test") + val run = TestRun(job) + val trashDirWithInternal = Paths.get(trashDir.absolutePath, "test_name").toString() + val workspace = FilePath(File(trashDirWithInternal)) + val env = EnvVars() + + afterEach { + responseDispatcher.removeAllEndpoints() + } + should("perform SubmitJobSyncStepDeclarative operation without spool files") { + var isJobSubmitting = false + var isJobSubmitted = false + var isWaitingJobFinish = false + var isJobFinished = false + var isDownloadingExecutionLog = false + var isNoSpoolLogs = false + + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Submitting a JOB")) { + isJobSubmitting = true + } else if (firstArg().contains("JOB submitted successfully")) { + isJobSubmitted = true + } else if (firstArg().contains("Waiting for a JOB finish")) { + isWaitingJobFinish = true + } else if (firstArg().contains("JOB was finished. Returned code")) { + isJobFinished = true + } else if (firstArg().contains("Downloading execution log")) { + isDownloadingExecutionLog = true + } else if (firstArg().contains("There are no logs for")) { + isNoSpoolLogs = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_submitJob", + { it?.requestLine?.matches(Regex("PUT /zosmf/restjobs/jobs HTTP/.*")) == true }, + { MockResponse().setBody(responseDispatcher.readMockJson("submitJobResponse") ?: "") } + ) + val getJobsRegex = Regex("GET /zosmf/restjobs/jobs/(?!.*files).* HTTP/.*") + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_getJob", + { it?.requestLine?.matches(getJobsRegex) == true }, + { MockResponse().setBody(responseDispatcher.readMockJson("getJobResponse") ?: "") } + ) + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_getJobSpoolFiles", + { it?.requestLine?.matches(Regex("GET /zosmf/restjobs/jobs/.*/files HTTP/.*")) == true }, + { MockResponse().setBody("[]") } + ) + + val submitJobSyncStepDeclInst = spyk( + SubmitJobSyncStepDeclarative("test") + ) + submitJobSyncStepDeclInst.perform( + run, + workspace, + env, + launcher, + taskListener, + zosConnection + ) + + assertSoftly { isJobSubmitting shouldBe true } + assertSoftly { isJobSubmitted shouldBe true } + assertSoftly { isWaitingJobFinish shouldBe true } + assertSoftly { isJobFinished shouldBe true } + assertSoftly { isDownloadingExecutionLog shouldBe true } + assertSoftly { isNoSpoolLogs shouldBe true } + } + should("perform SubmitJobSyncStepDeclarative operation with spool files") { + var isJobSubmitting = false + var isJobSubmitted = false + var isWaitingJobFinish = false + var isJobFinished = false + var isDownloadingExecutionLog = false + var isSubmissionLogSaved = false + + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Submitting a JOB")) { + isJobSubmitting = true + } else if (firstArg().contains("JOB submitted successfully")) { + isJobSubmitted = true + } else if (firstArg().contains("Waiting for a JOB finish")) { + isWaitingJobFinish = true + } else if (firstArg().contains("JOB was finished. Returned code")) { + isJobFinished = true + } else if (firstArg().contains("Downloading execution log")) { + isDownloadingExecutionLog = true + } else if (firstArg().contains("Submission log:")) { + isSubmissionLogSaved = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_submitJob", + { it?.requestLine?.matches(Regex("PUT /zosmf/restjobs/jobs HTTP/.*")) == true }, + { MockResponse().setBody(responseDispatcher.readMockJson("submitJobResponse") ?: "") } + ) + val getJobsRegex = Regex("GET /zosmf/restjobs/jobs/(?!.*files).* HTTP/.*") + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_getJob", + { it?.requestLine?.matches(getJobsRegex) == true }, + { MockResponse().setBody(responseDispatcher.readMockJson("getJobResponse") ?: "") } + ) + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_getJobSpoolFiles", + { it?.requestLine?.matches(Regex("GET /zosmf/restjobs/jobs/.*/files(?!.*records).* HTTP/.*")) == true }, + { MockResponse().setBody(responseDispatcher.readMockJson("getJobSpoolFilesResponse") ?: "") } + ) + val getSpoolFileRecordsRespBody = javaClass.classLoader.getResource("mock/getSpoolFileRecordsResponse.txt")?.readText() + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_getSpoolFileRecords", + { it?.requestLine?.matches(Regex("GET /zosmf/restjobs/jobs/.*/files/.*/records.* HTTP/.*")) == true }, + { MockResponse().setBody(getSpoolFileRecordsRespBody ?: "") } + ) + + val submitJobSyncStepDeclInst = spyk( + SubmitJobSyncStepDeclarative("test") + ) + submitJobSyncStepDeclInst.perform( + run, + workspace, + env, + launcher, + taskListener, + zosConnection + ) + + assertSoftly { isJobSubmitting shouldBe true } + assertSoftly { isJobSubmitted shouldBe true } + assertSoftly { isWaitingJobFinish shouldBe true } + assertSoftly { isJobFinished shouldBe true } + assertSoftly { isDownloadingExecutionLog shouldBe true } + assertSoftly { isSubmissionLogSaved shouldBe true } + } + } +}) diff --git a/src/test/kotlin/org/zowe/zdevops/declarative/jobs/WriteFileToFileDeclarativeSpec.kt b/src/test/kotlin/org/zowe/zdevops/declarative/jobs/WriteFileToFileDeclarativeSpec.kt new file mode 100644 index 0000000..9009c6b --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/declarative/jobs/WriteFileToFileDeclarativeSpec.kt @@ -0,0 +1,110 @@ +/* + * 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 + */ + +package org.zowe.zdevops.declarative.jobs + +import hudson.EnvVars +import hudson.FilePath +import hudson.model.Item +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.fail +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.engine.spec.tempdir +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.MOCK_SERVER_HOST +import org.zowe.zdevops.MockResponseDispatcher +import org.zowe.zdevops.MockServerFactory +import org.zowe.zdevops.classic.steps.TestBuildListener +import org.zowe.zdevops.classic.steps.TestLauncher +import java.io.File +import java.io.PrintStream +import java.nio.file.Paths + +class WriteFileToFileDeclarativeSpec : ShouldSpec({ + lateinit var mockServer: MockWebServer + lateinit var responseDispatcher: MockResponseDispatcher + val mockServerFactory = MockServerFactory() + + beforeSpec { + mockServer = mockServerFactory.startMockServer(MOCK_SERVER_HOST) + responseDispatcher = mockServerFactory.responseDispatcher + } + afterSpec { + mockServerFactory.stopMockServer() + } + context("declarative/jobs module: WriteFileToFileDeclarative") { + val virtualChannel = TestVirtualChannel() + val zosConnection = ZOSConnection(mockServer.hostName, mockServer.port.toString(), "test", "test", "https") + val rootDir = Paths.get("").toAbsolutePath().toString() + val trashDir = tempdir() + val itemGroup = object : TestItemGroup() { + override fun getRootDirFor(child: Item?): File { + return trashDir + } + } + val job = TestJob(itemGroup, "test") + val run = TestRun(job) + val mockDir = Paths.get(rootDir, "src", "test", "resources", "mock", "here").toString() + val workspace = FilePath(File(mockDir)) + val env = EnvVars() + + afterEach { + responseDispatcher.removeAllEndpoints() + } + should("perform WriteFileToFileDeclarative operation to write a local file to a USS file") { + var isWritingToFile = false + var isWritten = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Writing to Unix file")) { + isWritingToFile = true + } else if (firstArg().contains("Data has been written to Unix file")) { + isWritten = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_UssFile", + { it?.requestLine?.contains("zosmf/restfiles/fs") ?: false }, + { MockResponse().setBody("") } + ) + + val writeFileToFileDecl = spyk( + WriteFileToFileDeclarative("/u/TEST/test.txt", "test_file.txt") + ) + + writeFileToFileDecl.perform( + run, + workspace, + env, + launcher, + taskListener, + zosConnection + ) + assertSoftly { isWritingToFile shouldBe true } + assertSoftly { isWritten shouldBe true } + } + } +}) \ No newline at end of file diff --git a/src/test/kotlin/org/zowe/zdevops/declarative/jobs/WriteFileToMemberDeclarativeSpec.kt b/src/test/kotlin/org/zowe/zdevops/declarative/jobs/WriteFileToMemberDeclarativeSpec.kt new file mode 100644 index 0000000..da34807 --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/declarative/jobs/WriteFileToMemberDeclarativeSpec.kt @@ -0,0 +1,109 @@ +/* + * 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 + */ + +package org.zowe.zdevops.declarative.jobs + +import hudson.EnvVars +import hudson.FilePath +import hudson.model.Item +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.fail +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.engine.spec.tempdir +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.MOCK_SERVER_HOST +import org.zowe.zdevops.MockResponseDispatcher +import org.zowe.zdevops.MockServerFactory +import java.io.File +import java.io.PrintStream +import java.nio.file.Paths + +class WriteFileToMemberDeclarativeSpec : ShouldSpec({ + lateinit var mockServer: MockWebServer + lateinit var responseDispatcher: MockResponseDispatcher + val mockServerFactory = MockServerFactory() + + beforeSpec { + mockServer = mockServerFactory.startMockServer(MOCK_SERVER_HOST) + responseDispatcher = mockServerFactory.responseDispatcher + } + afterSpec { + mockServerFactory.stopMockServer() + } + context("declarative/jobs module: WriteFileToMemberDeclarative") { + val virtualChannel = TestVirtualChannel() + val zosConnection = ZOSConnection(mockServer.hostName, mockServer.port.toString(), "test", "test", "https") + val rootDir = Paths.get("").toAbsolutePath().toString() + val trashDir = tempdir() + val itemGroup = object : TestItemGroup() { + override fun getRootDirFor(child: Item?): File { + return trashDir + } + } + val job = TestJob(itemGroup, "test") + val run = TestRun(job) + val mockDir = Paths.get(rootDir, "src", "test", "resources", "mock", "here").toString() + val workspace = FilePath(File(mockDir)) + val env = EnvVars() + + afterEach { + responseDispatcher.removeAllEndpoints() + } + should("perform WriteFileToMemberDeclarative operation to write file to a member") { + var isWritingToDataset = false + var isWritten = false + val taskListener = object : TestBuildListener() { + override fun getLogger(): PrintStream { + val logger = mockk() + every { + logger.println(any()) + } answers { + if (firstArg().contains("Writing to dataset")) { + isWritingToDataset = true + } else if (firstArg().contains("Data has been written to dataset")) { + isWritten = true + } else { + fail("Unexpected logger message: ${firstArg()}") + } + } + return logger + } + } + val launcher = TestLauncher(taskListener, virtualChannel) + + responseDispatcher.injectEndpoint( + "${this.testCase.name.testName}_listDataSets", + { it?.requestLine?.contains("zosmf/restfiles/ds") ?: false }, + { MockResponse().setBody(responseDispatcher.readMockJson("listDataSets") ?: "") } + ) + + val writeFileToMemDecl = spyk( + WriteFIleToMemberDeclarative("TEST.IJMP.DATASET1", "TEST", "test_file.txt") + ) + writeFileToMemDecl.perform( + run, + workspace, + env, + launcher, + taskListener, + zosConnection + ) + + assertSoftly { isWritingToDataset shouldBe true } + assertSoftly { isWritten shouldBe true } + } + } +}) diff --git a/src/test/kotlin/org/zowe/zdevops/declarative/jobs/dummyClasses.kt b/src/test/kotlin/org/zowe/zdevops/declarative/jobs/dummyClasses.kt new file mode 100644 index 0000000..5ef3b8a --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/declarative/jobs/dummyClasses.kt @@ -0,0 +1,145 @@ +/* + * 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 + */ + +package org.zowe.zdevops.declarative.jobs + +import hudson.FilePath +import hudson.Launcher +import hudson.Proc +import hudson.model.* +import hudson.remoting.Callable +import hudson.remoting.Channel +import hudson.remoting.Future +import hudson.remoting.VirtualChannel +import java.io.File +import java.io.OutputStream +import java.io.PrintStream +import java.util.* + +open class TestJob(parent: ItemGroup<*>?, name: String?) : Job(parent, name) { + override fun isBuildable(): Boolean { + TODO("Not yet implemented") + } + + override fun _getRuns(): SortedMap { + TODO("Not yet implemented") + } + + override fun removeRun(run: TestRun?) { + TODO("Not yet implemented") + } + +} + +open class TestRun(job: TestJob) : Run(job) + +open class TestItemGroup : ItemGroup { + override fun save() { + TODO("Not yet implemented") + } + + override fun getRootDir(): File { + TODO("Not yet implemented") + } + + override fun getDisplayName(): String { + TODO("Not yet implemented") + } + + override fun getFullName(): String { + TODO("Not yet implemented") + } + + override fun getFullDisplayName(): String { + TODO("Not yet implemented") + } + + override fun getItems(): MutableCollection { + TODO("Not yet implemented") + } + + override fun getUrl(): String { + TODO("Not yet implemented") + } + + override fun getUrlChildPrefix(): String { + TODO("Not yet implemented") + } + + override fun getItem(name: String?): Item? { + TODO("Not yet implemented") + } + + override fun onDeleted(item: Item?) { + TODO("Not yet implemented") + } + + override fun getRootDirFor(child: Item?): File { + TODO("Not yet implemented") + } + +} + +open class TestVirtualChannel : VirtualChannel { + override fun call(callable: Callable?): V { + TODO("Not yet implemented") + } + + override fun callAsync(callable: Callable?): Future { + TODO("Not yet implemented") + } + + override fun close() { + TODO("Not yet implemented") + } + + override fun join() { + TODO("Not yet implemented") + } + + override fun join(timeout: Long) { + TODO("Not yet implemented") + } + + override fun export(type: Class?, instance: T?): T? { + TODO("Not yet implemented") + } + + override fun syncLocalIO() { + TODO("Not yet implemented") + } + +} + +open class TestBuildListener : BuildListener { + override fun getLogger(): PrintStream { + TODO("Not yet implemented") + } + +} + +open class TestLauncher(taskListener: TaskListener, virtualChannel: VirtualChannel) : Launcher(taskListener, virtualChannel) { + override fun launch(starter: ProcStarter): Proc { + TODO("Not yet implemented") + } + + override fun launchChannel( + cmd: Array, + out: OutputStream, + workDir: FilePath?, + envVars: MutableMap + ): Channel { + TODO("Not yet implemented") + } + + override fun kill(modelEnvVars: MutableMap?) { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/src/test/kotlin/org/zowe/zdevops/testConstants.kt b/src/test/kotlin/org/zowe/zdevops/testConstants.kt new file mode 100644 index 0000000..29dcc70 --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/testConstants.kt @@ -0,0 +1,13 @@ +/* + * 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 + */ + +package org.zowe.zdevops + +const val MOCK_SERVER_HOST = "localhost" diff --git a/src/test/kotlin/org/zowe/zdevops/utils/FieldsValidationSpec.kt b/src/test/kotlin/org/zowe/zdevops/utils/FieldsValidationSpec.kt new file mode 100644 index 0000000..529c301 --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/utils/FieldsValidationSpec.kt @@ -0,0 +1,45 @@ +/* + * 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 + */ +package org.zowe.zdevops.utils + +import hudson.util.FormValidation +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.matchers.shouldBe +import org.zowe.zdevops.Messages + +class FieldsValidationSpec : ShouldSpec({ + + should("validate dataset name") { + val validDsn = "TEST.TEST.TEST" + val invalidDsn = "INVALID_DATASET@" + + validateDatasetName(validDsn) shouldBe FormValidation.ok() + validateDatasetName(invalidDsn) shouldBe FormValidation.warning(Messages.zdevops_dataset_name_is_invalid_validation()) + validateDatasetName("") shouldBe FormValidation.error(Messages.zdevops_value_must_not_be_empty_validation()) + } + + should("validate member name") { + val validMember = "MEMBER" + val invalidMember = "1@SQW" + + validateMemberName(validMember) shouldBe FormValidation.ok() + validateMemberName(invalidMember) shouldBe FormValidation.warning(Messages.zdevops_member_name_is_invalid_validation()) + validateMemberName("MEMBER_NAME_IS_TOO_LONG") shouldBe FormValidation.error(Messages.zdevops_value_up_to_eight_in_length_validation()) + validateMemberName("") shouldBe FormValidation.error(Messages.zdevops_value_up_to_eight_in_length_validation()) + } + + should("validate field is not empty") { + val validValue = "VALID_VALUE" + val emptyValue = "" + + validateFieldIsNotEmpty(validValue) shouldBe FormValidation.ok() + validateFieldIsNotEmpty(emptyValue) shouldBe FormValidation.error(Messages.zdevops_value_must_not_be_empty_validation()) + } +}) \ No newline at end of file diff --git a/src/test/resources/mock/emptyDataSetsList.json b/src/test/resources/mock/emptyDataSetsList.json new file mode 100644 index 0000000..809888f --- /dev/null +++ b/src/test/resources/mock/emptyDataSetsList.json @@ -0,0 +1,5 @@ +{ + "items": [], + "returnedRows": 0, + "JSONversion": 1 +} \ No newline at end of file diff --git a/src/test/resources/mock/endTsoResponse.json b/src/test/resources/mock/endTsoResponse.json new file mode 100644 index 0000000..09f8ef9 --- /dev/null +++ b/src/test/resources/mock/endTsoResponse.json @@ -0,0 +1,6 @@ +{ + "servletKey": "TEST-120-aabgaaad", + "ver": "0100", + "reused": false, + "timeout": false +} \ No newline at end of file diff --git a/src/test/resources/mock/getJobResponse.json b/src/test/resources/mock/getJobResponse.json new file mode 100644 index 0000000..dfeb9c7 --- /dev/null +++ b/src/test/resources/mock/getJobResponse.json @@ -0,0 +1,15 @@ +{ + "owner": "ZOSMFAD", + "phase": 14, + "subsystem": "JES2", + "phase-name": "Job is actively executing", + "job-correlator": "J0007380S0W1....DB23523B.......:", + "type": "JOB", + "url": "https://test:10443/zosmf/restjobs/jobs/J0007380S0W1....DB23523B.......%3A", + "jobid": "JOB07380", + "class": "B", + "files-url": "https://test:10443/zosmf/restjobs/jobs/J0007380S0W1....DB23523B.......%3A/files", + "jobname": "JOBNAME", + "status": "OUTPUT", + "retcode": "CC 0000" +} \ No newline at end of file diff --git a/src/test/resources/mock/getJobSpoolFilesResponse.json b/src/test/resources/mock/getJobSpoolFilesResponse.json new file mode 100644 index 0000000..99260a2 --- /dev/null +++ b/src/test/resources/mock/getJobSpoolFilesResponse.json @@ -0,0 +1,18 @@ +[ + { + "recfm": "UA", + "records-url": "https:\/\/test:10443\/zosmf\/restjobs\/jobs\/T0000555S0W1....DB9E6D9D.......%3A\/files\/2\/records", + "stepname": "JES2", + "subsystem": "JES2", + "job-correlator": "T0000555S0W1....DB9E6D9D.......:", + "byte-count": 0, + "lrecl": 133, + "jobid": "TSU00555", + "ddname": "JESMSGLG", + "id": 2, + "record-count": 2, + "class": "K", + "jobname": "NBEL", + "procstep": null + } +] \ No newline at end of file diff --git a/src/test/resources/mock/getSpoolFileRecordsResponse.txt b/src/test/resources/mock/getSpoolFileRecordsResponse.txt new file mode 100644 index 0000000..7f82db4 --- /dev/null +++ b/src/test/resources/mock/getSpoolFileRecordsResponse.txt @@ -0,0 +1,21 @@ +1 J E S 2 J O B L O G -- S Y S T E M S 0 W 1 -- N O D E S 0 W 1 +0 + 13.27.41 JOB09502 ---- WEDNESDAY, 06 APR 2022 ---- + 13.27.41 JOB09502 IRR010I USERID ZOSMFAD IS ASSIGNED TO THIS JOB. + 13.27.43 JOB09502 ICH70001I ZOSMFAD LAST ACCESS AT 13:27:39 ON WEDNESDAY, APRIL 6, 2022 + 13.27.43 JOB09502 $HASP373 IJMP05 STARTED - INIT 7 - CLASS A - SYS S0W1 + 13.27.43 JOB09502 IEF403I IJMP05 - STARTED - TIME=13.27.43 + 13.27.43 JOB09502 - -----TIMINGS (MINS.)------ -----PAGING COUNTS---- + 13.27.43 JOB09502 -STEPNAME PROCSTEP RC EXCP CONN TCB SRB CLOCK SERV WORKLOAD PAGE SWAP VIO SWAPS + 13.27.43 JOB09502 -STEP1 00 12 0 .00 .00 .0 BATCH 0 0 0 0 + 13.27.44 JOB09502 -DD00907 00 92 0 .00 .00 .0 4 BATCH 1 0 0 0 + 13.27.44 JOB09502 -DD00908 00 93 0 .00 .00 .0 3 BATCH 0 0 0 0 + 13.27.45 JOB09502 -DD00909 00 90 0 .00 .00 .0 4 BATCH 0 0 0 0 + 13.27.45 JOB09502 -DD00910 00 90 0 .00 .00 .0 3 BATCH 0 0 0 0 + 13.27.47 JOB09502 -DD00911 00 87 0 .00 .00 .0 3 BATCH 0 0 0 0 + 13.27.47 JOB09502 -DD00912 00 92 0 .00 .00 .0 3 BATCH 0 0 0 0 + 13.27.48 JOB09502 -DD00913 00 107 0 .00 .00 .0 4 BATCH 0 0 0 0 + 13.27.49 JOB09502 -DD00914 00 91 0 .00 .00 .0 3 BATCH 0 0 0 0 + 13.30.15 JOB09502 IEF404I IJMP05 - ENDED - TIME=13.30.15 + 13.30.15 JOB09502 -IJMP05 ENDED. NAME-TESTJCL TOTAL TCB CPU TIME= .24 TOTAL ELAPSED TIME= 2.5 + 13.30.15 JOB09502 $HASP395 IJMP05 ENDED - RC=0000 \ No newline at end of file diff --git a/src/test/resources/mock/getTsoResponse.json b/src/test/resources/mock/getTsoResponse.json new file mode 100644 index 0000000..ba77db7 --- /dev/null +++ b/src/test/resources/mock/getTsoResponse.json @@ -0,0 +1,50 @@ +{ + "servletKey": "TEST-120-aabgaaad", + "ver": "0100", + "tsoData": [ + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "IKJ56951I NO BROADCAST MESSAGES" + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "*****************************************************************" + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "* *" + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "*****************************************************************" + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": " " + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "READY " + } + }, + { + "TSO PROMPT": { + "VERSION": "0100", + "HIDDEN": "FALSE" + } + } + ], + "reused": false, + "timeout": false +} \ No newline at end of file diff --git a/src/test/resources/mock/listDataSetMembers.json b/src/test/resources/mock/listDataSetMembers.json new file mode 100644 index 0000000..21eec46 --- /dev/null +++ b/src/test/resources/mock/listDataSetMembers.json @@ -0,0 +1,27 @@ +{ + "items": [ + { + "member": "TEST1" + }, + { + "member": "TEST2" + }, + { + "member": "TEST3" + }, + { + "member": "TEST4" + }, + { + "member": "TEST5" + }, + { + "member": "TEST6" + }, + { + "member": "TEST7" + } + ], + "returnedRows": 7, + "JSONversion": 1 +} \ No newline at end of file diff --git a/src/test/resources/mock/listDataSets.json b/src/test/resources/mock/listDataSets.json new file mode 100644 index 0000000..1ab3f59 --- /dev/null +++ b/src/test/resources/mock/listDataSets.json @@ -0,0 +1,94 @@ +{ + "items": [ + { + "dsname": "TEST.IJMP.DATASET1", + "blksz": "4500", + "catnm": "TEST.CATALOG.MASTER", + "cdate": "2021/11/15", + "dev": "3390", + "dsntp": "PDS", + "dsorg": "PO", + "edate": "***None***", + "extx": "1", + "lrecl": "150", + "migr": "NO", + "mvol": "N", + "ovf": "NO", + "rdate": "2021/11/17", + "recfm": "FB", + "sizex": "8", + "spacu": "TRACKS", + "used": "37", + "vol": "TESTVOL", + "vols": "TESTVOL" + }, + { + "dsname": "TEST.IJMP.DATASET2", + "blksz": "4500", + "catnm": "TEST.CATALOG.MASTER", + "cdate": "2021/11/17", + "dev": "3390", + "dsntp": "PDS", + "dsorg": "PO", + "edate": "***None***", + "extx": "1", + "lrecl": "150", + "migr": "NO", + "mvol": "N", + "ovf": "NO", + "rdate": "2021/11/17", + "recfm": "FB", + "sizex": "8", + "spacu": "TRACKS", + "used": "12", + "vol": "TESTVOL", + "vols": "TESTVOL" + }, + { + "dsname": "TEST.IJMP.DATASET3", + "blksz": "3120", + "catnm": "TEST.CATALOG.MASTER", + "cdate": "2021/10/22", + "dev": "3390", + "dsntp": "PDS", + "dsorg": "PO", + "edate": "***None***", + "extx": "1", + "lrecl": "80", + "migr": "NO", + "mvol": "N", + "ovf": "NO", + "rdate": "2021/11/17", + "recfm": "FB", + "sizex": "8", + "spacu": "TRACKS", + "used": "12", + "vol": "TESTVOL", + "vols": "TESTVOL" + }, + { + "dsname": "TEST.IJMP.DATASET4", + "blksz": "129", + "catnm": "TEST.CATALOG.MASTER", + "cdate": "2021/01/17", + "dev": "3390", + "dsorg": "PS", + "edate": "***None***", + "extx": "1", + "lrecl": "125", + "migr": "NO", + "mvol": "N", + "ovf": "NO", + "rdate": "2021/11/17", + "recfm": "VA", + "sizex": "9", + "spacu": "BLOCKS", + "used": "33", + "vol": "TESTVOL", + "vols": "TESTVOL" + } + ], + "returnedRows": 4, + "totalRows": 4, + "JSONversion": 1 +} diff --git a/src/test/resources/mock/listDataSetsPS.json b/src/test/resources/mock/listDataSetsPS.json new file mode 100644 index 0000000..9304e56 --- /dev/null +++ b/src/test/resources/mock/listDataSetsPS.json @@ -0,0 +1,28 @@ +{ + "items": [ + { + "dsname": "TEST.IJMP.DATASET4", + "blksz": "129", + "catnm": "TEST.CATALOG.MASTER", + "cdate": "2021/01/17", + "dev": "3390", + "dsorg": "PS", + "edate": "***None***", + "extx": "1", + "lrecl": "125", + "migr": "NO", + "mvol": "N", + "ovf": "NO", + "rdate": "2021/11/17", + "recfm": "VA", + "sizex": "9", + "spacu": "BLOCKS", + "used": "33", + "vol": "TESTVOL", + "vols": "TESTVOL" + } + ], + "returnedRows": 1, + "totalRows": 1, + "JSONversion": 1 +} diff --git a/src/test/resources/mock/retrieveDatasetContentResponse.txt b/src/test/resources/mock/retrieveDatasetContentResponse.txt new file mode 100644 index 0000000..2e8c043 --- /dev/null +++ b/src/test/resources/mock/retrieveDatasetContentResponse.txt @@ -0,0 +1,4 @@ +//TESTJOB JOB (,),'TEST JOB ', +// MSGLEVEL=(1,1), +// MSGCLASS=H +//STEP01 EXEC PGM=IEFBR14 diff --git a/src/test/resources/mock/sendTsoResponse.json b/src/test/resources/mock/sendTsoResponse.json new file mode 100644 index 0000000..2775d58 --- /dev/null +++ b/src/test/resources/mock/sendTsoResponse.json @@ -0,0 +1,26 @@ +{ + "servletKey": "TEST-120-aabgaaad", + "ver": "0100", + "tsoData": [ + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "IKJ56650I TIME-02:20:29 PM. CPU-00:00:00 SERVICE-448 SESSION-00:00:39 OCTOBER 4,2023" + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "READY " + } + }, + { + "TSO PROMPT": { + "VERSION": "0100", + "HIDDEN": "FALSE" + } + } + ], + "reused": false, + "timeout": false +} \ No newline at end of file diff --git a/src/test/resources/mock/startTsoResponse.json b/src/test/resources/mock/startTsoResponse.json new file mode 100644 index 0000000..846726e --- /dev/null +++ b/src/test/resources/mock/startTsoResponse.json @@ -0,0 +1,16 @@ +{ + "servletKey": "TEST-120-aabgaaad", + "queueID": "655385", + "sessionID": "0x78", + "ver": "0100", + "tsoData": [ + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "IKJ56455I TEST LOGON IN PROGRESS AT 14:19:47 ON OCTOBER 4, 2023" + } + } + ], + "reused": false, + "timeout": false +} \ No newline at end of file diff --git a/src/test/resources/mock/submitJobFailResponse.json b/src/test/resources/mock/submitJobFailResponse.json new file mode 100644 index 0000000..9eafcc6 --- /dev/null +++ b/src/test/resources/mock/submitJobFailResponse.json @@ -0,0 +1,7 @@ +{ + "rc": 4, + "reason": 13, + "stack": "JesException: CATEGORY_SERVICE rc=4 reason=13 message=Job input was not recognized by system as a job\n\tat com.ibm.zoszmf.restjobs.util.JesException.serviceException(JesException.java:183)\n\tat com.ibm.zoszmf.restjobs.util.AbstractStatusDao.submitJobFromDataSource(AbstractStatusDao.java:286)\n\tat com.ibm.zoszmf.restjobs.util.AbstractStatusDao.submitJobFromFile(AbstractStatusDao.java:90)\n\tat com.ibm.zoszmf.restjobs.servlet.JesService.submitJob(JesService.java:463)\n\tat com.ibm.zoszmf.restjobs.servlet.JesServlet.doPut(JesServlet.java:307)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:710)\n\tat com.ibm.zoszmf.restjobs.servlet.JesServlet.service(JesServlet.java:168)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:790)\n\tat com.ibm.ws.webcontainer.servlet.ServletWrapper.service(ServletWrapper.java:1255)\n\tat com.ibm.ws.webcontainer.servlet.ServletWrapper.handleRequest(ServletWrapper.java:743)\n\tat com.ibm.ws.webcontainer.servlet.ServletWrapper.handleRequest(ServletWrapper.java:440)\n\tat com.ibm.ws.webcontainer.filter.WebAppFilterChain.invokeTarget(WebAppFilterChain.java:182)\n\tat com.ibm.ws.webcontainer.filter.WebAppFilterChain.doFilter(WebAppFilterChain.java:93)\n\tat com.ibm.zoszmf.util.data.ActivityFilter.doFilter(ActivityFilter.java:100)\n\tat com.ibm.ws.webcontainer.filter.FilterInstanceWrapper.doFilter(FilterInstanceWrapper.java:201)\n\tat com.ibm.ws.webcontainer.filter.WebAppFilterChain.doFilter(WebAppFilterChain.java:90)\n\tat com.ibm.zoszmf.util.appscan.NoCacheFilter.doFilter(NoCacheFilter.java:92)\n\tat com.ibm.ws.webcontainer.filter.FilterInstanceWrapper.doFilter(FilterInstanceWrapper.java:201)\n\tat com.ibm.ws.webcontainer.filter.WebAppFilterChain.doFilter(WebAppFilterChain.java:90)\n\tat com.ibm.zoszmf.util.auth.CSRFwithWLFilter.doFilter(CSRFwithWLFilter.java:192)\n\tat com.ibm.ws.webcontainer.filter.FilterInstanceWrapper.doFilter(FilterInstanceWrapper.java:201)\n\tat com.ibm.ws.webcontainer.filter.WebAppFilterChain.doFilter(WebAppFilterChain.java:90)\n\tat com.ibm.zoszmf.util.auth.CommonSecurityHeaderFilter.doFilter(CommonSecurityHeaderFilter.java:72)\n\tat com.ibm.ws.webcontainer.filter.FilterInstanceWrapper.doFilter(FilterInstanceWrapper.java:201)\n\tat com.ibm.ws.webcontainer.filter.WebAppFilterChain.doFilter(WebAppFilterChain.java:90)\n\tat com.ibm.ws.webcontainer.filter.WebAppFilterManager.doFilter(WebAppFilterManager.java:996)\n\tat com.ibm.ws.webcontainer.filter.WebAppFilterManager.invokeFilters(WebAppFilterManager.java:1134)\n\tat com.ibm.ws.webcontainer.filter.WebAppFilterManager.invokeFilters(WebAppFilterManager.java:1005)\n\tat com.ibm.ws.webcontainer.servlet.CacheServletWrapper.handleRequest(CacheServletWrapper.java:75)\n\tat com.ibm.ws.webcontainer.WebContainer.handleRequest(WebContainer.java:927)\n\tat com.ibm.ws.webcontainer.osgi.DynamicVirtualHost$2.run(DynamicVirtualHost.java:279)\n\tat com.ibm.ws.http.dispatcher.internal.channel.HttpDispatcherLink$TaskWrapper.run(HttpDispatcherLink.java:1011)\n\tat com.ibm.ws.zos.wlm.internal.WlmClassification$WlmExecutor.wlmRunWork(WlmClassification.java:324)\n\tat com.ibm.ws.zos.wlm.internal.WlmClassification$WlmExecutor.execute(WlmClassification.java:314)\n\tat com.ibm.ws.http.dispatcher.internal.channel.HttpDispatcherLink.wrapHandlerAndExecute(HttpDispatcherLink.java:409)\n\tat com.ibm.ws.http.dispatcher.internal.channel.HttpDispatcherLink.ready(HttpDispatcherLink.java:373)\n\tat com.ibm.ws.http.channel.internal.inbound.HttpInboundLink.handleDiscrimination(HttpInboundLink.java:532)\n\tat com.ibm.ws.http.channel.internal.inbound.HttpInboundLink.handleNewRequest(HttpInboundLink.java:466)\n\tat com.ibm.ws.http.channel.internal.inbound.HttpInboundLink.processRequest(HttpInboundLink.java:331)\n\tat com.ibm.ws.http.channel.internal.inbound.HttpInboundLink.ready(HttpInboundLink.java:302)\n\tat com.ibm.ws.channel.ssl.internal.SSLConnectionLink.determineNextChannel(SSLConnectionLink.java:1059)\n\tat com.ibm.ws.channel.ssl.internal.SSLConnectionLink$MyReadCompletedCallback.complete(SSLConnectionLink.java:644)\n\tat com.ibm.ws.channel.ssl.internal.SSLReadServiceContext$SSLReadCompletedCallback.complete(SSLReadServiceContext.java:1803)\n\tat com.ibm.ws.tcpchannel.internal.WorkQueueManager.requestComplete(WorkQueueManager.java:501)\n\tat com.ibm.ws.tcpchannel.internal.WorkQueueManager.attemptIO(WorkQueueManager.java:571)\n\tat com.ibm.ws.tcpchannel.internal.WorkQueueManager.workerRun(WorkQueueManager.java:926)\n\tat com.ibm.ws.tcpchannel.internal.WorkQueueManager$Worker.run(WorkQueueManager.java:1015)\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1160)\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)\n\tat java.lang.Thread.run(Thread.java:812)\n", + "category": 6, + "message": "Job input was not recognized by system as a job" +} \ No newline at end of file diff --git a/src/test/resources/mock/submitJobResponse.json b/src/test/resources/mock/submitJobResponse.json new file mode 100644 index 0000000..6aac83b --- /dev/null +++ b/src/test/resources/mock/submitJobResponse.json @@ -0,0 +1,15 @@ +{ + "owner": "ZOSMFAD", + "phase": 14, + "subsystem": "JES2", + "phase-name": "Job is actively executing", + "job-correlator": "J0007380S0W1....DB23523B.......:", + "type": "JOB", + "url": "https://test:10443/zosmf/restjobs/jobs/J0007380S0W1....DB23523B.......%3A", + "jobid": "JOB07380", + "class": "B", + "files-url": "https://test:10443/zosmf/restjobs/jobs/J0007380S0W1....DB23523B.......%3A/files", + "jobname": "JOBNAME", + "status": "ACTIVE", + "retcode": null +} \ No newline at end of file diff --git a/src/test/resources/mock/test_file.txt b/src/test/resources/mock/test_file.txt new file mode 100644 index 0000000..4293397 --- /dev/null +++ b/src/test/resources/mock/test_file.txt @@ -0,0 +1 @@ +THIS IS A TEST FILE \ No newline at end of file From dfc0cff2a304c99a18dc8313df57b6db70e77b6d Mon Sep 17 00:00:00 2001 From: IBA-mainframe-dev Date: Thu, 24 Oct 2024 16:07:43 +0200 Subject: [PATCH 2/4] Changed version number and removed release-drafter.yml --- .github/release-drafter.yml | 2 -- pom.xml | 14 ++++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) delete mode 100644 .github/release-drafter.yml diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml deleted file mode 100644 index 4ed532e..0000000 --- a/.github/release-drafter.yml +++ /dev/null @@ -1,2 +0,0 @@ -# https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.adoc -_extends: .github \ No newline at end of file diff --git a/pom.xml b/pom.xml index c65d50f..7838a0e 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ https://github.com/jenkinsci/${project.artifactId}-plugin - 1.2.0- + 1.1.0- -SNAPSHOT 2.414.3 17 @@ -26,7 +26,7 @@ true 1.13 4.10.0 - 0.5.0-rc.11 + 0.5.0 5.6.1 official 17 @@ -335,7 +335,7 @@ org.yaml snakeyaml - 2.2 + 2.3 @@ -371,7 +371,13 @@ com.google.code.gson gson - 2.10.1 + 2.11.0 + + + + com.google.errorprone + error_prone_annotations + 2.27.0 From 313614a1e19daccf376b99eeaf309e53165f6249 Mon Sep 17 00:00:00 2001 From: IBA-mainframe-dev Date: Thu, 24 Oct 2024 18:55:04 +0200 Subject: [PATCH 3/4] Corrected revision and changelist format --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 7838a0e..95f24d7 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ 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 @@ -18,8 +18,8 @@ https://github.com/jenkinsci/${project.artifactId}-plugin - 1.1.0- - -SNAPSHOT + 1.1.0 + 999999-SNAPSHOT 2.414.3 17 1.9.20 From 23f64bddca6f7af7f2f1c0666f4a09fba68c18e1 Mon Sep 17 00:00:00 2001 From: IBA-mainframe-dev Date: Tue, 29 Oct 2024 22:10:17 +0100 Subject: [PATCH 4/4] Changed zowe.sdk ID to correct one --- pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 95f24d7..802680e 100644 --- a/pom.xml +++ b/pom.xml @@ -465,7 +465,8 @@ - zowe.jfrog.io + + org.zowe.sdk https://zowe.jfrog.io/artifactory/libs-release