Skip to content

Commit 95f7d6c

Browse files
authored
Implement Perform MVS command method (#90)
* remove 'WithResult' postfix Signed-off-by: Anatoli Kalbasin <qwnize@outlook.com> * implement PerformMvsCommand method Signed-off-by: Anatoli Kalbasin <qwnize@outlook.com> * add tests Signed-off-by: Anatoli Kalbasin <qwnize@outlook.com> * update license Signed-off-by: Anatoli Kalbasin <qwnize@outlook.com> * add docs Signed-off-by: Anatoli Kalbasin <qwnize@outlook.com> --------- Signed-off-by: Anatoli Kalbasin <qwnize@outlook.com>
1 parent 620a967 commit 95f7d6c

File tree

11 files changed

+350
-2
lines changed

11 files changed

+350
-2
lines changed

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,47 @@ deleteDataset dsn:"EXAMPLE.DATASET"
129129
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'.
130130
```
131131
132+
### `performMvsCommand` - Execute an MVS System Command
133+
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.
134+
135+
#### Usage:
136+
137+
The step can be specified in two ways:
138+
```groovy
139+
performMvsCommand "DISPLAY TIME"
140+
```
141+
or using the named parameter:
142+
```groovy
143+
performMvsCommand command: "DISPLAY TIME"
144+
```
145+
**Mandatory Parameters:** there is only one parameter - `command`.
146+
147+
#### Example - Displaying Active Units of Work:
148+
To display detailed information about all active units of work, use the following command:
149+
```groovy
150+
def active_units = performMvsCommand "D A,L"
151+
```
152+
153+
#### Expected behavior under various scenarios:
154+
155+
* Insufficient Authorization: you are not authorized to issue the command:
156+
```
157+
[Perform MVS command] - Issuing command : D T
158+
[Perform MVS command] - MVS command execution failed
159+
160+
Also: org.jenkinsci.plugins.workflow.actions.ErrorAction$ErrorId: f3f36e14-75f5-48ec-a47e-32727371972b
161+
java.lang.Exception: {"reason":"Unexpected IEE136I: IEE345I DISPLAY AUTHORITY INVALID, FAILED BY SECURITY PRODUCT","return-code":5,"reason-code":4}
162+
```
163+
* Successful Execution:
164+
```
165+
[Perform MVS command] - Issuing command : D A,L
166+
CNZ4105I 12.45.44 DISPLAY ACTIVITY 535
167+
...
168+
[Perform MVS command] - The command has been successfully executed
169+
```
170+
171+
172+
132173
## Use case example
133174
Here you can find an example of a minimal declarative Jenkins pipeline for execution, testing and further modification for your personal needs.
134175
Pipeline can be used either directly inside the ```Pipeline``` code block in the Jenkins server, or in a ```Jenkinsfile``` stored in Git
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright (c) 2024 IBA Group.
3+
*
4+
* This program and the accompanying materials are made available under the terms of the
5+
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v20.html
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* IBA Group
12+
* Zowe Community
13+
*/
14+
15+
package org.zowe.zdevops.classic.steps
16+
17+
import hudson.Extension
18+
import hudson.Launcher
19+
import hudson.model.AbstractBuild
20+
import hudson.model.BuildListener
21+
import hudson.util.FormValidation
22+
import org.kohsuke.stapler.DataBoundConstructor
23+
import org.kohsuke.stapler.QueryParameter
24+
import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection
25+
import org.zowe.zdevops.Messages
26+
import org.zowe.zdevops.classic.AbstractBuildStep
27+
import org.zowe.zdevops.logic.performMvsCommand
28+
import org.zowe.zdevops.utils.validateFieldIsNotEmpty
29+
30+
31+
/**
32+
* A Jenkins Pipeline step for performing a MVS command on a z/OS system via freestyle job.
33+
*/
34+
class PerformMvsCommandStep
35+
/**
36+
* Data-bound constructor for the {@code PerformMvsCommandStep} step.
37+
*
38+
* @param connectionName The name of the z/OS connection to be used for executing the MVS command.
39+
* @param command The MVS command to be executed.
40+
*/
41+
@DataBoundConstructor
42+
constructor(
43+
connectionName: String,
44+
val command: String,
45+
) : AbstractBuildStep(connectionName) {
46+
47+
/**
48+
* Performs the MVS command execution step within a Jenkins Pipeline build.
49+
*
50+
* @param build The current Jenkins build.
51+
* @param launcher The build launcher.
52+
* @param listener The build listener.
53+
* @param zosConnection The z/OS connection to execute the MVS command.
54+
*/
55+
override fun perform(
56+
build: AbstractBuild<*, *>,
57+
launcher: Launcher,
58+
listener: BuildListener,
59+
zosConnection: ZOSConnection
60+
) {
61+
performMvsCommand(zosConnection, listener, command)
62+
}
63+
64+
65+
/**
66+
* Descriptor for the {@code PerformMvsCommandStep} step.
67+
*
68+
* This descriptor provides information about the step and makes it available for use
69+
* within Jenkins Pipelines.
70+
*/
71+
@Extension
72+
class DescriptorImpl : Companion.DefaultBuildDescriptor(Messages.zdevops_classic_performMvsCommandStep_display_name()) {
73+
74+
/**
75+
* Performs form validation for the 'command' parameter to ensure it is not empty.
76+
*
77+
* @param command The MVS command field value to validate.
78+
* @return A {@link FormValidation} object indicating whether the field is valid or contains an error.
79+
*/
80+
fun doCheckCommand(@QueryParameter command: String): FormValidation? {
81+
return validateFieldIsNotEmpty(command)
82+
}
83+
84+
}
85+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright (c) 2024 IBA Group.
3+
*
4+
* This program and the accompanying materials are made available under the terms of the
5+
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v20.html
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* IBA Group
12+
* Zowe Community
13+
*/
14+
15+
package org.zowe.zdevops.declarative.jobs
16+
17+
import hudson.AbortException
18+
import hudson.EnvVars
19+
import hudson.Extension
20+
import hudson.FilePath
21+
import hudson.model.TaskListener
22+
import org.kohsuke.stapler.DataBoundConstructor
23+
import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection
24+
import org.zowe.zdevops.declarative.AbstractZosmfActionWithResult
25+
import org.zowe.zdevops.logic.performMvsCommand
26+
27+
/**
28+
* Class that represents an action to perform an MVS command with a result in a declarative pipeline.
29+
* This class extends {@code AbstractZosmfActionWithResult} and is designed to execute an MVS command
30+
* via Zowe z/OSMF and return the command's output.
31+
*
32+
* @param command the MVS command to be executed.
33+
*/
34+
class PerformMvsCommandDeclarative
35+
@DataBoundConstructor
36+
constructor(
37+
val command: String,
38+
) : AbstractZosmfActionWithResult() {
39+
40+
override fun run(
41+
workspace: FilePath,
42+
listener: TaskListener,
43+
envVars: EnvVars,
44+
zoweZOSConnection: ZOSConnection
45+
): String {
46+
return performMvsCommand(zoweZOSConnection, listener, command)
47+
?: throw AbortException("MVS command execution returned an empty result")
48+
}
49+
50+
@Extension
51+
class DescriptorImpl : Companion.DefaultStepDescriptor(functionName = "performMvsCommand")
52+
}

src/main/kotlin/org/zowe/zdevops/declarative/jobs/PerformTsoCommandDeclarative.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,5 @@ constructor(
5050
}
5151

5252
@Extension
53-
class DescriptorImpl : Companion.DefaultStepDescriptor(functionName = "performTsoCommandWithResult")
53+
class DescriptorImpl : Companion.DefaultStepDescriptor(functionName = "performTsoCommand")
5454
}

src/main/kotlin/org/zowe/zdevops/declarative/jobs/SubmitJobSyncStepDeclarative.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,5 @@ constructor(val fileToSubmit: String)
5050
}
5151

5252
@Extension
53-
class DescriptorImpl : Companion.DefaultStepDescriptor(functionName = "submitJobSyncWithResult")
53+
class DescriptorImpl : Companion.DefaultStepDescriptor(functionName = "submitJobSync")
5454
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright (c) 2024 IBA Group.
3+
*
4+
* This program and the accompanying materials are made available under the terms of the
5+
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v20.html
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* IBA Group
12+
* Zowe Community
13+
*/
14+
15+
package org.zowe.zdevops.logic
16+
17+
import hudson.AbortException
18+
import hudson.model.TaskListener
19+
import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection
20+
import org.zowe.kotlinsdk.zowe.client.sdk.zosconsole.ConsoleResponse
21+
import org.zowe.kotlinsdk.zowe.client.sdk.zosconsole.IssueCommand
22+
23+
/**
24+
* Executes an MVS command on a z/OS system using the provided z/OS connection.
25+
*
26+
* This function allows you to send an MVS command to a z/OS system, and capture the response
27+
*
28+
* @param zosConnection The z/OS connection through which the MVS command will be executed.
29+
* @param listener The Jenkins build listener for logging and monitoring the execution.
30+
* @param command The MVS command to be executed.
31+
* @return the command output.
32+
* @throws AbortException if the MVS command execution fails, with the error message indicating
33+
* the reason for the failure.
34+
*/
35+
fun performMvsCommand(
36+
zosConnection: ZOSConnection,
37+
listener: TaskListener,
38+
command: String,
39+
): String? {
40+
listener.logger.println("[Perform MVS command] - Issuing command : $command")
41+
val commandResponseObj: ConsoleResponse
42+
try {
43+
commandResponseObj = IssueCommand(zosConnection).issueSimple(command)
44+
listener.logger.println(commandResponseObj.commandResponse)
45+
} catch (ex: Exception) {
46+
listener.logger.println("[Perform MVS command] - MVS command execution failed")
47+
throw ex
48+
}
49+
listener.logger.println("[Perform MVS command] - The command has been successfully executed")
50+
return commandResponseObj.commandResponse
51+
}

src/main/resources/org/zowe/zdevops/Messages.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ zdevops.classic.downloadDatasetStep.display.name=[z/OS] - Download dataset/membe
4545
zdevops.classic.deleteDatasetStep.display.name=[z/OS] - Delete dataset/member
4646
zdevops.classic.deleteDatasetsByMaskStep.display.name=[z/OS] - Delete datasets by mask
4747
zdevops.classic.performTsoCommandStep.display.name=[z/OS] - Perform TSO command
48+
zdevops.classic.performMvsCommandStep.display.name=[z/OS] - Perform MVS command
4849
4950
zdevops.declarative.ZOSJobs.submitting=Submitting a JOB from file {0} with connection: {1}:{2}
5051
zdevops.declarative.ZOSJobs.submitted.success=JOB submitted successfully. JOBID={0}, JOBNAME={1}, OWNER={2}.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<?jelly escape-by-default='true'?>
4+
5+
<jelly:jelly xmlns:jelly="jelly:core" xmlns:form="/lib/form">
6+
<form:entry field="connectionName" title="${%zdevops.classic.connection.title}">
7+
<form:select/>
8+
</form:entry>
9+
<form:entry field="command" title="${%zdevops.classic.command.title}">
10+
<form:textbox/>
11+
</form:entry>
12+
</jelly:jelly>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
zdevops.classic.connection.title=z/OS connection
2+
zdevops.classic.command.title=MVS command to be executed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright (c) 2024 IBA Group.
3+
*
4+
* This program and the accompanying materials are made available under the terms of the
5+
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v20.html
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* IBA Group
12+
* Zowe Community
13+
*/
14+
15+
package org.zowe.zdevops.classic.steps
16+
17+
import hudson.EnvVars
18+
import hudson.FilePath
19+
import io.kotest.assertions.assertSoftly
20+
import io.kotest.core.spec.style.ShouldSpec
21+
import io.kotest.engine.spec.tempdir
22+
import io.kotest.matchers.shouldBe
23+
import io.kotest.matchers.string.shouldContain
24+
import io.mockk.every
25+
import io.mockk.mockk
26+
import io.mockk.spyk
27+
import okhttp3.mockwebserver.MockResponse
28+
import okhttp3.mockwebserver.MockWebServer
29+
import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection
30+
import org.zowe.zdevops.MOCK_SERVER_HOST
31+
import org.zowe.zdevops.MockResponseDispatcher
32+
import org.zowe.zdevops.MockServerFactory
33+
import org.zowe.zdevops.declarative.jobs.PerformMvsCommandDeclarative
34+
import java.io.File
35+
import java.io.PrintStream
36+
import java.nio.file.Paths
37+
38+
class PerformMvsCommandDeclarativeSpec : ShouldSpec({
39+
lateinit var mockServer: MockWebServer
40+
lateinit var responseDispatcher: MockResponseDispatcher
41+
val mockServerFactory = MockServerFactory()
42+
43+
beforeSpec {
44+
mockServer = mockServerFactory.startMockServer(MOCK_SERVER_HOST)
45+
responseDispatcher = mockServerFactory.responseDispatcher
46+
}
47+
afterSpec {
48+
mockServerFactory.stopMockServer()
49+
}
50+
context("declarative/jobs module: PerformMvsCommandStep") {
51+
val zosConnection = ZOSConnection(mockServer.hostName, mockServer.port.toString(), "test", "test", "https")
52+
val trashDir = tempdir()
53+
val trashDirWithInternal = Paths.get(trashDir.absolutePath, "test_name").toString()
54+
val workspace = FilePath(File(trashDirWithInternal))
55+
val env = EnvVars()
56+
57+
afterEach {
58+
responseDispatcher.removeAllEndpoints()
59+
}
60+
should("perform PerformMvsCommandDeclarative operation and return its result") {
61+
var isPreExecuteStage = false
62+
var isCommandExecuted = false
63+
val expectedPartialMvsDisplayActiveCommandOutput = "CNZ4105I 12.56.27 DISPLAY ACTIVITY"
64+
val taskListener = object : TestBuildListener() {
65+
override fun getLogger(): PrintStream {
66+
val logger = mockk<PrintStream>()
67+
every {
68+
logger.println(any<String>())
69+
} answers {
70+
if (firstArg<String>().contains("Issuing command")) {
71+
isPreExecuteStage = true
72+
} else if (firstArg<String>().contains("The command has been successfully executed")) {
73+
isCommandExecuted = true
74+
}
75+
}
76+
return logger
77+
}
78+
}
79+
80+
responseDispatcher.injectEndpoint(
81+
this.testCase.name.testName,
82+
{ it?.requestLine?.contains("PUT /zosmf/restconsoles/consoles/") ?: false },
83+
{ MockResponse()
84+
.setResponseCode(200)
85+
.setBody(responseDispatcher.readMockJson("displayActiveASCommandOutput") ?: "") }
86+
)
87+
88+
val performMvsCommandInst = spyk(
89+
PerformMvsCommandDeclarative("D A,L")
90+
)
91+
val mvsCommandResult = performMvsCommandInst.run(workspace, taskListener, env, zosConnection)
92+
93+
assertSoftly { mvsCommandResult shouldContain expectedPartialMvsDisplayActiveCommandOutput }
94+
assertSoftly { isPreExecuteStage shouldBe true }
95+
assertSoftly { isCommandExecuted shouldBe true }
96+
}
97+
}
98+
})
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"cmd-response-key": "C1977656",
3+
"cmd-response-url": "https://192.168.1.1:10443/zosmf/restconsoles/consoles/defcn/solmsgs/C1977656",
4+
"cmd-response-uri": "/zosmf/restconsoles/consoles/defcn/solmsgs/C1977656",
5+
"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 ..."
6+
}

0 commit comments

Comments
 (0)