From 95f7d6c1ac0e26d4d78f3c5507575cce8f4c0d8f Mon Sep 17 00:00:00 2001 From: Anatoli Kalbasin <37359814+callbacksin@users.noreply.github.com> Date: Tue, 22 Oct 2024 18:49:54 +0300 Subject: [PATCH] Implement Perform MVS command method (#90) * remove 'WithResult' postfix Signed-off-by: Anatoli Kalbasin * implement PerformMvsCommand method Signed-off-by: Anatoli Kalbasin * add tests Signed-off-by: Anatoli Kalbasin * update license Signed-off-by: Anatoli Kalbasin * add docs Signed-off-by: Anatoli Kalbasin --------- Signed-off-by: Anatoli Kalbasin --- README.md | 41 ++++++++ .../classic/steps/PerformMvsCommandStep.kt | 85 ++++++++++++++++ .../jobs/PerformMvsCommandDeclarative.kt | 52 ++++++++++ .../jobs/PerformTsoCommandDeclarative.kt | 2 +- .../jobs/SubmitJobSyncStepDeclarative.kt | 2 +- .../logic/PerformMvsCommandOperation.kt | 51 ++++++++++ .../org/zowe/zdevops/Messages.properties | 1 + .../steps/PerformMvsCommandStep/config.jelly | 12 +++ .../PerformMvsCommandStep/config.properties | 2 + .../jobs/PerformMvsCommandDeclarativeSpec.kt | 98 +++++++++++++++++++ .../mock/displayActiveASCommandOutput.json | 6 ++ 11 files changed, 350 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/org/zowe/zdevops/classic/steps/PerformMvsCommandStep.kt create mode 100644 src/main/kotlin/org/zowe/zdevops/declarative/jobs/PerformMvsCommandDeclarative.kt create mode 100644 src/main/kotlin/org/zowe/zdevops/logic/PerformMvsCommandOperation.kt create mode 100644 src/main/resources/org/zowe/zdevops/classic/steps/PerformMvsCommandStep/config.jelly create mode 100644 src/main/resources/org/zowe/zdevops/classic/steps/PerformMvsCommandStep/config.properties create mode 100644 src/test/kotlin/org/zowe/zdevops/declarative/jobs/PerformMvsCommandDeclarativeSpec.kt create mode 100644 src/test/resources/mock/displayActiveASCommandOutput.json diff --git a/README.md b/README.md index 8c003ab..2ff73de 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,47 @@ deleteDataset dsn:"EXAMPLE.DATASET" 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'. ``` +### `performMvsCommand` - Execute an MVS System Command +The output of a command can be returned to a variable and subsequently processed. The step must be accompanied by `script` tag. It gives wide range of options such displaying system activities, device statuses, managing configuration, starting system tasks, etc. + +#### Usage: + +The step can be specified in two ways: +```groovy +performMvsCommand "DISPLAY TIME" +``` +or using the named parameter: +```groovy +performMvsCommand command: "DISPLAY TIME" +``` +**Mandatory Parameters:** there is only one parameter - `command`. + +#### Example - Displaying Active Units of Work: +To display detailed information about all active units of work, use the following command: +```groovy +def active_units = performMvsCommand "D A,L" +``` + +#### Expected behavior under various scenarios: + +* Insufficient Authorization: you are not authorized to issue the command: + ``` + [Perform MVS command] - Issuing command : D T + [Perform MVS command] - MVS command execution failed + + Also: org.jenkinsci.plugins.workflow.actions.ErrorAction$ErrorId: f3f36e14-75f5-48ec-a47e-32727371972b + java.lang.Exception: {"reason":"Unexpected IEE136I: IEE345I DISPLAY AUTHORITY INVALID, FAILED BY SECURITY PRODUCT","return-code":5,"reason-code":4} + ``` +* Successful Execution: + ``` + [Perform MVS command] - Issuing command : D A,L + CNZ4105I 12.45.44 DISPLAY ACTIVITY 535 + ... + [Perform MVS command] - The command has been successfully executed + ``` + + + ## 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 diff --git a/src/main/kotlin/org/zowe/zdevops/classic/steps/PerformMvsCommandStep.kt b/src/main/kotlin/org/zowe/zdevops/classic/steps/PerformMvsCommandStep.kt new file mode 100644 index 0000000..3c8fe8a --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/classic/steps/PerformMvsCommandStep.kt @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2024 IBA Group. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBA Group + * Zowe Community + */ + +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.performMvsCommand +import org.zowe.zdevops.utils.validateFieldIsNotEmpty + + +/** + * A Jenkins Pipeline step for performing a MVS command on a z/OS system via freestyle job. + */ +class PerformMvsCommandStep +/** + * Data-bound constructor for the {@code PerformMvsCommandStep} step. + * + * @param connectionName The name of the z/OS connection to be used for executing the MVS command. + * @param command The MVS command to be executed. + */ +@DataBoundConstructor +constructor( + connectionName: String, + val command: String, +) : AbstractBuildStep(connectionName) { + + /** + * Performs the MVS 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 MVS command. + */ + override fun perform( + build: AbstractBuild<*, *>, + launcher: Launcher, + listener: BuildListener, + zosConnection: ZOSConnection + ) { + performMvsCommand(zosConnection, listener, command) + } + + + /** + * Descriptor for the {@code PerformMvsCommandStep} 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_performMvsCommandStep_display_name()) { + + /** + * Performs form validation for the 'command' parameter to ensure it is not empty. + * + * @param command The MVS 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/declarative/jobs/PerformMvsCommandDeclarative.kt b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/PerformMvsCommandDeclarative.kt new file mode 100644 index 0000000..c21142e --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/PerformMvsCommandDeclarative.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 IBA Group. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBA Group + * Zowe Community + */ + +package org.zowe.zdevops.declarative.jobs + +import hudson.AbortException +import hudson.EnvVars +import hudson.Extension +import hudson.FilePath +import hudson.model.TaskListener +import org.kohsuke.stapler.DataBoundConstructor +import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection +import org.zowe.zdevops.declarative.AbstractZosmfActionWithResult +import org.zowe.zdevops.logic.performMvsCommand + +/** + * Class that represents an action to perform an MVS command with a result in a declarative pipeline. + * This class extends {@code AbstractZosmfActionWithResult} and is designed to execute an MVS command + * via Zowe z/OSMF and return the command's output. + * + * @param command the MVS command to be executed. + */ +class PerformMvsCommandDeclarative +@DataBoundConstructor +constructor( + val command: String, +) : AbstractZosmfActionWithResult() { + + override fun run( + workspace: FilePath, + listener: TaskListener, + envVars: EnvVars, + zoweZOSConnection: ZOSConnection + ): String { + return performMvsCommand(zoweZOSConnection, listener, command) + ?: throw AbortException("MVS command execution returned an empty result") + } + + @Extension + class DescriptorImpl : Companion.DefaultStepDescriptor(functionName = "performMvsCommand") +} diff --git a/src/main/kotlin/org/zowe/zdevops/declarative/jobs/PerformTsoCommandDeclarative.kt b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/PerformTsoCommandDeclarative.kt index f81f8c3..d7bd637 100644 --- a/src/main/kotlin/org/zowe/zdevops/declarative/jobs/PerformTsoCommandDeclarative.kt +++ b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/PerformTsoCommandDeclarative.kt @@ -50,5 +50,5 @@ constructor( } @Extension - class DescriptorImpl : Companion.DefaultStepDescriptor(functionName = "performTsoCommandWithResult") + class DescriptorImpl : Companion.DefaultStepDescriptor(functionName = "performTsoCommand") } diff --git a/src/main/kotlin/org/zowe/zdevops/declarative/jobs/SubmitJobSyncStepDeclarative.kt b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/SubmitJobSyncStepDeclarative.kt index 3086399..762b0ca 100644 --- a/src/main/kotlin/org/zowe/zdevops/declarative/jobs/SubmitJobSyncStepDeclarative.kt +++ b/src/main/kotlin/org/zowe/zdevops/declarative/jobs/SubmitJobSyncStepDeclarative.kt @@ -50,5 +50,5 @@ constructor(val fileToSubmit: String) } @Extension - class DescriptorImpl : Companion.DefaultStepDescriptor(functionName = "submitJobSyncWithResult") + class DescriptorImpl : Companion.DefaultStepDescriptor(functionName = "submitJobSync") } diff --git a/src/main/kotlin/org/zowe/zdevops/logic/PerformMvsCommandOperation.kt b/src/main/kotlin/org/zowe/zdevops/logic/PerformMvsCommandOperation.kt new file mode 100644 index 0000000..4bc59fd --- /dev/null +++ b/src/main/kotlin/org/zowe/zdevops/logic/PerformMvsCommandOperation.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2024 IBA Group. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBA Group + * Zowe Community + */ + +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.zosconsole.ConsoleResponse +import org.zowe.kotlinsdk.zowe.client.sdk.zosconsole.IssueCommand + +/** + * Executes an MVS command on a z/OS system using the provided z/OS connection. + * + * This function allows you to send an MVS command to a z/OS system, and capture the response + * + * @param zosConnection The z/OS connection through which the MVS command will be executed. + * @param listener The Jenkins build listener for logging and monitoring the execution. + * @param command The MVS command to be executed. + * @return the command output. + * @throws AbortException if the MVS command execution fails, with the error message indicating + * the reason for the failure. + */ +fun performMvsCommand( + zosConnection: ZOSConnection, + listener: TaskListener, + command: String, +): String? { + listener.logger.println("[Perform MVS command] - Issuing command : $command") + val commandResponseObj: ConsoleResponse + try { + commandResponseObj = IssueCommand(zosConnection).issueSimple(command) + listener.logger.println(commandResponseObj.commandResponse) + } catch (ex: Exception) { + listener.logger.println("[Perform MVS command] - MVS command execution failed") + throw ex + } + listener.logger.println("[Perform MVS command] - The command has been successfully executed") + return commandResponseObj.commandResponse +} \ No newline at end of file diff --git a/src/main/resources/org/zowe/zdevops/Messages.properties b/src/main/resources/org/zowe/zdevops/Messages.properties index 767350b..1f6d285 100644 --- a/src/main/resources/org/zowe/zdevops/Messages.properties +++ b/src/main/resources/org/zowe/zdevops/Messages.properties @@ -45,6 +45,7 @@ zdevops.classic.downloadDatasetStep.display.name=[z/OS] - Download dataset/membe 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.classic.performMvsCommandStep.display.name=[z/OS] - Perform MVS 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}. diff --git a/src/main/resources/org/zowe/zdevops/classic/steps/PerformMvsCommandStep/config.jelly b/src/main/resources/org/zowe/zdevops/classic/steps/PerformMvsCommandStep/config.jelly new file mode 100644 index 0000000..f76167f --- /dev/null +++ b/src/main/resources/org/zowe/zdevops/classic/steps/PerformMvsCommandStep/config.jelly @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/org/zowe/zdevops/classic/steps/PerformMvsCommandStep/config.properties b/src/main/resources/org/zowe/zdevops/classic/steps/PerformMvsCommandStep/config.properties new file mode 100644 index 0000000..bccc47d --- /dev/null +++ b/src/main/resources/org/zowe/zdevops/classic/steps/PerformMvsCommandStep/config.properties @@ -0,0 +1,2 @@ +zdevops.classic.connection.title=z/OS connection +zdevops.classic.command.title=MVS command to be executed diff --git a/src/test/kotlin/org/zowe/zdevops/declarative/jobs/PerformMvsCommandDeclarativeSpec.kt b/src/test/kotlin/org/zowe/zdevops/declarative/jobs/PerformMvsCommandDeclarativeSpec.kt new file mode 100644 index 0000000..450f97b --- /dev/null +++ b/src/test/kotlin/org/zowe/zdevops/declarative/jobs/PerformMvsCommandDeclarativeSpec.kt @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2024 IBA Group. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBA Group + * Zowe Community + */ + +package org.zowe.zdevops.classic.steps + +import hudson.EnvVars +import hudson.FilePath +import io.kotest.assertions.assertSoftly +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 org.zowe.zdevops.declarative.jobs.PerformMvsCommandDeclarative +import java.io.File +import java.io.PrintStream +import java.nio.file.Paths + +class PerformMvsCommandDeclarativeSpec : 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: PerformMvsCommandStep") { + val zosConnection = ZOSConnection(mockServer.hostName, mockServer.port.toString(), "test", "test", "https") + val trashDir = tempdir() + val trashDirWithInternal = Paths.get(trashDir.absolutePath, "test_name").toString() + val workspace = FilePath(File(trashDirWithInternal)) + val env = EnvVars() + + afterEach { + responseDispatcher.removeAllEndpoints() + } + should("perform PerformMvsCommandDeclarative operation and return its result") { + var isPreExecuteStage = false + var isCommandExecuted = false + val expectedPartialMvsDisplayActiveCommandOutput = "CNZ4105I 12.56.27 DISPLAY ACTIVITY" + 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 + } + } + + responseDispatcher.injectEndpoint( + this.testCase.name.testName, + { it?.requestLine?.contains("PUT /zosmf/restconsoles/consoles/") ?: false }, + { MockResponse() + .setResponseCode(200) + .setBody(responseDispatcher.readMockJson("displayActiveASCommandOutput") ?: "") } + ) + + val performMvsCommandInst = spyk( + PerformMvsCommandDeclarative("D A,L") + ) + val mvsCommandResult = performMvsCommandInst.run(workspace, taskListener, env, zosConnection) + + assertSoftly { mvsCommandResult shouldContain expectedPartialMvsDisplayActiveCommandOutput } + assertSoftly { isPreExecuteStage shouldBe true } + assertSoftly { isCommandExecuted shouldBe true } + } + } +}) \ No newline at end of file diff --git a/src/test/resources/mock/displayActiveASCommandOutput.json b/src/test/resources/mock/displayActiveASCommandOutput.json new file mode 100644 index 0000000..46870c7 --- /dev/null +++ b/src/test/resources/mock/displayActiveASCommandOutput.json @@ -0,0 +1,6 @@ +{ + "cmd-response-key": "C1977656", + "cmd-response-url": "https://192.168.1.1:10443/zosmf/restconsoles/consoles/defcn/solmsgs/C1977656", + "cmd-response-uri": "/zosmf/restconsoles/consoles/defcn/solmsgs/C1977656", + "cmd-response": " CNZ4105I 12.56.27 DISPLAY ACTIVITY 546\r JOBS M/S TS USERS SYSAS INITS ACTIVE/MAX VTAM OAS\r 00020 00040 00004 00034 00025 00002/00040 00041\r ..." +} \ No newline at end of file