diff --git a/.github/setup/action.yml b/.github/setup/action.yml index 0d39adb..802090c 100644 --- a/.github/setup/action.yml +++ b/.github/setup/action.yml @@ -5,7 +5,7 @@ inputs: jdkVersion: description: "JDK version" required: false - default: "11" + default: "17" mavenVersion: description: "Maven version" required: false diff --git a/.github/workflows/hpi-builder.yml b/.github/workflows/hpi-builder.yml index 987e892..7c5ff09 100644 --- a/.github/workflows/hpi-builder.yml +++ b/.github/workflows/hpi-builder.yml @@ -20,7 +20,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '11' + java-version: '17' cache: maven - name: Build Jenkins Plugin into .hpi @@ -52,9 +52,9 @@ jobs: uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '11' + java-version: '17' cache: maven - name: Run Unit Tests with Maven shell: bash - run: ./mvnw test \ No newline at end of file + run: ./mvnw test diff --git a/Jenkinsfile b/Jenkinsfile index 87d1157..12f6a8d 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, tests: [[skip: 'true']], configurations: [[ platform: 'linux', jdk: '17' ]]) diff --git a/pom.xml b/pom.xml index f14afc6..2a9f4d9 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.jenkins-ci.plugins plugin - 4.78 + 4.78 @@ -19,21 +19,31 @@ - 0.2.0- + 0.2.0 -SNAPSHOT 2.414.3 - 11 - 1.8.21 + 17 + 1.9.20 true 1.13 4.10.0 - 0.5.0-rc.1 + 0.5.0-rc.11 5.6.1 official - 11 + 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 @@ -133,10 +143,10 @@ org.apache.maven.plugins maven-compiler-plugin - 3.11.0 + 3.13.0 - 11 - 11 + 17 + 17 @@ -302,19 +312,19 @@ 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 @@ -365,22 +375,12 @@ 2.10.1 - - org.jenkins-ci.plugins - script-security - - org.apache.commons commons-lang3 3.12.0 - - org.jenkins-ci - annotation-indexer - - com.squareup.okhttp3 okhttp @@ -429,6 +429,28 @@ 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 + + diff --git a/src/main/kotlin/org/zowe/zdevops/classic/AbstractBuildStep.kt b/src/main/kotlin/org/zowe/zdevops/classic/AbstractBuildStep.kt index c64cca0..6aecdba 100644 --- a/src/main/kotlin/org/zowe/zdevops/classic/AbstractBuildStep.kt +++ b/src/main/kotlin/org/zowe/zdevops/classic/AbstractBuildStep.kt @@ -24,6 +24,7 @@ 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 @@ -71,6 +72,8 @@ abstract class AbstractBuildStep(val connectionName: String) : Builder(), Simple connURL.host, connURL.port.toString(), connection.username, connection.password, connURL.protocol ) + validateConnection(zosConnection) + perform(build, launcher, listener, zosConnection) return true } diff --git a/src/main/kotlin/org/zowe/zdevops/classic/steps/AllocateDatasetStep.kt b/src/main/kotlin/org/zowe/zdevops/classic/steps/AllocateDatasetStep.kt index e12f511..52639e9 100644 --- a/src/main/kotlin/org/zowe/zdevops/classic/steps/AllocateDatasetStep.kt +++ b/src/main/kotlin/org/zowe/zdevops/classic/steps/AllocateDatasetStep.kt @@ -55,6 +55,7 @@ constructor( val primary: Int = 1, var secondary: Int, var recFm: RecordFormat, + var failOnExist: Boolean = false, ) : AbstractBuildStep(connectionName){ private var volser: String? = null @@ -177,7 +178,8 @@ constructor( dataClass, avgBlk, dsnType, - dsModel + dsModel, + failOnExist, ) } diff --git a/src/main/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetStep.kt b/src/main/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetStep.kt index b694865..6974e54 100644 --- a/src/main/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetStep.kt +++ b/src/main/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetStep.kt @@ -22,7 +22,6 @@ 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.validateFieldIsNotEmpty import org.zowe.zdevops.utils.validateMemberName class DeleteDatasetStep @@ -38,6 +37,7 @@ constructor( connectionName: String, val dsn: String, val member: String?, + val failOnNotExist: Boolean = false , ) : AbstractBuildStep(connectionName) { override fun perform( @@ -46,7 +46,7 @@ constructor( listener: BuildListener, zosConnection: ZOSConnection ) { - deleteDatasetOrMember(dsn, member, zosConnection, listener) + deleteDatasetOrMember(dsn, member, zosConnection, listener, failOnNotExist) } @Extension @@ -67,10 +67,14 @@ constructor( * Checks if the member name is valid * * @param member The dataset member name - * @return FormValidation.ok() if the member name is valid, or an error message otherwise + * @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 validateMemberName(member)?: validateFieldIsNotEmpty(member) + 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 index adf3117..ec82d84 100644 --- a/src/main/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetsByMaskStep.kt +++ b/src/main/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetsByMaskStep.kt @@ -31,6 +31,7 @@ class DeleteDatasetsByMaskStep constructor( connectionName: String, val dsnMask: String, + val failOnNotExist: Boolean = false, ) : AbstractBuildStep(connectionName) { override fun perform( @@ -39,7 +40,7 @@ constructor( listener: BuildListener, zosConnection: ZOSConnection ) { - deleteDatasetsByMask(dsnMask, zosConnection, listener) + deleteDatasetsByMask(dsnMask, zosConnection, listener, failOnNotExist) } @Extension diff --git a/src/main/kotlin/org/zowe/zdevops/classic/steps/SubmitJobStep.kt b/src/main/kotlin/org/zowe/zdevops/classic/steps/SubmitJobStep.kt index 65dfd04..6a5e548 100644 --- a/src/main/kotlin/org/zowe/zdevops/classic/steps/SubmitJobStep.kt +++ b/src/main/kotlin/org/zowe/zdevops/classic/steps/SubmitJobStep.kt @@ -41,8 +41,8 @@ constructor( val linkBuilder: (String?, String, String) -> String = { jobUrl, jobName, jobId -> "${jobUrl}ws/${jobName}.${jobId}/*view*/" } - val returnCode = submitJobSync(jobName, zosConnection, listener, workspace, build.getEnvironment(listener)["JOB_URL"], linkBuilder) - if (checkRC && !returnCode.equals("CC 0000")) { + 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 { diff --git a/src/main/kotlin/org/zowe/zdevops/config/ZOSConnection.kt b/src/main/kotlin/org/zowe/zdevops/config/ZOSConnection.kt index 75b68aa..ececb4b 100644 --- a/src/main/kotlin/org/zowe/zdevops/config/ZOSConnection.kt +++ b/src/main/kotlin/org/zowe/zdevops/config/ZOSConnection.kt @@ -8,7 +8,7 @@ * Copyright IBA Group 2022 */ -package org.zowe.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 org.zowe.zdevops.Messages -import org.zowe.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/org/zowe/zdevops/declarative/AbstractZosmfAction.kt b/src/main/kotlin/org/zowe/zdevops/declarative/AbstractZosmfAction.kt index 5cf4b4e..e985c1e 100644 --- a/src/main/kotlin/org/zowe/zdevops/declarative/AbstractZosmfAction.kt +++ b/src/main/kotlin/org/zowe/zdevops/declarative/AbstractZosmfAction.kt @@ -21,11 +21,9 @@ 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.Messages -import org.zowe.zdevops.config.ZOSConnectionList +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 { diff --git a/src/main/kotlin/org/zowe/zdevops/declarative/ZosmfStepDeclarative.kt b/src/main/kotlin/org/zowe/zdevops/declarative/ZosmfStepDeclarative.kt index d3b3cfc..75349ed 100644 --- a/src/main/kotlin/org/zowe/zdevops/declarative/ZosmfStepDeclarative.kt +++ b/src/main/kotlin/org/zowe/zdevops/declarative/ZosmfStepDeclarative.kt @@ -13,17 +13,24 @@ 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/org/zowe/zdevops/declarative/jobs/AllocateDatasetDeclarative.kt b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/AllocateDatasetDeclarative.kt index 27e886c..09c63ed 100644 --- a/src/main/kotlin/org/zowe/zdevops/declarative/jobs/AllocateDatasetDeclarative.kt +++ b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/AllocateDatasetDeclarative.kt @@ -46,7 +46,8 @@ constructor( private val dsOrg: DatasetOrganization, private val primary: Int, private var secondary: Int, - private var recFm: RecordFormat) : + private var recFm: RecordFormat, + private var failOnExist: Boolean = false) : AbstractZosmfAction() { private var volser: String? = null @@ -126,7 +127,8 @@ constructor( dataClass, avgBlk, dsnType, - dsModel + dsModel, + failOnExist, ) } diff --git a/src/main/kotlin/org/zowe/zdevops/declarative/jobs/DeleteDatasetDeclarative.kt b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/DeleteDatasetDeclarative.kt index 5b71e1a..fc72a4e 100644 --- a/src/main/kotlin/org/zowe/zdevops/declarative/jobs/DeleteDatasetDeclarative.kt +++ b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/DeleteDatasetDeclarative.kt @@ -66,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 } @@ -73,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( @@ -83,7 +87,7 @@ class DeleteDatasetDeclarative @DataBoundConstructor constructor( listener: TaskListener, zosConnection: ZOSConnection ) { - deleteDatasetOrMember(dsn, member, zosConnection, listener) + deleteDatasetOrMember(dsn, member, zosConnection, listener, failOnNotExist) } @Symbol("deleteDataset") diff --git a/src/main/kotlin/org/zowe/zdevops/declarative/jobs/DeleteDatasetsByMaskDeclarative.kt b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/DeleteDatasetsByMaskDeclarative.kt index 026d1ae..e91566e 100644 --- a/src/main/kotlin/org/zowe/zdevops/declarative/jobs/DeleteDatasetsByMaskDeclarative.kt +++ b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/DeleteDatasetsByMaskDeclarative.kt @@ -56,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( @@ -70,7 +74,7 @@ class DeleteDatasetsByMaskDeclarative @DataBoundConstructor constructor( listener: TaskListener, zosConnection: ZOSConnection ) { - deleteDatasetsByMask(mask, zosConnection,listener) + deleteDatasetsByMask(mask, zosConnection,listener, failOnNotExist) } diff --git a/src/main/kotlin/org/zowe/zdevops/logic/AllocateOperation.kt b/src/main/kotlin/org/zowe/zdevops/logic/AllocateOperation.kt index ad6dc58..ca24225 100644 --- a/src/main/kotlin/org/zowe/zdevops/logic/AllocateOperation.kt +++ b/src/main/kotlin/org/zowe/zdevops/logic/AllocateOperation.kt @@ -57,8 +57,8 @@ fun allocateDataset(listener: TaskListener, dataClass: String?, avgBlk: Int?, dsnType: DsnameType?, - dsModel: String? - + dsModel: String?, + failOnExist: Boolean, ) { listener.logger.println(Messages.zdevops_declarative_DSN_allocating(dsn, zosConnection.host, zosConnection.zosmfPort)) val alcParms = CreateDataset( @@ -79,6 +79,14 @@ fun allocateDataset(listener: TaskListener, dsnType, dsModel ) - ZosDsn(zosConnection).createDsn(dsn, alcParms) - listener.logger.println(Messages.zdevops_declarative_DSN_allocated_success(dsn)) + 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 index 4f139c2..6048c5f 100644 --- a/src/main/kotlin/org/zowe/zdevops/logic/DeleteOperation.kt +++ b/src/main/kotlin/org/zowe/zdevops/logic/DeleteOperation.kt @@ -29,22 +29,31 @@ private val successMessage: String = zMessages.zdevops_deleting_ds_success() * @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) { +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)) - 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) + 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.") } - listener.logger.println(successMessage) } /** @@ -56,22 +65,28 @@ fun deleteDatasetsByMask(mask: String, zosConnection: ZOSConnection, listener: T * @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) { +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) - runMFTryCatchWrappedQuery(listener) { + 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.") } - listener.logger.println(successMessage) } /** diff --git a/src/main/kotlin/org/zowe/zdevops/logic/PerformTsoCommandOperation.kt b/src/main/kotlin/org/zowe/zdevops/logic/PerformTsoCommandOperation.kt index dabb4c1..c6aeb07 100644 --- a/src/main/kotlin/org/zowe/zdevops/logic/PerformTsoCommandOperation.kt +++ b/src/main/kotlin/org/zowe/zdevops/logic/PerformTsoCommandOperation.kt @@ -14,6 +14,7 @@ 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 /** @@ -36,12 +37,12 @@ fun performTsoCommand( command: String, ) { listener.logger.println(Messages.zdevops_issue_TSO_command(command)) - runCatching { - val tsoCommandResponse = IssueTso(zosConnection).issueTsoCommand(acct, command) + try { + val tsoCommandResponse = IssueTso(zosConnection).issueTsoCommand(acct, command, StartTsoParams(), failOnPrompt = true) listener.logger.println(tsoCommandResponse.commandResponses) - }.onFailure { + } catch (ex: Exception) { listener.logger.println(Messages.zdevops_TSO_command_fail()) - throw AbortException(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/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/resources/org/zowe/zdevops/classic/steps/AllocateDatasetStep/config.jelly b/src/main/resources/org/zowe/zdevops/classic/steps/AllocateDatasetStep/config.jelly index f17a368..adca495 100644 --- a/src/main/resources/org/zowe/zdevops/classic/steps/AllocateDatasetStep/config.jelly +++ b/src/main/resources/org/zowe/zdevops/classic/steps/AllocateDatasetStep/config.jelly @@ -86,4 +86,7 @@ + + + \ No newline at end of file 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 index bea0c67..aa711c2 100644 --- a/src/main/resources/org/zowe/zdevops/classic/steps/DeleteDatasetStep/config.jelly +++ b/src/main/resources/org/zowe/zdevops/classic/steps/DeleteDatasetStep/config.jelly @@ -12,4 +12,7 @@ + + + \ No newline at end of file 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 index f0ccd34..6fabee9 100644 --- a/src/main/resources/org/zowe/zdevops/classic/steps/DeleteDatasetsByMaskStep/config.jelly +++ b/src/main/resources/org/zowe/zdevops/classic/steps/DeleteDatasetsByMaskStep/config.jelly @@ -9,4 +9,7 @@ + + + \ No newline at end of file diff --git a/src/main/resources/org/zowe/zdevops/config/ZOSConnection/config.jelly b/src/main/resources/org/zowe/zdevops/config/ZOSConnection/config.jelly index 414507a..8280a21 100644 --- a/src/main/resources/org/zowe/zdevops/config/ZOSConnection/config.jelly +++ b/src/main/resources/org/zowe/zdevops/config/ZOSConnection/config.jelly @@ -7,7 +7,7 @@ - + diff --git a/src/test/kotlin/org/zowe/zdevops/classic/steps/AllocateDatasetStepSpec.kt b/src/test/kotlin/org/zowe/zdevops/classic/steps/AllocateDatasetStepSpec.kt index a071ca6..1b2324f 100644 --- a/src/test/kotlin/org/zowe/zdevops/classic/steps/AllocateDatasetStepSpec.kt +++ b/src/test/kotlin/org/zowe/zdevops/classic/steps/AllocateDatasetStepSpec.kt @@ -124,6 +124,74 @@ class AllocateDatasetStepSpec : ShouldSpec({ 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() diff --git a/src/test/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetStepSpec.kt b/src/test/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetStepSpec.kt index c3d3ab5..f110b9d 100644 --- a/src/test/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetStepSpec.kt +++ b/src/test/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetStepSpec.kt @@ -99,8 +99,52 @@ class DeleteDatasetStepSpec : ShouldSpec({ 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") { @@ -110,7 +154,7 @@ class DeleteDatasetStepSpec : ShouldSpec({ } should("validate member name") { - descriptor.doCheckMember("") shouldBe FormValidation.error(Messages.zdevops_value_up_to_eight_in_length_validation()) + 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() } diff --git a/src/test/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetsByMaskStepSpec.kt b/src/test/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetsByMaskStepSpec.kt index 1b1aeb7..87f0ea8 100644 --- a/src/test/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetsByMaskStepSpec.kt +++ b/src/test/kotlin/org/zowe/zdevops/classic/steps/DeleteDatasetsByMaskStepSpec.kt @@ -132,7 +132,7 @@ class DeleteDatasetsByMaskStepSpec : ShouldSpec({ ) val deleteDatasetDecl = spyk( - DeleteDatasetsByMaskStep("test", "TEST.IJMP.DATASET%.NONE") + DeleteDatasetsByMaskStep("test", "TEST.IJMP.DATASET%.NONE", failOnNotExist = true) ) shouldThrow { deleteDatasetDecl.perform( diff --git a/src/test/kotlin/org/zowe/zdevops/classic/steps/PerformTsoCommandStepSpec.kt b/src/test/kotlin/org/zowe/zdevops/classic/steps/PerformTsoCommandStepSpec.kt index ea837f8..ff95f15 100644 --- a/src/test/kotlin/org/zowe/zdevops/classic/steps/PerformTsoCommandStepSpec.kt +++ b/src/test/kotlin/org/zowe/zdevops/classic/steps/PerformTsoCommandStepSpec.kt @@ -120,7 +120,7 @@ class PerformTsoCommandStepSpec : ShouldSpec({ assertSoftly { isPreExecuteStage shouldBe true } assertSoftly { isCommandExecuted shouldBe true } } - should("fail SubmitJobStep operation") { + should("fail PerformTsoCommand operation") { var isPreExecuteStage = false var isExecuteCommandFailLogged = false val taskListener = object : TestBuildListener() {