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 4 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
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 LLA LLA LLA NSW S JES2 JES2 IEFPROC NSW S\r VLF VLF VLF NSW S VTAM VTAM VTAM NSW S\r DLF DLF DLF NSW S RACF RACF RACF NSW S\r RRS RRS RRS NSW S TSO TSO STEP1 OWT S\r SDSF SDSF SDSF NSW S TCPIP TCPIP TCPIP NSW SO\r TN3270 TN3270 TN3270 NSW SO DBCGMSTR DBCGMSTR IEFPROC NSW S\r SDSFAUX SDSFAUX SDSFAUX NSW S HTTPD1 HTTPD1 *OMVSEX OWT SO\r NFSS NFSS GFSAMAIN NSW SO CSF CSF CSF NSW S\r HZSPROC HZSPROC HZSSTEP NSW SO DBCGIRLM DBCGIRLM NSW S\r DBCGDBM1 DBCGDBM1 IEFPROC NSW S CICSTS55 CICSTS55 CICS NSW SO\r IMS15RL1 IMS15RL1 DXRJPROC NSW S DBCGDIST DBCGDIST IEFPROC NSW SO\r IMS15CR1 IMS15CR1 IEFPROC NSW S DBCGADMT DBCGADMT STARTADM IN SO\r CSQ9MSTR CSQ9MSTR PROCSTEP NSW S CSQ9CHIN CSQ9CHIN PROCSTEP IN SO\r IMS15DL1 IMS15DL1 IEFPROC NSW S IMS15RC1 IMS15RC1 IEFPROC NSW S\r ASCH ASCH ASCH NSW S JMON JMON JMON OWT SO\r RSED RSED RSED OWT SO FTPD1 STEP1 FTPD OWT AO\r CFZCIM CFZCIM *OMVSEX OWT SO PAGENT PAGENT PAGENT OWT SO\r PORTMAP PORTMAP PMAP OWT SO SSHD3 STEP1 START1 OWT AO\r HTTPD13 STEP1 WEBSRV OWT AO BUZAGNT BUZAGNT BUZAGNT OWT SO\r ZOSCSRV ZOSCSRV ZCONNECT IN SO HTTPD19 STEP1 WEBSRV IN AO\r BUZAGNT1 *OMVSEX START1 IN AO BLZBFA3 STEP1 STCBFA OWT AO\r HTTPD11 STEP1 WEBSRV IN AO HTTPD12 STEP1 WEBSRV OWT AO\r BUZAGNT3 *OMVSEX START1 IN AO IMS15M11 IMS15M11 REGION NSW J\r IMS15F11 IMS15F11 IFP NSW J IMS15F12 IMS15F12 IFP NSW J\r IMS15F13 IMS15F13 IFP NSW J IMS15J11 IMS15J11 JMPRGN NSW JO\r RSED9 STEP1 STCRSE OWT AO RSED1 STEP1 STCRSE OWT AO\r RSED1 STEP1 STCRSE OWT AO RSED2 STEP1 STCRSE OWT AO\r RSED3 STEP1 STCRSE OWT AO RSED4 STEP1 STCRSE OWT AO\r APPC APPC APPC NSW S RMF RMF IEFPROC NSW S\r RMFGAT RMFGAT IEFPROC NSW SO AUTOMAN AUTOMAN AUTOMAN NSW S\r CICADM OWT KIRJU OWT KBA OWT O KBA IN O"
}