Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Perform MVS command method #90

Merged
merged 5 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}

}
}
Original file line number Diff line number Diff line change
@@ -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")
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,5 @@ constructor(
}

@Extension
class DescriptorImpl : Companion.DefaultStepDescriptor(functionName = "performTsoCommandWithResult")
class DescriptorImpl : Companion.DefaultStepDescriptor(functionName = "performTsoCommand")
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,5 @@ constructor(val fileToSubmit: String)
}

@Extension
class DescriptorImpl : Companion.DefaultStepDescriptor(functionName = "submitJobSyncWithResult")
class DescriptorImpl : Companion.DefaultStepDescriptor(functionName = "submitJobSync")
}
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions src/main/resources/org/zowe/zdevops/Messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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}.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>

<?jelly escape-by-default='true'?>

<jelly:jelly xmlns:jelly="jelly:core" xmlns:form="/lib/form">
<form:entry field="connectionName" title="${%zdevops.classic.connection.title}">
<form:select/>
</form:entry>
<form:entry field="command" title="${%zdevops.classic.command.title}">
<form:textbox/>
</form:entry>
</jelly:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
zdevops.classic.connection.title=z/OS connection
zdevops.classic.command.title=MVS command to be executed
Original file line number Diff line number Diff line change
@@ -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<PrintStream>()
every {
logger.println(any<String>())
} answers {
if (firstArg<String>().contains("Issuing command")) {
isPreExecuteStage = true
} else if (firstArg<String>().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 }
}
}
})
6 changes: 6 additions & 0 deletions src/test/resources/mock/displayActiveASCommandOutput.json
Original file line number Diff line number Diff line change
@@ -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 ..."
}