├── modules
├── lib-common
│ ├── src
│ │ └── main
│ │ │ ├── resources
│ │ │ └── VERSION
│ │ │ └── java
│ │ │ └── com
│ │ │ └── sap
│ │ │ └── cmclient
│ │ │ ├── Transport.java
│ │ │ └── VersionHelper.java
│ └── pom.xml
├── dist.cli
│ ├── src
│ │ ├── test
│ │ │ └── bash
│ │ │ │ ├── testsWithoutFootprint
│ │ │ │ ├── testPrintVersionReturnsStringContainingGitCommitId.sh
│ │ │ │ ├── testGetStatusReturnsTrueForOpenChangeDocument.sh
│ │ │ │ └── testLogonWithInvalidCredentialsFails.sh
│ │ │ │ ├── README.txt
│ │ │ │ ├── runTestWithoutFootprint.sh
│ │ │ │ └── prepare.sh
│ │ └── main
│ │ │ ├── assembly
│ │ │ └── dist.xml
│ │ │ └── resources
│ │ │ └── bin
│ │ │ └── cmclient
│ └── pom.xml
├── cli
│ ├── src
│ │ ├── main
│ │ │ └── java
│ │ │ │ └── sap
│ │ │ │ └── prd
│ │ │ │ └── cmintegration
│ │ │ │ └── cli
│ │ │ │ ├── package-info.java
│ │ │ │ ├── CommandDescriptor.java
│ │ │ │ ├── CMCommandLineException.java
│ │ │ │ ├── TransportNotFoundException.java
│ │ │ │ ├── ExitWrapper.java
│ │ │ │ ├── SolmanClientFactory.java
│ │ │ │ ├── GetTransportOwnerSOLMAN.java
│ │ │ │ ├── GetTransportDescriptionSOLMAN.java
│ │ │ │ ├── Command.java
│ │ │ │ ├── GetTransportModifiableSOLMAN.java
│ │ │ │ ├── ExitException.java
│ │ │ │ ├── GetTransportDevelopmentSystemSOLMAN.java
│ │ │ │ ├── ReleaseTransport.java
│ │ │ │ ├── GetChangeStatus.java
│ │ │ │ ├── UploadFileToTransportSOLMAN.java
│ │ │ │ ├── CreateTransportSOLMAN.java
│ │ │ │ ├── GetChangeTransports.java
│ │ │ │ └── TransportRelatedSOLMAN.java
│ │ └── test
│ │ │ └── java
│ │ │ └── sap
│ │ │ └── prd
│ │ │ └── cmintegration
│ │ │ └── cli
│ │ │ ├── CMSolmanTestBase.java
│ │ │ ├── SolManBackendGetChangeTransportOwnerTest.java
│ │ │ ├── SolManBackendGetChangeTransportDescriptionTest.java
│ │ │ ├── SolManBackendGetChangeTransportDevelopmentSystemTest.java
│ │ │ ├── Matchers.java
│ │ │ ├── CommandsHelpersTest.java
│ │ │ ├── CMTestBase.java
│ │ │ ├── SolManBackendCMTransportTestBase.java
│ │ │ ├── SolManBackendReleaseTransportTest.java
│ │ │ ├── SolManBackendUploadFileToTransportTest.java
│ │ │ ├── SolManBackendGetChangeTransportModifiableTest.java
│ │ │ ├── SolManBackendGetChangeTransportsTest.java
│ │ │ ├── SolManBackendCreateTransportTest.java
│ │ │ ├── CommandsTest.java
│ │ │ └── SolManBackendGetChangeStatusTest.java
│ └── pom.xml
├── lib-solman
│ ├── src
│ │ ├── test
│ │ │ └── java
│ │ │ │ └── sap
│ │ │ │ └── ai
│ │ │ │ └── st
│ │ │ │ └── cm
│ │ │ │ └── plugins
│ │ │ │ └── ciintegration
│ │ │ │ └── odataclient
│ │ │ │ ├── MockHelper.java
│ │ │ │ ├── CMOdataHTTPFactoryTest.java
│ │ │ │ ├── Matchers.java
│ │ │ │ ├── CMODataClientBaseTest.java
│ │ │ │ ├── CMODataClientTransportMarshallingTest.java
│ │ │ │ ├── CMODataClientReleaseTransportTest.java
│ │ │ │ ├── CMODataClientChangesTest.java
│ │ │ │ ├── CMODataClientGetTransportsTest.java
│ │ │ │ └── CMODataClientFileUploadTest.java
│ │ └── main
│ │ │ └── java
│ │ │ └── sap
│ │ │ └── ai
│ │ │ └── st
│ │ │ └── cm
│ │ │ └── plugins
│ │ │ └── ciintegration
│ │ │ └── odataclient
│ │ │ ├── CMODataChange.java
│ │ │ ├── CMODataClientException.java
│ │ │ ├── CMOdataHTTPFactory.java
│ │ │ └── CMODataTransport.java
│ └── pom.xml
└── testutils
│ ├── pom.xml
│ └── src
│ └── main
│ └── java
│ └── com
│ └── sap
│ └── cmclient
│ └── Matchers.java
├── .gitignore
├── .pipeline
└── config.yml
├── cfg
└── settings.xml
├── .github
└── workflows
│ ├── release-notes.yml
│ ├── maven-build-test-master.yml
│ └── build-publish-release.yml
├── RELEASES.md
├── CONTRIBUTING.md
├── .reuse
└── dep5
├── README.md
├── pom.xml
└── LICENSES
└── Apache-2.0.txt
/modules/lib-common/src/main/resources/VERSION:
--------------------------------------------------------------------------------
1 | mvnProjectVersion=${project.version}
2 | gitCommitId=${git.commit.id}
3 | olingoVersionV2=${olingo.v2.version}
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | /bin/
3 | .idea
4 | /modules/cli/ci-integration-cli.iml
5 | /modules/jenkins-plugin/ci-integration.iml
6 | /modules/lib/ci-integration-lib.iml
7 | .project
8 | .settings
9 | .classpath
10 |
--------------------------------------------------------------------------------
/modules/lib-common/src/main/java/com/sap/cmclient/Transport.java:
--------------------------------------------------------------------------------
1 | package com.sap.cmclient;
2 |
3 | public interface Transport {
4 |
5 | String getTransportID();
6 |
7 | Boolean isModifiable();
8 |
9 | String getDescription();
10 |
11 | String getOwner();
12 | }
13 |
--------------------------------------------------------------------------------
/modules/dist.cli/src/test/bash/testsWithoutFootprint/testPrintVersionReturnsStringContainingGitCommitId.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | . ./prepare.sh
4 |
5 | VERSION=`${CM_CLIENT_HOME}/bin/cmclient --version`
6 |
7 | rc=$?
8 |
9 | if [ ${rc} != 0 ];then
10 | exit ${rc}
11 | fi
12 |
13 | echo $VERSION |grep -e "^.* : [0-9,a-f]\{40\}$" > /dev/null
14 |
15 |
--------------------------------------------------------------------------------
/.pipeline/config.yml:
--------------------------------------------------------------------------------
1 | steps:
2 | githubPublishRelease:
3 | addClosedIssues: true
4 | addDeltaToLastRelease: true
5 | excludeLabels:
6 | - 'discussion'
7 | - 'duplicate'
8 | - 'invalid'
9 | - 'question'
10 | - 'wontfix'
11 | - 'stale'
12 | owner: 'SAP'
13 | repository: 'devops-cm-client'
14 | releaseBodyHeader: ''
15 |
--------------------------------------------------------------------------------
/modules/dist.cli/src/test/bash/README.txt:
--------------------------------------------------------------------------------
1 | This folder contains some tests using the CM_CLIENT from the command line.
2 |
3 | In order to run the tests execute './runTestWithoutFootprint.sh'.
4 |
5 | The tests are not executed during the maven build since the test relies on
6 | a backend which must be available in order to run the tests.
7 |
8 | The tests are intened for being triggered manually.
9 |
--------------------------------------------------------------------------------
/modules/cli/src/main/java/sap/prd/cmintegration/cli/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Contains a command line client for connecting to SAP Solution
3 | * Manager. The classes contained in this package are not
4 | * intended for being reused as an API.
5 | * For more details how to use the command line client use
6 | * <CLIENT_HOME>/bin/cmclient --help
7 | */
8 | package sap.prd.cmintegration.cli;
9 |
--------------------------------------------------------------------------------
/modules/dist.cli/src/test/bash/testsWithoutFootprint/testGetStatusReturnsTrueForOpenChangeDocument.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | . ./prepare.sh
4 |
5 | IS_IN_DEVELOPMENT=`${CM_CLIENT_HOME}/bin/cmclient \
6 | -e ${CM_ENDPOINT} \
7 | -u ${CM_USER} \
8 | -p ${CM_PASSWORD} \
9 | is-change-in-development -cID 8000038673`
10 |
11 | rc=$?
12 | if [ ${rc} == 2 ];then
13 | exit ${rc}
14 | fi
15 |
16 | test "${IS_IN_DEVELOPMENT}" = "true"
17 |
--------------------------------------------------------------------------------
/modules/cli/src/main/java/sap/prd/cmintegration/cli/CommandDescriptor.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * Contains the name of the command as it is used from the command line.
10 | */
11 | @Retention(RetentionPolicy.RUNTIME)
12 | @Target(ElementType.TYPE)
13 | @interface CommandDescriptor {
14 | String name();
15 | }
16 |
--------------------------------------------------------------------------------
/modules/dist.cli/src/test/bash/runTestWithoutFootprint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | for f in `ls testsWithoutFootprint/test* |sort`;do
4 | TESTNAME=`echo $f |sed -e 's/.*\///g' -e 's/\.sh$//g'`
5 | printf 'Running test: %-60s: ' $TESTNAME
6 | bash $f
7 | rc=$?
8 | if [ ${rc} == 0 ];then
9 | printf "SUCCESS\n"
10 | else
11 | printf "FAILED\n"
12 | fi
13 | if [ ${rc} == 2 ];then
14 | echo "Wrong password provided. Stopping test execution."
15 | echo "Otherwise user ${CM_USER} will be locked."
16 | break
17 | fi
18 | done
19 |
--------------------------------------------------------------------------------
/modules/dist.cli/src/test/bash/testsWithoutFootprint/testLogonWithInvalidCredentialsFails.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | . ./prepare.sh
4 |
5 | EXPECTED_RC=2
6 |
7 | STDERR_OUTPUT=`${CM_CLIENT_HOME}/bin/cmclient \
8 | -e ${CM_ENDPOINT} \
9 | -u DOES_NOT_EXIST \
10 | -p WRONG \
11 | is-change-in-development -cID 8000038673 2>&1 1>/dev/null`
12 |
13 | rc=$?
14 |
15 | if [ ${rc} != ${EXPECTED_RC} ];then
16 | echo "Invalid return code received: '${rc}'. Should be '${EXPECTED_RC}'."
17 | exit 1
18 | fi
19 |
20 | echo $STDERR_OUTPUT |grep "401" > /dev/null
21 |
--------------------------------------------------------------------------------
/modules/lib-solman/src/test/java/sap/ai/st/cm/plugins/ciintegration/odataclient/MockHelper.java:
--------------------------------------------------------------------------------
1 | package sap.ai.st.cm.plugins.ciintegration.odataclient;
2 |
3 | import org.apache.olingo.client.api.Configuration;
4 | import org.apache.olingo.client.core.ConfigurationImpl;
5 |
6 | public class MockHelper {
7 |
8 | private final static Configuration config = new ConfigurationImpl();
9 |
10 | static {
11 | config.setKeyAsSegment(false); // with that we get .../Changes(''), otherwise .../Changes/''
12 | }
13 |
14 | public final static Configuration getConfiguration() {
15 | return config;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/modules/cli/src/test/java/sap/prd/cmintegration/cli/CMSolmanTestBase.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import java.lang.reflect.Field;
4 |
5 | import org.junit.After;
6 |
7 | public class CMSolmanTestBase extends CMTestBase {
8 |
9 | @After
10 | public void tearDown() throws Exception {
11 | System.setOut(oldOut);
12 | setMock(null);
13 | }
14 |
15 | protected static void setMock(SolmanClientFactory mock) throws Exception {
16 | Field field = SolmanClientFactory.class.getDeclaredField("instance");
17 | field.setAccessible(true);
18 | field.set(null, mock);
19 | field.setAccessible(false);
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/cfg/settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ossrh
5 | ${env.OSSRH_JIRA_USERNAME}
6 | ${env.OSSRH_JIRA_PASSWORD}
7 |
8 |
9 | ossrh-snapshots
10 | ${env.OSSRH_JIRA_USERNAME}
11 | ${env.OSSRH_JIRA_PASSWORD}
12 |
13 |
14 |
15 |
16 | signing
17 |
18 | gpg
19 | ${env.GPG_PASSPHRASE}
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/modules/lib-solman/src/main/java/sap/ai/st/cm/plugins/ciintegration/odataclient/CMODataChange.java:
--------------------------------------------------------------------------------
1 | package sap.ai.st.cm.plugins.ciintegration.odataclient;
2 |
3 | /**
4 | * Data transfer Object representing a Change.
5 | */
6 | public class CMODataChange {
7 |
8 | private final String ChangeID;
9 |
10 | public String getChangeID() {
11 | return ChangeID;
12 | }
13 |
14 | public boolean isInDevelopment() {
15 | return isInDevelopment;
16 | }
17 | private final boolean isInDevelopment;
18 |
19 | public CMODataChange(String ChangeID, boolean isInDevelopment){
20 |
21 | this.ChangeID = ChangeID;
22 | this.isInDevelopment = isInDevelopment;
23 |
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/modules/cli/src/main/java/sap/prd/cmintegration/cli/CMCommandLineException.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | /**
4 | * Root for all exceptions related to the command line client in the
5 | * narrower sense.
6 | */
7 | class CMCommandLineException extends RuntimeException {
8 |
9 | private static final long serialVersionUID = 5251372712902531523L;
10 |
11 | CMCommandLineException() {
12 | this((String)null);
13 | }
14 |
15 | CMCommandLineException(String message) {
16 | this(message, null);
17 | }
18 |
19 | CMCommandLineException(Throwable cause) {
20 | this(null, cause);
21 | }
22 |
23 | CMCommandLineException(String message, Throwable cause) {
24 | super(message, cause);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/modules/lib-solman/src/test/java/sap/ai/st/cm/plugins/ciintegration/odataclient/CMOdataHTTPFactoryTest.java:
--------------------------------------------------------------------------------
1 | package sap.ai.st.cm.plugins.ciintegration.odataclient;
2 |
3 | import static org.hamcrest.Matchers.containsString;
4 | import static org.junit.Assert.assertThat;
5 |
6 | import java.net.URI;
7 |
8 | import org.apache.http.client.HttpClient;
9 | import org.apache.http.params.CoreProtocolPNames;
10 | import org.apache.olingo.commons.api.http.HttpMethod;
11 | import org.junit.Test;
12 |
13 | public class CMOdataHTTPFactoryTest {
14 |
15 | @Test
16 | public void testUserAgentStringContainsCMClientHint() throws Exception {
17 | HttpClient httpClient = new CMOdataHTTPFactory("me", "*****").create(HttpMethod.GET, new URI("http://example.org"));
18 | assertThat((String)httpClient.getParams().getParameter(CoreProtocolPNames.USER_AGENT),
19 | containsString("SAP CM Client"));
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/modules/cli/src/main/java/sap/prd/cmintegration/cli/TransportNotFoundException.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | public class TransportNotFoundException extends CMCommandLineException {
4 |
5 | private static final long serialVersionUID = 7378231344272008562L;
6 | private final String transportId;
7 |
8 | public TransportNotFoundException(String transportId) {
9 | this(transportId, (String)null);
10 | }
11 |
12 | public TransportNotFoundException(String transportId, String message) {
13 | this(transportId, message, null);
14 | }
15 |
16 | public TransportNotFoundException(String transportId, Throwable cause) {
17 | this(transportId, (String) null ,cause);
18 | }
19 |
20 | public TransportNotFoundException(String transportId, String message, Throwable cause) {
21 | super(message, cause);
22 | this.transportId = transportId;
23 | }
24 |
25 | public String getTransportId() {
26 | return transportId;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.github/workflows/release-notes.yml:
--------------------------------------------------------------------------------
1 | name: Release Notes
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | tag:
7 | description: 'Define name for new Tag'
8 | required: true
9 | branch:
10 | description: 'Choose Branch to release'
11 | required: true
12 | type: choice
13 | options:
14 | - master
15 | - release
16 | - docker
17 |
18 | jobs:
19 | build:
20 | name: Publish
21 | runs-on: ubuntu-latest
22 | steps:
23 | - name: Step 1 - Checkout Repository from GitHub
24 | uses: actions/checkout@v1
25 |
26 | - name: Step 2 - Setup JDK 8
27 | uses: actions/setup-java@v1
28 | with:
29 | java-version: '8'
30 |
31 | - name: Step 3 - Publish Release
32 | uses: SAP/project-piper-action@master
33 | with:
34 | piper-version: master
35 | command: githubPublishRelease
36 | flags: --token ${{ secrets.GITHUB_TOKEN }} --version ${{ github.event.inputs.tag }} --commitish ${{ github.event.inputs.branch }}
--------------------------------------------------------------------------------
/modules/lib-solman/src/main/java/sap/ai/st/cm/plugins/ciintegration/odataclient/CMODataClientException.java:
--------------------------------------------------------------------------------
1 | package sap.ai.st.cm.plugins.ciintegration.odataclient;
2 |
3 | /**
4 | * Root of the exception hierarchy for all exceptions specific
5 | * to the CMODataClient.
6 | */
7 | public class CMODataClientException extends Exception {
8 |
9 | private static final long serialVersionUID = -404548464645983071L;
10 |
11 | public CMODataClientException() {
12 | this((String)null);
13 | }
14 |
15 | public CMODataClientException(String message) {
16 | this(message, null);
17 | }
18 |
19 | public CMODataClientException(Throwable cause) {
20 | this(null, cause);
21 | }
22 |
23 | public CMODataClientException(String message, Throwable cause) {
24 | this(message, cause, true, true);
25 | }
26 |
27 | public CMODataClientException(String message, Throwable cause, boolean enableSuppression,
28 | boolean writableStackTrace) {
29 | super(message, cause, enableSuppression, writableStackTrace);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/RELEASES.md:
--------------------------------------------------------------------------------
1 | # Release Notes
2 | * 3.0.0 Remove Interaction with CTS
3 | * Incompatible changes:
4 | * Remove --backend-type Option
5 | * 2.0.1 Allow also empty DevelopmentSystemIds
6 | * Bug fixes:
7 | * The client is now able to deal with empty development system ids.
8 | * 2.0.0 Support for DevelopmentSystemID property
9 | * Incompatible changes:
10 | * Creating a transport requires a new property 'developmentSystemID' (`-dID`)
11 | * 1.0.0 Additing commands for interacting with CTS.
12 | * Incompatible changes:
13 | * changeId and transportId needs to be provided as option `-cID`, `-tID` rather than as argument.
14 | * new option `-t`, `--backend-type` needs to provided for each call in order to distinguish between SOLMAN and CTS use cases.
15 | * Bug fixes:
16 | * bug fix in launcher script `bin/cmclient`. Calling the command line client failed in case of having a symbolic link e.g.
17 | in `/usr/local/bin`. `CMCLIENT_HOME` was not taken into account for setting up the class path in this case.
18 | * 0.0.1 Initial release, providing commands for interacting with SAP SolutionManager.
19 |
--------------------------------------------------------------------------------
/modules/cli/src/main/java/sap/prd/cmintegration/cli/ExitWrapper.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | /**
7 | * Launcher class which launches another class and handels {@link ExitException}s. In case
8 | * an ExitExcpetion is encountered the exit code contained in that exception is used
9 | * as return code when exiting the Java Virtual Machine.
10 | */
11 | class ExitWrapper {
12 | final static private Logger logger = LoggerFactory.getLogger(ExitWrapper.class);
13 | public final static void main(String[] args) throws Exception {
14 | try {
15 | Commands.main(args);
16 | } catch(ExitException e) {
17 | if(e.getExitCode() != ExitException.ExitCodes.FALSE) {
18 | if(e.getCause() == null) {
19 | e.printStackTrace();
20 | } else {
21 | e.getCause().printStackTrace();
22 | }
23 | logger.error(e.getMessage(), e);
24 | }
25 | System.exit(e.getExitCode());
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/modules/cli/src/test/java/sap/prd/cmintegration/cli/SolManBackendGetChangeTransportOwnerTest.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import static org.hamcrest.Matchers.equalTo;
4 | import static org.hamcrest.Matchers.is;
5 | import static org.junit.Assert.assertThat;
6 |
7 | import org.apache.commons.io.IOUtils;
8 | import org.junit.Test;
9 |
10 | public class SolManBackendGetChangeTransportOwnerTest extends SolManBackendCMTransportTestBase {
11 |
12 | @Test
13 | public void getChangeTransportOwnerStraightForward() throws Exception {
14 |
15 | setMock(setupMock("L21K900026", "J01~JAVA", "xOwner", "xDesc", false));
16 | Commands.main(new String[] {
17 | "-u", SERVICE_USER,
18 | "-p", SERVICE_PASSWORD,
19 | "-e", SERVICE_ENDPOINT,
20 | "get-transport-owner",
21 | "-cID" ,"8000038673", "-tID", "L21K900026"});
22 |
23 | assertThat(removeCRLF(IOUtils.toString(result.toByteArray(), "UTF-8")),
24 | is(equalTo("xOwner")));
25 |
26 | assertThat(changeId.getValue(), is(equalTo("8000038673")));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/modules/cli/src/test/java/sap/prd/cmintegration/cli/SolManBackendGetChangeTransportDescriptionTest.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import static org.hamcrest.Matchers.equalTo;
4 | import static org.hamcrest.Matchers.is;
5 | import static org.junit.Assert.assertThat;
6 |
7 | import org.apache.commons.io.IOUtils;
8 | import org.junit.Test;
9 |
10 | public class SolManBackendGetChangeTransportDescriptionTest extends SolManBackendCMTransportTestBase {
11 |
12 | @Test
13 | public void getChangeTransportDesciptionStraightForward() throws Exception {
14 |
15 | setMock(setupMock("L21K900026", "J01~JAVA", "xOwner", "xDescription", false));
16 | Commands.main(new String[] {
17 | "-u", SERVICE_USER,
18 | "-p", SERVICE_PASSWORD,
19 | "-e", SERVICE_ENDPOINT,
20 | "get-transport-description",
21 | "-cID", "8000038673", "-tID", "L21K900026"});
22 |
23 | assertThat(removeCRLF(IOUtils.toString(result.toByteArray(), "UTF-8")),
24 | is(equalTo("xDescription")));
25 |
26 | assertThat(changeId.getValue(), is(equalTo("8000038673")));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/modules/cli/src/main/java/sap/prd/cmintegration/cli/SolmanClientFactory.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient;
4 |
5 | /**
6 | * Provides {@link CMODataSolmanClient} instances.
7 | */
8 | class SolmanClientFactory {
9 |
10 | private static SolmanClientFactory instance;
11 |
12 | private SolmanClientFactory() {
13 | }
14 |
15 | static synchronized SolmanClientFactory getInstance() {
16 | if(instance == null) {
17 | instance = new SolmanClientFactory();
18 | }
19 | return instance;
20 | }
21 |
22 | /**
23 | * Provides a new instance of {@link CMODataSolmanClient}
24 | * @param serviceUrl The OData endpoint of the SAP Solution Manager
25 | * @param serviceUser The service user.
26 | * @param servicePassword The password for authenticating.
27 | * @return A new instance of {@link CMODataSolmanClient}
28 | */
29 | CMODataSolmanClient newClient(String serviceUrl, String serviceUser, String servicePassword) {
30 | return new CMODataSolmanClient(serviceUrl, serviceUser, servicePassword);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/modules/cli/src/test/java/sap/prd/cmintegration/cli/SolManBackendGetChangeTransportDevelopmentSystemTest.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import static org.hamcrest.Matchers.equalTo;
4 | import static org.hamcrest.Matchers.is;
5 | import static org.junit.Assert.assertThat;
6 |
7 | import org.apache.commons.io.IOUtils;
8 | import org.junit.Test;
9 |
10 | public class SolManBackendGetChangeTransportDevelopmentSystemTest extends SolManBackendCMTransportTestBase {
11 |
12 | @Test
13 | public void getChangeTransportDesciptionStraightForward() throws Exception {
14 |
15 | setMock(setupMock("L21K900026", "J01~JAVA", "xOwner", "xDescription", false));
16 | Commands.main(new String[] {
17 | "-u", SERVICE_USER,
18 | "-p", SERVICE_PASSWORD,
19 | "-e", SERVICE_ENDPOINT,
20 | "get-transport-development-system",
21 | "-cID", "8000038673", "-tID", "L21K900026"});
22 |
23 | assertThat(removeCRLF(IOUtils.toString(result.toByteArray(), "UTF-8")),
24 | is(equalTo("J01~JAVA")));
25 |
26 | assertThat(changeId.getValue(), is(equalTo("8000038673")));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/modules/cli/src/main/java/sap/prd/cmintegration/cli/GetTransportOwnerSOLMAN.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import static sap.prd.cmintegration.cli.Commands.Helpers.getCommandName;
4 |
5 | import java.util.function.Function;
6 |
7 | import org.apache.commons.cli.Options;
8 |
9 | import com.sap.cmclient.Transport;
10 |
11 | /**
12 | * Command for retrieving the owner of a transport.
13 | */
14 | @CommandDescriptor(name="get-transport-owner")
15 | class GetTransportOwnerSOLMAN extends TransportRelatedSOLMAN {
16 |
17 | GetTransportOwnerSOLMAN(String host, String user, String password, String changeId, String transportId, boolean returnCodeMode) {
18 | super(host, user, password, changeId, transportId, returnCodeMode);
19 | }
20 |
21 | @Override
22 | protected Function getAction() {
23 | return getOwner;
24 | }
25 |
26 | public final static void main(String[] args) throws Exception {
27 | TransportRelatedSOLMAN.main(GetTransportOwnerSOLMAN.class, new Options(), args,
28 | getCommandName(GetTransportOwnerSOLMAN.class),
29 | "Returns the owner of the given transport.", "");
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Guidance on how to contribute
2 |
3 | There are two primary ways to help:
4 | * using the issue tracker, and
5 | * changing the code-base.
6 |
7 | ## Using the issue tracker
8 |
9 | Use the issue tracker to suggest feature requests, report bugs, and ask
10 | questions. This is also a great way to connect with the developers of the
11 | project as well as others who are interested in this solution.
12 |
13 | Use the issue tracker to find ways to contribute. Find a bug or a feature,
14 | mention in the issue that you will take on that effort, then follow the
15 | guidance below.
16 |
17 | ## Changing the code-base
18 |
19 | Generally speaking, you should fork this repository, make changes in your own
20 | fork, and then submit a pull-request. All new code should have been thoroughly
21 | tested end-to-end in order to validate implemented features and the presence or
22 | lack of defects. All new classes and methods _must_ come with automated unit
23 | tests.
24 |
25 | The contract of functionality exposed by classes and methods functionality needs
26 | to be documented, so it can be properly used. Implementation of a functionality
27 | and its documentation shall happen within the same commit(s).
28 |
29 |
--------------------------------------------------------------------------------
/modules/dist.cli/src/main/assembly/dist.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 | dummy
7 |
8 | tar.gz
9 |
10 | false
11 | cmcli-${project.version}
12 |
13 |
14 |
15 |
16 | src/main/resources/bin
17 | bin
18 |
19 | cmclient
20 |
21 | unix
22 | 0755
23 | 0755
24 |
25 |
26 |
27 |
28 |
29 | lib
30 | false
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/.github/workflows/maven-build-test-master.yml:
--------------------------------------------------------------------------------
1 | name: Maven Build and Test (Master Branch)
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | branches:
9 | - master
10 |
11 | jobs:
12 | Build:
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - name: Step 1 - Checkout main branch from GitHub
17 | uses: actions/checkout@v2
18 |
19 | - name: Step 2 - Set up JDK 1.8
20 | uses: actions/setup-java@v2
21 | with:
22 | java-version: '8'
23 | distribution: 'adopt'
24 | cache: maven
25 |
26 | - name: Step 3 - Verify with Maven
27 | run: |
28 | echo "[INFO]: Not preparing code signing."
29 | echo "[INFO] Running with profile: noop"
30 | mvn --batch-mode --settings cfg/settings.xml -P noop clean verify
31 |
32 | - name: Step 4 - Check Project-Version
33 | run: |
34 | mkdir tmp/
35 | export PROJECT_VERSION=`mvn -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive exec:exec`
36 | tar -C tmp -xvf modules/dist.cli/target/dist.cli-${PROJECT_VERSION}.tar.gz
37 | tmp/bin/cmclient --version | grep -q "^${PROJECT_VERSION}"
--------------------------------------------------------------------------------
/modules/dist.cli/src/test/bash/prepare.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [ -z ${CM_USER} ];then
4 | echo "[ERROR] Provide CM service user with environment variable '\${CM_USER}'."; exit 1
5 | fi
6 |
7 | if [ -z ${CM_PASSWORD} ];then
8 | echo "[ERROR] Provide password for user '${CM_USER}' with environment variable '\${CM_PASSWORD}'."; exit 1
9 | fi
10 |
11 | if [ -z "${CM_ENDPOINT}" ];then
12 | echo "[ERROR] Provide CM service endpoint with environment variable '\${CM_ENDPOINT}'."; exit 1
13 | fi
14 |
15 |
16 | MAVEN_BUILD_DIR="../../../target"
17 |
18 | if [ ! -d "${MAVEN_BUILD_DIR}" ];then
19 | echo "[ERROR] Maven build directory '${MAVEN_BUILD_DIR}' does not exist. Run a maven build ..."; exit 1
20 | fi
21 |
22 | CM_CLIENT_HOME="${MAVEN_BUILD_DIR}/cmclient"
23 |
24 | TAR_FILE_NAME=`ls ${MAVEN_BUILD_DIR} |grep dist\.cli.*\.tar.gz`
25 | TAR_FILE="${MAVEN_BUILD_DIR}/${TAR_FILE_NAME}"
26 |
27 | if [[ -z "${TAR_FILE_NAME}" ||! -f "${TAR_FILE}" ]];then
28 | echo "[ERROR] Tar file '${TAR_FILE}' not found.";exit 1
29 | fi
30 |
31 | if [ -e ${CM_CIENT_HOMT} ];then
32 | rm -rf ${CM_CLIENT_HOME} |exit 1
33 | fi
34 |
35 | mkdir ${CM_CLIENT_HOME} |exit 1
36 |
37 | tar -C ${CM_CLIENT_HOME} -xf "${TAR_FILE}" |exit 1
38 |
39 |
40 |
--------------------------------------------------------------------------------
/modules/cli/src/main/java/sap/prd/cmintegration/cli/GetTransportDescriptionSOLMAN.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import static sap.prd.cmintegration.cli.Commands.Helpers.getCommandName;
4 |
5 | import java.util.function.Function;
6 |
7 | import org.apache.commons.cli.Options;
8 |
9 | import com.sap.cmclient.Transport;
10 |
11 | /**
12 | * Command for retrieving the description of a transport.
13 | */
14 | @CommandDescriptor(name="get-transport-description")
15 | class GetTransportDescriptionSOLMAN extends TransportRelatedSOLMAN {
16 |
17 | GetTransportDescriptionSOLMAN(String host, String user, String password, String changeId, String transportId, boolean isReturnCodeMode) {
18 | super(host, user, password, changeId, transportId, isReturnCodeMode);
19 | }
20 |
21 | @Override
22 | protected Function getAction() {
23 | return getDescription;
24 | }
25 |
26 | public final static void main(String[] args) throws Exception {
27 | TransportRelatedSOLMAN.main(GetTransportDescriptionSOLMAN.class, new Options(), args,
28 | getCommandName(GetTransportDescriptionSOLMAN.class),
29 | "Returns the description for the given transport.", "");
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/modules/cli/src/test/java/sap/prd/cmintegration/cli/Matchers.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import static java.lang.String.format;
4 |
5 | import org.hamcrest.BaseMatcher;
6 | import org.hamcrest.Description;
7 |
8 | public class Matchers {
9 |
10 | private Matchers() {
11 | }
12 |
13 | public static class ExitCodeMatcher extends BaseMatcher {
14 |
15 | private final int expected;
16 | private int actual = -1;
17 |
18 | ExitCodeMatcher(int exitCode) {
19 | expected = exitCode;
20 | }
21 |
22 | @Override
23 | public boolean matches(Object item) {
24 | if(! (item instanceof ExitException)) {
25 | return false;
26 | }
27 | actual = ((ExitException)item).getExitCode();
28 | return actual == expected;
29 | }
30 |
31 | @Override
32 | public void describeTo(Description description) {
33 | description.appendText(format("Unexpected exit code received: '%d'. Expected was: '%d'.", actual, expected));
34 | }
35 | }
36 |
37 | public final static ExitCodeMatcher exitCode(int exitCode) {
38 | return new ExitCodeMatcher(exitCode);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/modules/cli/src/main/java/sap/prd/cmintegration/cli/Command.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import static com.google.common.base.Preconditions.checkArgument;
4 | import static org.apache.commons.lang3.StringUtils.isBlank;
5 |
6 | import org.apache.commons.cli.Options;
7 |
8 | /**
9 | * Root class for all commands.
10 | */
11 | abstract class Command {
12 |
13 | protected final String host, user, password;
14 |
15 | protected Command(String host, String user, String password) {
16 |
17 | checkArgument(! isBlank(host), "No endpoint provided.");
18 | checkArgument(! isBlank(user), "No user provided.");
19 | checkArgument(! isBlank(password), "No password provided.");
20 |
21 | this.host = host;
22 | this.user = user;
23 | this.password = password;
24 | }
25 |
26 | /**
27 | * Contains the command specific logic. E.g. performs a
28 | * call to SAP Solution Manager, parses the result and
29 | * provides it via System.out to the caller of the command line.
30 | * @throws Exception In case of trouble.
31 | */
32 | abstract void execute() throws Exception;
33 |
34 | protected static Options addOpts(Options options) {
35 | Commands.Helpers.addStandardParameters(options);
36 | return options;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/modules/dist.cli/src/main/resources/bin/cmclient:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -o noglob
4 |
5 | _SN=`basename "$0"` # script name
6 |
7 | is_emulated_shell() {
8 | [[ `uname` =~ CYGWIN|MINGW ]] && return
9 | false
10 | }
11 |
12 | determine_dir() {
13 | if [ -z "$CMCLIENT_HOME" ]; then
14 | if [ `echo $0 | cut -c1` = "/" ]; then
15 | CMCLIENT_HOME=$0
16 | else
17 | CMCLIENT_HOME=`pwd`/$0
18 | fi
19 | CMCLIENT_HOME=`dirname $CMCLIENT_HOME`/..
20 | if is_emulated_shell; then
21 | CMCLIENT_HOME=`cygpath -wp $CMCLIENT_HOME`
22 | CMCLIENT_LIB=`cygpath -wp $CMCLIENT_LIB`
23 | fi
24 | fi
25 | CMCLIENT_LIB=$CMCLIENT_HOME/lib/*
26 | }
27 |
28 | check_env() {
29 | if [ -z "$CMCLIENT_HOME" ]; then
30 | echo "$_SN: error: CMCLIENT_HOME must be set to the directory containing the cm client tool" 1>&2
31 | exit 1
32 | fi
33 | if [ ! -d "$CMCLIENT_HOME" ]; then
34 | echo "$_SN: error: CMCLIENT_HOME does not denote a directory: $CM_CLIENT_HOME" 1>&2
35 | exit 1
36 | fi
37 | }
38 |
39 | java_executable() {
40 | if [ -n "$JAVA_HOME" ]; then
41 | echo "${JAVA_HOME}/bin/java"
42 | else
43 | echo "java"
44 | fi
45 | }
46 |
47 | determine_dir
48 | check_env
49 |
50 | "`java_executable`" $CMCLIENT_OPTS -cp "$CMCLIENT_LIB" sap.prd.cmintegration.cli.ExitWrapper "$@"
51 |
--------------------------------------------------------------------------------
/modules/cli/src/main/java/sap/prd/cmintegration/cli/GetTransportModifiableSOLMAN.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import static sap.prd.cmintegration.cli.Commands.Helpers.getCommandName;
4 |
5 | import java.util.function.Function;
6 |
7 | import org.apache.commons.cli.Options;
8 |
9 | import com.sap.cmclient.Transport;
10 |
11 | /**
12 | * Checks if a transport is modifiable.
13 | */
14 | @CommandDescriptor(name="is-transport-modifiable")
15 | class GetTransportModifiableSOLMAN extends TransportRelatedSOLMAN {
16 |
17 | private static class Opts {
18 | static Options addOptions(Options opts, boolean includeStandardOpts) {
19 | TransportRelatedSOLMAN.Opts.addOptions(opts, includeStandardOpts);
20 | return opts.addOption(Commands.CMOptions.RETURN_CODE);
21 | }
22 | }
23 | GetTransportModifiableSOLMAN(String host, String user, String password, String changeId, String transportId, boolean returnCodeMode) {
24 | super(host, user, password, changeId, transportId, returnCodeMode);
25 | }
26 |
27 | protected Function getAction() {
28 | return isModifiable;
29 | }
30 |
31 | public final static void main(String[] args) throws Exception {
32 | TransportRelatedSOLMAN.main(GetTransportModifiableSOLMAN.class, Opts.addOptions(new Options(), true), args,
33 | getCommandName(GetTransportModifiableSOLMAN.class), "",
34 | "Returns 'true' if the transport is modifiable. Otherwise 'false'.");
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/modules/cli/src/test/java/sap/prd/cmintegration/cli/CommandsHelpersTest.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import static org.hamcrest.Matchers.equalTo;
4 | import static org.hamcrest.Matchers.is;
5 | import static org.junit.Assert.assertThat;
6 |
7 | import org.apache.commons.lang3.StringUtils;
8 | import org.junit.Test;
9 |
10 | public class CommandsHelpersTest {
11 |
12 | @Test
13 | public void testPasswordIsHidden() {
14 | String[] args = Commands.Helpers.hidePassword(new String[] {
15 | "-e", "http://example.org",
16 | "-u", "me",
17 | "-p", "topSecret"});
18 |
19 | assertThat(StringUtils.join(args, " "), is(equalTo(
20 | "-e http://example.org -u me -p ********")));
21 | }
22 |
23 | @Test
24 | public void testEmptyPasswordOptionDoesNotFail() {
25 | String[] args = Commands.Helpers.hidePassword(new String[] {
26 | "-e", "http://example.org",
27 | "-u", "me",
28 | "-p"});
29 |
30 | assertThat(StringUtils.join(args, " "), is(equalTo(
31 | "-e http://example.org -u me -p")));
32 | }
33 |
34 | @Test
35 | public void testDashAsPasswordNotHidden() {
36 |
37 | //Password read from stdin in this case.
38 |
39 | String[] args = Commands.Helpers.hidePassword(new String[] {
40 | "-e", "http://example.org",
41 | "-u", "me",
42 | "-p", "-"});
43 |
44 | assertThat(StringUtils.join(args, " "), is(equalTo(
45 | "-e http://example.org -u me -p -")));
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/modules/cli/src/main/java/sap/prd/cmintegration/cli/ExitException.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | /**
4 | * Used in case a status is encounter where any further processing inside
5 | * the command line client does not make sense. The exit code transported
6 | * alongside with this exception is used as exit code of the Java Virtual
7 | * Machine.
8 | */
9 | class ExitException extends CMCommandLineException {
10 |
11 | public static class ExitCodes {
12 | public final static int OK = 0,
13 | GENERIC_FAILURE = 1,
14 | NOT_AUTHENTIFICATED = 2,
15 | FALSE = 3; // returned in case of --return-code option.
16 | }
17 |
18 | private static final long serialVersionUID = -3269137608207801150L;
19 | private final int exitCode;
20 |
21 | ExitException(int exitCode) {
22 | this((String)null, exitCode);
23 | }
24 |
25 | ExitException(String message, int exitCode) {
26 | this(message, null, exitCode);
27 | }
28 |
29 | ExitException(Throwable cause, int exitCode) {
30 | this(null, cause, exitCode);
31 | }
32 |
33 | ExitException(String message, Throwable cause, int exitCode) {
34 | super(message, cause);
35 | if(exitCode == 0)
36 | throw new RuntimeException("Cannot create ExitException for exit code 0. "
37 | + "The cause contained in this exception is the original exception (if any) "
38 | + "handed over to constructor of the ExitException.", cause);
39 | this.exitCode = exitCode;
40 | }
41 |
42 | public int getExitCode() {
43 | return exitCode;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/modules/lib-solman/src/main/java/sap/ai/st/cm/plugins/ciintegration/odataclient/CMOdataHTTPFactory.java:
--------------------------------------------------------------------------------
1 | package sap.ai.st.cm.plugins.ciintegration.odataclient;
2 |
3 | import static java.lang.String.format;
4 |
5 | import java.net.URI;
6 | import org.apache.http.client.CookieStore;
7 | import org.apache.http.impl.client.BasicCookieStore;
8 | import org.apache.http.impl.client.DefaultHttpClient;
9 | import org.apache.http.params.CoreProtocolPNames;
10 | import org.apache.olingo.client.core.http.BasicAuthHttpClientFactory;
11 | import org.apache.olingo.commons.api.http.HttpMethod;
12 |
13 | import com.sap.cmclient.VersionHelper;
14 |
15 | /**
16 | * Our own factory for http clients.
17 | * We set
18 | *
19 | *
We set a cookie store
20 | *
We set the USER_AGENT header in order to be able to identify requests performed
21 | * by the client (and the corresponding client version) on the server side.
22 | *
23 | */
24 | public class CMOdataHTTPFactory extends BasicAuthHttpClientFactory {
25 |
26 | private final CookieStore cookieStore;
27 |
28 | public CMOdataHTTPFactory(String username, String password) {
29 |
30 | super(username, password);
31 |
32 | this.cookieStore = new BasicCookieStore();
33 | }
34 |
35 | @Override
36 | public DefaultHttpClient create(final HttpMethod method, final URI uri) {
37 |
38 | final DefaultHttpClient httpClient = super.create(method, uri);
39 |
40 | httpClient.getParams().setParameter(CoreProtocolPNames.USER_AGENT,
41 | format("SAP CM Client/%s based on %s", VersionHelper.getShortVersion(), USER_AGENT));
42 |
43 | httpClient.setCookieStore(this.cookieStore);
44 |
45 | return httpClient;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/modules/cli/src/main/java/sap/prd/cmintegration/cli/GetTransportDevelopmentSystemSOLMAN.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import static sap.prd.cmintegration.cli.Commands.Helpers.getCommandName;
4 |
5 | import java.util.function.Function;
6 |
7 | import org.apache.commons.cli.Options;
8 | import org.apache.commons.lang3.StringUtils;
9 |
10 | import com.sap.cmclient.Transport;
11 |
12 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataTransport;
13 |
14 | /**
15 | * Command for retrieving the description of a transport.
16 | */
17 | @CommandDescriptor(name="get-transport-development-system")
18 | class GetTransportDevelopmentSystemSOLMAN extends TransportRelatedSOLMAN {
19 |
20 | GetTransportDevelopmentSystemSOLMAN(String host, String user, String password, String changeId, String transportId, boolean isReturnCodeMode) {
21 | super(host, user, password, changeId, transportId, isReturnCodeMode);
22 | }
23 |
24 | @Override
25 | protected Function getAction() {
26 | return new Function() {
27 |
28 | @Override
29 | public String apply(Transport t) {
30 | String developmentSystem = ((CMODataTransport)t).getDevelopmentSystemID();
31 | if(StringUtils.isBlank(developmentSystem)) {
32 | developmentSystem = "";
33 | }
34 | return developmentSystem;
35 | };
36 | };
37 | }
38 |
39 | public final static void main(String[] args) throws Exception {
40 | TransportRelatedSOLMAN.main(GetTransportDevelopmentSystemSOLMAN.class, new Options(), args,
41 | getCommandName(GetTransportDevelopmentSystemSOLMAN.class),
42 | "Returns the development system id for the given transport.", "");
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/modules/lib-common/src/main/java/com/sap/cmclient/VersionHelper.java:
--------------------------------------------------------------------------------
1 | package com.sap.cmclient;
2 |
3 | import static java.lang.String.format;
4 |
5 | import java.io.IOException;
6 | import java.io.InputStream;
7 | import java.util.Properties;
8 |
9 | import org.slf4j.Logger;
10 | import org.slf4j.LoggerFactory;
11 |
12 | public class VersionHelper {
13 |
14 | private final static Logger logger = LoggerFactory.getLogger(VersionHelper.class);
15 |
16 | private VersionHelper() {
17 | // avoid getting instances.
18 | }
19 |
20 | public static String getLongVersion() {
21 | Properties vProps = getVersionProperties();
22 | return (vProps == null) ? "" : format("%s : %s",
23 | vProps.getProperty("mvnProjectVersion", ""),
24 | vProps.getProperty("gitCommitId", ""));
25 | }
26 |
27 | public static String getShortVersion() {
28 | Properties vProps = getVersionProperties();
29 | return (vProps == null) ? "" : vProps.getProperty("mvnProjectVersion", "");
30 | }
31 |
32 | public static String getOlingoV2Version() {
33 | Properties vProps = getVersionProperties();
34 | return (vProps == null) ? "n/a" : vProps.getProperty("olingoVersionV2", "");
35 | }
36 |
37 | public Object clone() {
38 | throw new UnsupportedOperationException();
39 | }
40 |
41 | private static Properties getVersionProperties() {
42 | try(InputStream version = VersionHelper.class.getResourceAsStream("/VERSION")) {
43 | Properties vProps = new Properties();
44 | if(version != null) vProps.load(version);
45 | return vProps;
46 | } catch(IOException e) {
47 | logger.warn("Cannot retrieve version.", e);
48 | return null;
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/modules/lib-solman/src/main/java/sap/ai/st/cm/plugins/ciintegration/odataclient/CMODataTransport.java:
--------------------------------------------------------------------------------
1 | package sap.ai.st.cm.plugins.ciintegration.odataclient;
2 |
3 | import static com.google.common.base.Strings.isNullOrEmpty;
4 |
5 | import com.google.common.base.Preconditions;
6 | import com.sap.cmclient.Transport;
7 |
8 | /**
9 | * Data transfer object representing a transport.
10 | */
11 | public class CMODataTransport implements Transport {
12 |
13 | private final String transportID;
14 | private final String developmentSystemID;
15 | private final Boolean isModifiable;
16 | private final String description;
17 | private final String owner;
18 |
19 | public String getTransportID() {
20 | return transportID;
21 | }
22 |
23 | public String getDevelopmentSystemID() {
24 | return developmentSystemID;
25 | }
26 | public Boolean isModifiable() {
27 | return isModifiable;
28 | }
29 |
30 | public String getDescription() {
31 | return description;
32 | }
33 |
34 | public String getOwner() {
35 | return owner;
36 | }
37 |
38 | public CMODataTransport(String transportID, String developmentSystemID, Boolean isModifiable, String description, String owner) {
39 |
40 | Preconditions.checkArgument(! isNullOrEmpty(transportID), "transportId was null or empty.");
41 | this.transportID = transportID;
42 | this.developmentSystemID = developmentSystemID;
43 | this.isModifiable = isModifiable;
44 | this.description = description;
45 | this.owner = owner;
46 | }
47 |
48 | @Override
49 | public String toString() {
50 | return "CMODataTransport [TransportID='" + transportID + "', DevelopmentSystemID='" + developmentSystemID + "'IsModifiable='" + isModifiable + "', Description='"
51 | + description + "', Owner='" + owner + "']";
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/.reuse/dep5:
--------------------------------------------------------------------------------
1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2 | Upstream-Name: SAP/devops-cm-client
3 | Upstream-Contact: thorsten.duda_AT_sap.com
4 | Source: https://github.com/SAP/devops-cm-client
5 | Disclaimer: The code in this project may include calls to APIs (“API Calls”) of
6 | SAP or third-party products or services developed outside of this project
7 | (“External Products”).
8 | “APIs” means application programming interfaces, as well as their respective
9 | specifications and implementing code that allows software to communicate with
10 | other software.
11 | API Calls to External Products are not licensed under the open source license
12 | that governs this project. The use of such API Calls and related External
13 | Products are subject to applicable additional agreements with the relevant
14 | provider of the External Products. In no event shall the open source license
15 | that governs this project grant any rights in or to any External Products,or
16 | alter, expand or supersede any terms of the applicable additional agreements.
17 | If you have a valid license agreement with SAP for the use of a particular SAP
18 | External Product, then you may make use of any API Calls included in this
19 | project’s code for that SAP External Product, subject to the terms of such
20 | license agreement. If you do not have a valid license agreement for the use of
21 | a particular SAP External Product, then you may only make use of any API Calls
22 | in this project for that SAP External Product for your internal, non-productive
23 | and non-commercial test and evaluation of such API Calls. Nothing herein grants
24 | you any rights to use or access any SAP External Product, or provide any third
25 | parties the right to use of access any SAP External Product, through API Calls.
26 |
27 | Files: **
28 | Copyright: 2017-2020 SAP SE or an SAP affiliate company and contributors
29 | License: Apache-2.0
--------------------------------------------------------------------------------
/.github/workflows/build-publish-release.yml:
--------------------------------------------------------------------------------
1 | name: Release - Maven Build and publish to Central
2 |
3 | on:
4 | push:
5 | branches:
6 | - release
7 |
8 | jobs:
9 | Deploy:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Step 1 - Checkout release branch from GitHub
14 | uses: actions/checkout@v2
15 |
16 | - name: Step 2 - Set up Apache Maven Central
17 | uses: actions/setup-java@v2
18 | with: # running setup-java again overwrites the settings.xml
19 | java-version: '8'
20 | distribution: 'adopt'
21 | cache: 'maven'
22 | server-id: ossrh # Value of the distributionManagement/repository/id field of the pom.xml
23 | server-username: OSSRH_JIRA_USERNAME # env variable for username in deploy
24 | server-password: OSSRH_JIRA_PASSWORD # env variable for token in deploy
25 | gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} # Value of the GPG private key to import
26 | gpg-passphrase: GPG_PASSPHRASE # env variable for GPG Passphrase
27 |
28 | - name: Step 3 - Verify with Maven
29 | run: |
30 | export GPG_TTY=$(tty)
31 | echo "[INFO] Running with profile: signing"
32 | mvn --batch-mode --settings cfg/settings.xml -P signing clean verify
33 | env:
34 | GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
35 |
36 | - name: Step 4 - Check Project-Version
37 | run: |
38 | mkdir tmp/
39 | export PROJECT_VERSION=`mvn -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive exec:exec`
40 | tar -C tmp -xvf modules/dist.cli/target/dist.cli-${PROJECT_VERSION}.tar.gz
41 | tmp/bin/cmclient --version | grep -q "^${PROJECT_VERSION}"
42 |
43 | - name: Step 5 - Publish package
44 | run: mvn --batch-mode clean deploy --settings cfg/settings.xml -P signing -DstagingProfileId=22fbc0443d9154
45 | env:
46 | OSSRH_JIRA_USERNAME: ${{ secrets.OSSRH_JIRA_USERNAME }}
47 | OSSRH_JIRA_PASSWORD: ${{ secrets.OSSRH_JIRA_PASSWORD }}
48 | GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
49 |
--------------------------------------------------------------------------------
/modules/cli/src/test/java/sap/prd/cmintegration/cli/CMTestBase.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.PrintStream;
5 |
6 | import org.apache.http.ProtocolVersion;
7 | import org.apache.http.message.BasicStatusLine;
8 | import org.easymock.Capture;
9 | import org.junit.Before;
10 | import org.junit.Rule;
11 | import org.junit.rules.ExpectedException;
12 |
13 | public class CMTestBase {
14 |
15 | protected final static String SERVICE_USER = System.getProperty("CM_SERVICE_USER", "john.doe"),
16 | SERVICE_PASSWORD = System.getProperty("CM_SERVICE_PASSWORD", "openSesame"),
17 | SERVICE_ENDPOINT = System.getProperty("CM_SERVICE_ENDPOINT", "https://example.org/myEndpoint");
18 |
19 | protected static class StatusLines {
20 | private final static ProtocolVersion HTTP_1_1 = new ProtocolVersion("HTTP", 1, 1);
21 | protected final static BasicStatusLine BAD_REQUEST = new BasicStatusLine(HTTP_1_1, 400, "Bad Request");
22 | protected final static BasicStatusLine UNAUTHORIZED = new BasicStatusLine(HTTP_1_1, 401, "Unauthorized");
23 | protected final static BasicStatusLine NOT_FOUND = new BasicStatusLine(HTTP_1_1, 404, "Not Found.");
24 | }
25 |
26 | @Rule
27 | public ExpectedException thrown = ExpectedException.none();
28 |
29 | protected PrintStream oldOut;
30 | protected ByteArrayOutputStream result;
31 |
32 | Capture host = Capture.newInstance(),
33 | user = Capture.newInstance(),
34 | password = Capture.newInstance(),
35 | changeId = Capture.newInstance();
36 |
37 | @Before
38 | public void setup() throws Exception {
39 | prepareOutputStream();
40 | }
41 |
42 | protected void prepareOutputStream(){
43 | result = new ByteArrayOutputStream();
44 | oldOut = System.out;
45 | System.setOut(new PrintStream(result));
46 | }
47 |
48 | /*
49 | * Intended for being used with a single line string.
50 | */
51 | protected static String removeCRLF(String str) {
52 | return str.replaceAll("\\r?\\n$", "");
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/modules/testutils/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 |
7 | com.sap.devops.cmclient
8 | module
9 | 0.0.2-SNAPSHOT
10 | ../..
11 |
12 |
13 | testutils
14 | jar
15 |
16 | testutils
17 | testutils
18 |
19 |
20 | 1.7
21 |
22 |
23 |
24 |
25 | com.google.guava
26 | guava
27 |
28 |
29 | junit
30 | junit
31 |
32 |
33 | org.hamcrest
34 | hamcrest-library
35 | compile
36 |
37 |
38 | org.hamcrest
39 | hamcrest-core
40 | compile
41 |
42 |
43 |
44 |
45 |
46 | maven-compiler-plugin
47 |
48 | ${java.level}
49 | ${java.level}
50 | ${java.level}
51 | ${java.level}
52 |
53 |
54 |
55 | org.apache.maven.plugins
56 | maven-javadoc-plugin
57 |
58 |
59 | org.apache.maven.plugins
60 | maven-source-plugin
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/modules/cli/src/main/java/sap/prd/cmintegration/cli/ReleaseTransport.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import static sap.prd.cmintegration.cli.Commands.Helpers.getChangeId;
4 | import static sap.prd.cmintegration.cli.Commands.Helpers.getCommandName;
5 | import static sap.prd.cmintegration.cli.Commands.Helpers.getHost;
6 | import static sap.prd.cmintegration.cli.Commands.Helpers.getPassword;
7 | import static sap.prd.cmintegration.cli.Commands.Helpers.getUser;
8 | import static sap.prd.cmintegration.cli.Commands.Helpers.handleHelpOption;
9 | import static sap.prd.cmintegration.cli.Commands.Helpers.helpRequested;
10 |
11 | import org.apache.commons.cli.CommandLine;
12 | import org.apache.commons.cli.DefaultParser;
13 | import org.apache.commons.cli.Options;
14 |
15 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient;
16 |
17 | /**
18 | * Command for releasing a transport.
19 | */
20 | @CommandDescriptor(name = "release-transport")
21 | class ReleaseTransport extends Command {
22 |
23 | static class Opts {
24 |
25 | static Options addOptions(Options opts, boolean includeStandardOptions) {
26 | if (includeStandardOptions) {
27 | Command.addOpts(opts);
28 | }
29 |
30 | return opts.addOption(Commands.CMOptions.CHANGE_ID).addOption(TransportRelatedSOLMAN.Opts.TRANSPORT_ID);
31 | }
32 | }
33 |
34 | private final String changeId, transportId;
35 |
36 | ReleaseTransport(String host, String user, String password, String changeId, String transportId) {
37 |
38 | super(host, user, password);
39 | this.changeId = changeId;
40 | this.transportId = transportId;
41 | }
42 |
43 | public final static void main(String[] args) throws Exception {
44 |
45 | if (helpRequested(args)) {
46 | handleHelpOption(getCommandName(ReleaseTransport.class), "",
47 | "Releases the transport specified by [,] .",
48 | Opts.addOptions(new Options(), false));
49 | return;
50 | }
51 |
52 | CommandLine commandLine = new DefaultParser().parse(Opts.addOptions(new Options(), true), args);
53 |
54 | new ReleaseTransport(getHost(commandLine), getUser(commandLine), getPassword(commandLine),
55 | getChangeId(commandLine), TransportRelatedSOLMAN.getTransportId(commandLine)).execute();
56 | }
57 |
58 | @Override
59 | void execute() throws Exception {
60 | try (CMODataSolmanClient client = SolmanClientFactory.getInstance().newClient(host, user, password)) {
61 | client.releaseDevelopmentTransport(changeId, transportId);
62 | } catch (Exception e) {
63 | throw e;
64 | }
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/modules/dist.cli/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 |
6 | com.sap.devops.cmclient
7 | module
8 | 0.0.2-SNAPSHOT
9 | ../..
10 |
11 |
12 | dist.cli
13 | CM - CLI Distribution Package
14 | pom
15 |
16 |
17 | ${project.parent.groupId}
18 | ci-integration-cli
19 | ${project.parent.version}
20 |
21 |
22 | org.apache.olingo
23 | odata-client-core
24 |
25 |
26 | com.google.guava
27 | guava
28 |
29 |
30 |
31 | org.slf4j
32 | slf4j-api
33 |
34 |
35 | org.slf4j
36 | slf4j-nop
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | maven-assembly-plugin
45 |
46 |
47 | package
48 | package
49 |
50 | single
51 |
52 |
53 |
54 | src/main/assembly/dist.xml
55 |
56 | false
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/modules/cli/src/test/java/sap/prd/cmintegration/cli/SolManBackendCMTransportTestBase.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import static org.easymock.EasyMock.capture;
4 | import static org.easymock.EasyMock.createMock;
5 | import static org.easymock.EasyMock.expect;
6 | import static org.easymock.EasyMock.expectLastCall;
7 | import static org.easymock.EasyMock.replay;
8 |
9 | import java.util.ArrayList;
10 |
11 | import org.junit.After;
12 | import org.junit.Before;
13 |
14 | import com.sap.cmclient.Transport;
15 |
16 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient;
17 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataTransport;
18 |
19 | public class SolManBackendCMTransportTestBase extends CMSolmanTestBase {
20 |
21 | @Before
22 | public void setup() throws Exception {
23 | super.setup();
24 | }
25 |
26 | @After
27 | public void tearDown() throws Exception {
28 | super.tearDown();
29 | }
30 |
31 | protected SolmanClientFactory setupMock(String transportId, String developmentSystemId, String owner, String description, boolean isModifiable) throws Exception {
32 | return setupMock(transportId, developmentSystemId, owner, description, isModifiable, null);
33 | }
34 |
35 | protected SolmanClientFactory setupMock(Exception e) throws Exception {
36 | return setupMock(null, null, null, null, false, e);
37 | }
38 |
39 | private SolmanClientFactory setupMock(String transportId, String developmentSystemId, String owner, String description, boolean isModifiable, Exception ex) throws Exception {
40 | CMODataSolmanClient clientMock = createMock(CMODataSolmanClient.class);
41 | clientMock.close(); expectLastCall();
42 | if(ex == null) {
43 | ArrayList transports = new ArrayList<>();
44 | transports.add(new CMODataTransport(transportId, developmentSystemId, isModifiable, description, owner));
45 | expect(clientMock.getChangeTransports(capture(changeId))).andReturn(transports);
46 | } else {
47 | expect(clientMock.getChangeTransports(capture(changeId))).andThrow(ex);
48 | }
49 | SolmanClientFactory factoryMock = createMock(SolmanClientFactory.class);
50 | expect(factoryMock
51 | .newClient(capture(host),
52 | capture(user),
53 | capture(password))).andReturn(clientMock);
54 |
55 | replay(clientMock, factoryMock);
56 | return factoryMock;
57 | }
58 |
59 | protected static String removeCRLF(String str) {
60 | return str.replaceAll("\\r?\\n$", "");
61 | }
62 | }
63 |
64 |
--------------------------------------------------------------------------------
/modules/testutils/src/main/java/com/sap/cmclient/Matchers.java:
--------------------------------------------------------------------------------
1 | package com.sap.cmclient;
2 |
3 | import static java.lang.String.format;
4 |
5 | import org.hamcrest.BaseMatcher;
6 | import org.hamcrest.Description;
7 | import org.hamcrest.Matcher;
8 |
9 | public class Matchers {
10 |
11 | private static class RootCauseMatcher extends BaseMatcher {
12 |
13 | private final Class extends Exception> expectedRootCause;
14 | private Throwable actualRootCause = null;
15 |
16 | private RootCauseMatcher(Class extends Exception> expectedRootCause) {
17 | this.expectedRootCause = expectedRootCause;
18 | }
19 |
20 | @Override
21 | public boolean matches(Object o) {
22 | actualRootCause = getRootCause((Exception)o);
23 | return expectedRootCause.isAssignableFrom(actualRootCause.getClass());
24 | }
25 |
26 | @Override
27 | public void describeTo(Description description) {
28 | description.appendText(
29 | format("Root cause found '%s' does not match the expected root cause '%s'.",
30 | (actualRootCause != null ? actualRootCause.getClass().getName() : ""), expectedRootCause));
31 | }
32 | }
33 |
34 | private static class RootCausMessageeMatcher extends BaseMatcher {
35 |
36 | private final String expectedMessage;
37 | private String actualMessage = null;
38 |
39 | private RootCausMessageeMatcher(String expectedMessage) {
40 | this.expectedMessage = expectedMessage;
41 | }
42 |
43 | @Override
44 | public boolean matches(Object o) {
45 | actualMessage = getRootCause((Exception)o).getMessage();
46 | return (actualMessage == null && expectedMessage == null) ||
47 | (actualMessage != null && actualMessage.contains(expectedMessage));
48 | }
49 |
50 | @Override
51 | public void describeTo(Description description) {
52 | description.appendText(
53 | format("Root cause message '%s' does not contain '%s'.",
54 | (actualMessage != null ? actualMessage : ""), expectedMessage));
55 | }
56 | }
57 |
58 | private Matchers() {
59 | }
60 |
61 | public static Matcher hasRootCause(Class extends Exception> root) {
62 | return new RootCauseMatcher(root);
63 | }
64 |
65 | public static Matcher rootCauseMessageContains(String expected) {
66 | return new RootCausMessageeMatcher(expected);
67 | }
68 |
69 | private static Throwable getRootCause(Throwable thr) {
70 | while(thr.getCause() != null) {
71 | thr = thr.getCause();
72 | };
73 | return thr;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/modules/lib-common/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 |
7 | com.sap.devops.cmclient
8 | module
9 | 0.0.2-SNAPSHOT
10 | ../..
11 |
12 |
13 | ci-integration-lib-common
14 | jar
15 |
16 | SAP Change Management Integration Library (Common parts)
17 | SAP Change Management Integration (Common parts)
18 |
19 |
20 | 1.7
21 |
22 |
23 |
24 |
25 | org.apache.httpcomponents
26 | httpclient
27 |
28 |
29 | org.slf4j
30 | slf4j-api
31 |
32 |
33 |
34 |
35 |
36 | src/main/resources
37 | true
38 |
39 |
40 |
41 |
42 |
43 | maven-compiler-plugin
44 |
45 | ${java.level}
46 | ${java.level}
47 | ${java.level}
48 | ${java.level}
49 |
50 |
51 |
52 | org.apache.maven.plugins
53 | maven-javadoc-plugin
54 |
55 |
56 | org.apache.maven.plugins
57 | maven-source-plugin
58 |
59 |
60 |
61 |
62 |
63 |
64 | logging
65 |
66 |
67 |
68 | maven-surefire-plugin
69 |
70 |
71 | TRACE
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | org.slf4j
80 | slf4j-simple
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/modules/cli/src/test/java/sap/prd/cmintegration/cli/SolManBackendReleaseTransportTest.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import static org.easymock.EasyMock.capture;
4 | import static org.easymock.EasyMock.expect;
5 | import static org.easymock.EasyMock.expectLastCall;
6 | import static org.hamcrest.Matchers.equalTo;
7 | import static org.hamcrest.Matchers.is;
8 | import static org.junit.Assert.assertThat;
9 |
10 | import org.apache.olingo.client.api.communication.ODataClientErrorException;
11 | import org.easymock.Capture;
12 | import org.easymock.EasyMock;
13 | import org.junit.After;
14 | import org.junit.Before;
15 | import org.junit.Test;
16 |
17 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient;
18 |
19 | public class SolManBackendReleaseTransportTest extends CMSolmanTestBase {
20 |
21 | private Capture transportId = null;
22 |
23 | @Before
24 | public void setup() throws Exception {
25 | super.setup();
26 | transportId = Capture.newInstance();
27 | }
28 |
29 | @After
30 | public void tearDown() throws Exception {
31 | transportId = null;
32 | super.tearDown();
33 | }
34 |
35 | @Test
36 | public void testReleaseTransportStraightForward() throws Exception {
37 |
38 | // comment line below in order to run against real back-end
39 | setMock(setupMock(null));
40 |
41 | Commands.main(new String[] {
42 | "-u", SERVICE_USER,
43 | "-p", SERVICE_PASSWORD,
44 | "-e", SERVICE_ENDPOINT,
45 | "release-transport",
46 | "-cID", "8000038673", "-tID", "L21K90002K"});
47 |
48 | assertThat(changeId.getValue(), is(equalTo("8000038673")));
49 | assertThat(transportId.getValue(), is(equalTo("L21K90002K")));
50 | }
51 |
52 | @Test
53 | public void testReleaseTransportFailsSinceTransportHasAlreadyBeenReleased() throws Exception {
54 |
55 | thrown.expect(ODataClientErrorException.class);
56 | thrown.expectMessage("400");
57 |
58 | // comment line below in order to run against real back-end
59 | setMock(setupMock(new ODataClientErrorException(StatusLines.BAD_REQUEST)));
60 |
61 | Commands.main(new String[] {
62 | "-u", SERVICE_USER,
63 | "-p", SERVICE_PASSWORD,
64 | "-e", SERVICE_ENDPOINT,
65 | "release-transport",
66 | "-cID", "8000038673", "-tID", "L21K900026"});
67 | }
68 |
69 | private SolmanClientFactory setupMock(Exception e) throws Exception {
70 |
71 | CMODataSolmanClient clientMock = EasyMock.createMock(CMODataSolmanClient.class);
72 | clientMock.releaseDevelopmentTransport(capture(changeId), capture(transportId));
73 | clientMock.close(); expectLastCall();
74 | if(e == null) expectLastCall(); else expectLastCall().andThrow(e);
75 |
76 | SolmanClientFactory factoryMock = EasyMock.createMock(SolmanClientFactory.class);
77 | expect(factoryMock
78 | .newClient(capture(host),
79 | capture(user),
80 | capture(password))).andReturn(clientMock);
81 |
82 | EasyMock.replay(clientMock, factoryMock);
83 | return factoryMock;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/modules/lib-solman/src/test/java/sap/ai/st/cm/plugins/ciintegration/odataclient/Matchers.java:
--------------------------------------------------------------------------------
1 | package sap.ai.st.cm.plugins.ciintegration.odataclient;
2 |
3 | import static java.lang.String.format;
4 |
5 | import org.apache.olingo.client.api.communication.ODataClientErrorException;
6 | import org.apache.olingo.client.api.http.HttpClientException;
7 | import org.apache.olingo.commons.api.ex.ODataError;
8 | import org.hamcrest.BaseMatcher;
9 | import org.hamcrest.Description;
10 | import org.hamcrest.Matcher;
11 |
12 | public class Matchers {
13 |
14 | private static class ErrorMessageMatcher extends BaseMatcher {
15 |
16 | String actualErrorMessage = "", expected;
17 |
18 | private ErrorMessageMatcher(String substring) {
19 | if(substring == null) throw new NullPointerException();
20 | this.expected = substring;
21 | }
22 |
23 | @Override
24 | public boolean matches(Object o) {
25 |
26 | Throwable rootCause = getRootCause((Exception)o);
27 |
28 | if(! (rootCause instanceof ODataClientErrorException))
29 | return false;
30 |
31 | ODataError error = ((ODataClientErrorException)rootCause).getODataError();
32 |
33 | if(error == null)
34 | return false;
35 |
36 | actualErrorMessage = error.getMessage();
37 | return actualErrorMessage != null && actualErrorMessage.contains(expected);
38 | }
39 |
40 | @Override
41 | public void describeTo(Description description) {
42 | description.appendText(
43 | format("Error message '%s' does not contain substring '%s'.",
44 | actualErrorMessage, expected));
45 | }
46 |
47 | }
48 |
49 | private static class RootCauseStatusCodeMatcher extends BaseMatcher {
50 |
51 | private final int expectedStatusCode;
52 | private int actualStatusCode = -1;
53 |
54 | private RootCauseStatusCodeMatcher(int expectedStatusCode) {
55 | this.expectedStatusCode = expectedStatusCode;
56 | }
57 |
58 | @Override
59 | public boolean matches(Object o) {
60 |
61 | Throwable rootCause = getRootCause((Exception) o);
62 | if(! (rootCause instanceof ODataClientErrorException))
63 | return false;
64 |
65 | actualStatusCode = ((ODataClientErrorException)rootCause)
66 | .getStatusLine().getStatusCode();
67 |
68 | return actualStatusCode == expectedStatusCode;
69 | }
70 |
71 | @Override
72 | public void describeTo(Description description) {
73 | if(actualStatusCode == -1)
74 | description.appendText("Cannot detect status code.");
75 | else
76 | description.appendText(format("Actual status code '%d' does not match expected status code '%d'.",
77 | actualStatusCode, expectedStatusCode));
78 |
79 | }
80 | }
81 |
82 | private Matchers() {
83 | }
84 |
85 | static Matcher carriesStatusCode(int statusCode) {
86 | return new RootCauseStatusCodeMatcher(statusCode);
87 | }
88 |
89 | static Matcher hasServerSideErrorMessage(String substring) {
90 | return new ErrorMessageMatcher(substring);
91 | }
92 |
93 | private static Throwable getRootCause(Throwable thr) {
94 | while(thr.getCause() != null) {
95 | thr = thr.getCause();
96 | };
97 | return thr;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/modules/cli/src/main/java/sap/prd/cmintegration/cli/GetChangeStatus.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import static java.lang.String.format;
4 | import static org.apache.commons.lang3.StringUtils.isBlank;
5 | import static sap.prd.cmintegration.cli.Commands.Helpers.getChangeId;
6 | import static sap.prd.cmintegration.cli.Commands.Helpers.getCommandName;
7 | import static sap.prd.cmintegration.cli.Commands.Helpers.getHost;
8 | import static sap.prd.cmintegration.cli.Commands.Helpers.getPassword;
9 | import static sap.prd.cmintegration.cli.Commands.Helpers.getUser;
10 | import static sap.prd.cmintegration.cli.Commands.Helpers.handleHelpOption;
11 | import static sap.prd.cmintegration.cli.Commands.Helpers.helpRequested;
12 |
13 | import org.apache.commons.cli.CommandLine;
14 | import org.apache.commons.cli.DefaultParser;
15 | import org.apache.commons.cli.Options;
16 | import org.slf4j.Logger;
17 | import org.slf4j.LoggerFactory;
18 |
19 | import com.google.common.base.Preconditions;
20 |
21 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataChange;
22 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient;
23 |
24 | /**
25 | * Command for retrieving the status of a change.
26 | */
27 | @CommandDescriptor(name = "is-change-in-development")
28 | class GetChangeStatus extends Command {
29 |
30 | static class Opts {
31 |
32 | static Options addOptions(Options opts, boolean includeStandardOptions) {
33 |
34 | if(includeStandardOptions) {
35 | Command.addOpts(opts);
36 | }
37 |
38 | return opts.addOption(Commands.CMOptions.CHANGE_ID)
39 | .addOption(Commands.CMOptions.RETURN_CODE);
40 | }
41 | }
42 |
43 | final static private Logger logger = LoggerFactory.getLogger(GetChangeStatus.class);
44 | private String changeId;
45 | private final boolean returnCodeMode;
46 |
47 | GetChangeStatus(String host, String user, String password, String changeId, boolean returnCodeMode) {
48 |
49 | super(host, user, password);
50 |
51 | Preconditions.checkArgument(! isBlank(changeId), "No changeId provided.");
52 |
53 | this.changeId = changeId;
54 | this.returnCodeMode = returnCodeMode;
55 | }
56 |
57 | @Override
58 | void execute() throws Exception {
59 | try (CMODataSolmanClient client = SolmanClientFactory.getInstance().newClient(host, user, password)) {
60 | CMODataChange change = client.getChange(changeId);
61 | logger.debug(format("Change '%s' retrieved from host '%s'. isInDevelopment: '%b'.", change.getChangeID(), host, change.isInDevelopment()));
62 |
63 | if(returnCodeMode) {
64 | if(!change.isInDevelopment()) {
65 | throw new ExitException(ExitException.ExitCodes.FALSE);
66 | }
67 | } else {
68 | System.out.println(change.isInDevelopment());
69 | }
70 | } catch(Exception e) {
71 | logger.warn(format("Change '%s' could not be retrieved from '%s'.", changeId, host), e);
72 | throw e;
73 | }
74 | }
75 |
76 | public final static void main(String[] args) throws Exception {
77 |
78 | if(helpRequested(args)) {
79 | handleHelpOption(getCommandName(GetChangeStatus.class), "",
80 | "Returns 'true' if the given change is in development. Otherwise 'false'.", Opts.addOptions(new Options(), false));
81 | return;
82 | }
83 |
84 | CommandLine commandLine = new DefaultParser().parse(Opts.addOptions(new Options(), true), args);
85 |
86 | new GetChangeStatus(
87 | getHost(commandLine),
88 | getUser(commandLine),
89 | getPassword(commandLine),
90 | getChangeId(commandLine),
91 | commandLine.hasOption(Commands.CMOptions.RETURN_CODE.getOpt())).execute();
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/modules/cli/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 |
7 | com.sap.devops.cmclient
8 | module
9 | 0.0.2-SNAPSHOT
10 | ../..
11 |
12 |
13 | ci-integration-cli
14 | jar
15 |
16 | SAP Change Management Integration - Command Line Interface
17 | SAP Change Management Integration
18 |
19 |
20 | 1.8
21 |
22 |
23 |
24 |
25 | ${project.parent.groupId}
26 | ci-integration-lib-solman
27 | ${project.parent.version}
28 |
29 |
30 | commons-cli
31 | commons-cli
32 | 1.4
33 |
34 |
35 | org.slf4j
36 | slf4j-api
37 |
38 |
39 | junit
40 | junit
41 |
42 |
43 | org.easymock
44 | easymock
45 |
46 |
47 | org.hamcrest
48 | hamcrest-library
49 |
50 |
51 | org.hamcrest
52 | hamcrest-core
53 |
54 |
55 | ${project.parent.groupId}
56 | testutils
57 | ${project.version}
58 |
59 |
60 |
61 |
62 |
63 |
64 | maven-compiler-plugin
65 |
66 | ${java.level}
67 | ${java.level}
68 | ${java.level}
69 | ${java.level}
70 |
71 |
72 |
73 | org.apache.maven.plugins
74 | maven-javadoc-plugin
75 |
76 | package
77 |
78 |
79 |
80 | org.apache.maven.plugins
81 | maven-source-plugin
82 |
83 |
84 |
85 |
86 |
87 | logging
88 |
89 |
90 |
91 | maven-surefire-plugin
92 |
93 |
94 | DEBUG
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 | org.slf4j
103 | slf4j-simple
104 |
105 |
106 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/modules/lib-solman/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 |
7 | com.sap.devops.cmclient
8 | module
9 | 0.0.2-SNAPSHOT
10 | ../..
11 |
12 |
13 | ci-integration-lib-solman
14 | jar
15 |
16 | SAP SOLMAN Change Management Integration Library
17 | SAP SOLMAN Change Management Integration
18 |
19 |
20 | 1.7
21 |
22 |
23 |
24 |
25 | ${project.parent.groupId}
26 | ci-integration-lib-common
27 | ${project.parent.version}
28 |
29 |
30 | org.apache.olingo
31 | odata-client-core
32 |
33 |
34 | com.google.guava
35 | guava
36 |
37 |
38 | junit
39 | junit
40 |
41 |
42 | org.easymock
43 | easymock
44 |
45 |
46 | org.hamcrest
47 | hamcrest-library
48 |
49 |
50 | ${project.parent.groupId}
51 | testutils
52 | ${project.version}
53 |
54 |
55 |
56 |
57 |
58 | src/main/resources
59 | true
60 |
61 |
62 |
63 |
64 |
65 | maven-compiler-plugin
66 |
67 | ${java.level}
68 | ${java.level}
69 | ${java.level}
70 | ${java.level}
71 |
72 |
73 |
74 | org.apache.maven.plugins
75 | maven-javadoc-plugin
76 |
77 |
78 | org.apache.maven.plugins
79 | maven-source-plugin
80 |
81 |
82 |
83 |
84 |
85 |
86 | logging
87 |
88 |
89 |
90 | maven-surefire-plugin
91 |
92 |
93 | TRACE
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | org.slf4j
102 | slf4j-simple
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/modules/cli/src/test/java/sap/prd/cmintegration/cli/SolManBackendUploadFileToTransportTest.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import static java.lang.String.format;
4 | import static org.easymock.EasyMock.capture;
5 | import static org.easymock.EasyMock.expect;
6 | import static org.easymock.EasyMock.expectLastCall;
7 | import static org.hamcrest.Matchers.endsWith;
8 | import static org.hamcrest.Matchers.equalTo;
9 | import static org.hamcrest.Matchers.is;
10 | import static org.junit.Assert.assertThat;
11 |
12 | import java.io.File;
13 | import java.io.IOException;
14 | import java.util.Arrays;
15 | import java.util.UUID;
16 |
17 | import org.apache.commons.io.FileUtils;
18 | import org.easymock.Capture;
19 | import org.easymock.EasyMock;
20 | import org.junit.Rule;
21 | import org.junit.Test;
22 | import org.junit.rules.TemporaryFolder;
23 |
24 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient;
25 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataTransport;
26 |
27 | public class SolManBackendUploadFileToTransportTest extends CMSolmanTestBase {
28 |
29 | @Rule
30 | public TemporaryFolder tmp = new TemporaryFolder();
31 |
32 | private Capture
33 | transportId = Capture.newInstance(),
34 | filePath = Capture.newInstance(),
35 | applicationId = Capture.newInstance();
36 |
37 | @Test
38 | public void testUploadFileStraightForward() throws Exception {
39 |
40 | setMock(setupMock());
41 |
42 | String fileName = UUID.randomUUID().toString() + ".txt";
43 | File upload = tmp.newFile(fileName);
44 | FileUtils.touch(upload);
45 |
46 | Commands.main(new String[] {
47 | "-u", SERVICE_USER,
48 | "-p", SERVICE_PASSWORD,
49 | "-e", SERVICE_ENDPOINT,
50 | "upload-file-to-transport",
51 | "-cID", "8000042445", "-tID", "L21K90002J", "HCP", upload.getAbsolutePath()
52 | });
53 |
54 | assertThat(changeId.getValue(), is(equalTo("8000042445")));
55 | assertThat(transportId.getValue(), is(equalTo("L21K90002J")));
56 | assertThat(filePath.getValue(), endsWith(fileName));
57 | assertThat(applicationId.getValue(), is(equalTo("HCP")));
58 | }
59 |
60 | @Test
61 | public void testUploadFileFailedDueToMissingFile() throws Exception {
62 |
63 | final String fileName = UUID.randomUUID().toString() + ".txt";
64 |
65 | File upload = tmp.newFile(fileName);
66 | if(upload.exists()) {
67 | if(!upload.delete()) {
68 | throw new IOException(format("Cannot delete file '%s'.", upload));
69 | }
70 | }
71 |
72 | assertThat("Upload file which should not be present according to test intention"
73 | + "exists already before test.", upload.exists(), is(equalTo(false)));
74 |
75 | /*
76 | * thrown is set here after assert above. Otherwise a failed assert would end up
77 | * in an error message about an unexpected exception. This would be hard to understand.
78 | */
79 | thrown.expect(IllegalArgumentException.class);
80 | thrown.expectMessage("Cannot read upload file");
81 |
82 | setMock(setupMock());
83 |
84 | Commands.main(new String[] {
85 | "-u", SERVICE_USER,
86 | "-p", SERVICE_PASSWORD,
87 | "-e", SERVICE_ENDPOINT,
88 | "upload-file-to-transport",
89 | "-cID", "8000042445", "-tID", "L21K90002J", "HCP", upload.getAbsolutePath()
90 | });
91 |
92 | }
93 |
94 | private SolmanClientFactory setupMock() throws Exception {
95 |
96 | CMODataSolmanClient clientMock = EasyMock.createMock(CMODataSolmanClient.class);
97 |
98 | expect(clientMock.getChangeTransports(EasyMock.anyString())).andReturn(Arrays.asList(new CMODataTransport("L21K90002J", "J01~JAVA", true, "desc", "me")));
99 | clientMock.uploadFileToTransport(capture(changeId), capture(transportId),
100 | capture(filePath), capture(applicationId)); expectLastCall();
101 | clientMock.close(); expectLastCall().anyTimes();
102 |
103 | SolmanClientFactory factoryMock = EasyMock.createMock(SolmanClientFactory.class);
104 | expect(factoryMock
105 | .newClient(capture(host),
106 | capture(user),
107 | capture(password))).andReturn(clientMock).anyTimes();
108 |
109 | EasyMock.replay(clientMock, factoryMock);
110 |
111 | return factoryMock;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/modules/lib-solman/src/test/java/sap/ai/st/cm/plugins/ciintegration/odataclient/CMODataClientBaseTest.java:
--------------------------------------------------------------------------------
1 | package sap.ai.st.cm.plugins.ciintegration.odataclient;
2 |
3 | import static java.lang.String.format;
4 | import static org.hamcrest.Matchers.equalTo;
5 | import static org.hamcrest.Matchers.is;
6 | import static org.hamcrest.Matchers.not;
7 | import static org.hamcrest.Matchers.nullValue;
8 | import static org.junit.Assert.assertThat;
9 | import static org.junit.Assume.assumeTrue;
10 |
11 | import java.io.File;
12 | import java.io.FileInputStream;
13 | import java.io.IOException;
14 | import java.io.InputStream;
15 | import java.lang.reflect.Field;
16 | import java.net.URI;
17 | import java.util.Properties;
18 |
19 | import org.apache.http.ProtocolVersion;
20 | import org.apache.http.message.BasicStatusLine;
21 | import org.apache.olingo.client.api.ODataClient;
22 | import org.easymock.Capture;
23 | import org.junit.Rule;
24 | import org.junit.Test;
25 | import org.junit.rules.ExpectedException;
26 |
27 | public class CMODataClientBaseTest {
28 |
29 |
30 | protected final static String SERVICE_USER = System.getProperty("CM_SERVICE_USER", "john.doe"),
31 | SERVICE_PASSWORD = System.getProperty("CM_SERVICE_PASSWORD", "openSesame"),
32 | SERVICE_ENDPOINT = System.getProperty("CM_SERVICE_ENDPOINT", "https://example.org/myEndpoint/");
33 |
34 | @Rule
35 | public ExpectedException thrown = ExpectedException.none();
36 |
37 | protected static class StatusLines {
38 | private final static ProtocolVersion HTTP_1_1 = new ProtocolVersion("HTTP", 1, 1);
39 | protected final static BasicStatusLine BAD_REQUEST = new BasicStatusLine(HTTP_1_1, 400, "Bad Request");
40 | protected final static BasicStatusLine UNAUTHORIZED = new BasicStatusLine(HTTP_1_1, 401, "Unauthorized");
41 | protected final static BasicStatusLine NOT_FOUND = new BasicStatusLine(HTTP_1_1, 404, "Not Found.");
42 | }
43 | protected Capture address;
44 |
45 | protected CMODataSolmanClient examinee;
46 |
47 | protected void setup() throws Exception {
48 |
49 | address = Capture.newInstance();
50 |
51 | examinee = new CMODataSolmanClient(
52 | SERVICE_ENDPOINT,
53 | SERVICE_USER,
54 | SERVICE_PASSWORD);
55 |
56 | }
57 |
58 | protected void tearDown() throws Exception {
59 | examinee = null;
60 | address = null;
61 | }
62 |
63 | protected static void setMock(CMODataSolmanClient examinee, ODataClient mock) throws Exception {
64 | Field client = CMODataSolmanClient.class.getDeclaredField("client");
65 | client.setAccessible(true);
66 | client.set(examinee, mock);
67 | client.setAccessible(false);
68 | }
69 |
70 |
71 | @Test
72 | public void testGetShortVersion() throws Exception {
73 |
74 | // we depend on the mvn build. mvn package or similar needs to be executed first.
75 | File versionFile = new File("target/classes/VERSION");
76 | assumeTrue(versionFile.exists());
77 |
78 | String actualShortVersion = CMODataSolmanClient.getShortVersion(),
79 | expectedShortVersion = getVersionProperties(versionFile).getProperty("mvnProjectVersion");
80 |
81 | assertThat(expectedShortVersion, is(not(nullValue())));
82 | assertThat(actualShortVersion, is(equalTo(expectedShortVersion)));
83 | }
84 |
85 | @Test
86 | public void testLongShortVersion() throws Exception {
87 |
88 | // we depend on the mvn build. mvn package or similar needs to be executed first.
89 | File versionFile = new File("target/classes/VERSION");
90 | assumeTrue(versionFile.exists());
91 |
92 | Properties vProps = getVersionProperties(versionFile);
93 | String actualLongVersion = CMODataSolmanClient.getLongVersion(),
94 | expectedLongVersion = format("%s : %s",
95 | vProps.getProperty("mvnProjectVersion"),
96 | vProps.getProperty("gitCommitId"));
97 |
98 | assertThat(expectedLongVersion, is(not(nullValue())));
99 | assertThat(actualLongVersion, is(equalTo(expectedLongVersion)));
100 | }
101 |
102 | private static Properties getVersionProperties(File versionFile) throws IOException {
103 | try(InputStream is = new FileInputStream(versionFile)) {
104 | Properties vProps = new Properties();
105 | vProps.load(is);
106 | return vProps;
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Important Notice
2 |
3 | This public repository is read-only and no longer maintained.
4 |
5 | 
6 |
7 | ---
8 |
9 | # Description
10 |
11 | Simple command line interface to handle basic change management related actions
12 | in SAP Solution Manager via ODATA requests. The client is intended to be used
13 | in continuous integration and continuous delivery scenarios and supports only
14 | the actions necessary within those scenarios. See section _Usage_ for more details.
15 |
16 | # Requirements
17 | ### SAP Solution Manager Functionality
18 | - SAP Solution Manager 7.2 SP6, SP7 -> cm_client v1.x
19 | - SAP Solution Manager 7.2 SP 8 and higher -> cm_client v2.0
20 |
21 | ### General Requirements
22 | - JDK 8 to build this project (to run the client JRE 8 is sufficient) OR
23 | - a Docker environment to run the Docker image
24 |
25 | # Download and Installation
26 | This command line client can be consumed either as a Java application from [maven.org](https://repo1.maven.org/maven2/com/sap/devops/cmclient/dist.cli) or as a Docker image from [hub.docker.com](https://hub.docker.com/r/ppiper/cm-client).
27 |
28 | The public key for verifing the artifacts is available [here](https://keys.openpgp.org/vks/v1/by-fingerprint/D59BDC1A924385CFEE6AA5962F55B9DDAC28BFAF)
29 |
30 | ## Using the Docker Image
31 |
32 | On a Linux machine, you can run:
33 |
34 | `docker run --rm ppiper/cm-client cmclient --help`
35 |
36 | This prints the help information of the CM Client. For a comprehensive overview of available commands, please read the [usage information](#usage) below.
37 |
38 | ### How to Build the Docker Image
39 |
40 | The Dockerfile is located in a designated branch [`dockerfile`](https://github.com/SAP/devops-cm-client/tree/docker). After checking out the branch, you can run:
41 | `docker build -t cm-client .`
42 |
43 | ## Using the Java Application from maven.org
44 |
45 | - Download the command line interface package from [maven.org](http://repo1.maven.org/maven2/com/sap/devops/cmclient/dist.cli).
46 | - Extract the command line interface package into a suitable folder.
47 |
48 | Example:
49 | ```
50 | CM_VERSION=2.0.1
51 | export CMCLIENT_HOME=`pwd`/cm_client
52 | export PATH=${CMCLIENT_HOME}/bin:${PATH}
53 | mkdir -p ${CMCLIENT_HOME}
54 | curl "https://repo1.maven.org/maven2/com/sap/devops/cmclient/dist.cli/${CM_VERSION}/dist.cli-${CM_VERSION}.tar.gz" \
55 | |tar -C ${CMCLIENT_HOME} -xvf -
56 | cmclient --version
57 | cmclient --help
58 | ```
59 | It is recommanded to define `CMCLIENT_HOME` and `PATH` in `~/.bash_profile` or in any other suitable way.
60 |
61 |
62 | # Usage of the CLI
63 |
64 | ````
65 | [COMMON_OPTIONS...] [SUBCOMMAND_OPTIONS]
66 | ````
67 |
68 | To pass additional Java options to the command (for example, another truststore), set the environment variable `CMCLIENT_OPTS`
69 |
70 | | Option | Description |
71 | |--------------------------|-------------------------|
72 | | `-e`, `--endpoint ` | Service endpoint |
73 | | `-h`, `--help` | Prints this help. |
74 | | `-p`, `--password ` | Service password, if '-' is provided, password will be read from stdin. |
75 | | `-u`, `--user ` | Service user. |
76 | | `-v`, `--version` | Prints the version. |
77 |
78 |
79 | | Subcommand | Description |
80 | |-----------------------------------|-------------------------------------------------|
81 | | `create-transport` | Creates a new transport entity. |
82 | | `get-transport-description` | Returns the description of the transport. |
83 | | `get-transport-owner` | Returns the owner of the transport. |
84 | | `get-transports` | Returns the IDs of the transports. |
85 | | `is-change-in-development` | Returns 'true' if the change is in development. |
86 | | `is-transport-modifiable` | Returns 'true' if the transport is modifiable. |
87 | | `release-transport` | Releases the transport. |
88 | | `upload-file-to-transport` | Uploads a file to a transport. |
89 | | `get-transport-development-system`| Returns the target system of the transport. |
90 |
91 | For more information about subcommands and subcommand options run ` --help`.
92 |
93 | # How to obtain support
94 |
95 | Feel free to open new issues for feature requests, bugs or general feedback on
96 | the [GitHub issues page of this project][cm-cli-issues].
97 |
98 | # Contributing
99 |
100 | Read and understand our [contribution guidelines][contribution]
101 | before opening a pull request.
102 |
103 |
104 | [cm-cli-issues]: https://github.com/SAP/devops-cm-client/issues
105 | [license]: ./LICENSE
106 | [contribution]: ./CONTRIBUTING.md
107 |
108 | # Release Notes
109 | The release notes are available [here](RELEASES.md).
110 |
--------------------------------------------------------------------------------
/modules/cli/src/main/java/sap/prd/cmintegration/cli/UploadFileToTransportSOLMAN.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import static com.google.common.base.Preconditions.checkArgument;
4 | import static java.lang.String.format;
5 | import static org.apache.commons.lang3.StringUtils.isBlank;
6 | import static sap.prd.cmintegration.cli.Commands.Helpers.getArg;
7 | import static sap.prd.cmintegration.cli.Commands.Helpers.getChangeId;
8 | import static sap.prd.cmintegration.cli.Commands.Helpers.getCommandName;
9 | import static sap.prd.cmintegration.cli.Commands.Helpers.getHost;
10 | import static sap.prd.cmintegration.cli.Commands.Helpers.getPassword;
11 | import static sap.prd.cmintegration.cli.Commands.Helpers.getUser;
12 | import static sap.prd.cmintegration.cli.Commands.Helpers.handleHelpOption;
13 | import static sap.prd.cmintegration.cli.Commands.Helpers.helpRequested;
14 |
15 | import java.io.File;
16 | import java.util.function.Function;
17 |
18 | import org.apache.commons.cli.CommandLine;
19 | import org.apache.commons.cli.DefaultParser;
20 | import org.apache.commons.cli.Options;
21 | import org.slf4j.Logger;
22 | import org.slf4j.LoggerFactory;
23 |
24 | import com.sap.cmclient.Transport;
25 |
26 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient;
27 |
28 | /**
29 | * Command for uploading a file into a transport.
30 | */
31 | @CommandDescriptor(name = "upload-file-to-transport")
32 | class UploadFileToTransportSOLMAN extends TransportRelatedSOLMAN {
33 |
34 | static class Opts {
35 |
36 | static Options addOptions(Options options, boolean includeStandardOpts) {
37 |
38 | if (includeStandardOpts) {
39 | Command.addOpts(options);
40 | }
41 |
42 | TransportRelatedSOLMAN.Opts.addOptions(options, false).addOption(Commands.CMOptions.CHANGE_ID);
43 |
44 | return options;
45 | }
46 | }
47 |
48 | final static private Logger logger = LoggerFactory.getLogger(TransportRelatedSOLMAN.class);
49 |
50 | private final String applicationId;
51 |
52 | private final File upload;
53 |
54 | UploadFileToTransportSOLMAN(String host, String user, String password, String changeId, String transportId,
55 | String applicationId, String filePath, boolean returnCodeMode) {
56 |
57 | super(host, user, password, changeId, transportId, returnCodeMode);
58 |
59 | checkArgument(!isBlank(applicationId), "applicationId was null or empty.");
60 | checkArgument(!isBlank(filePath), "filePath was null or empty.");
61 |
62 | this.applicationId = applicationId;
63 | this.upload = new File(filePath);
64 |
65 | checkArgument(this.upload.canRead(), format("Cannot read upload file '%s'.", this.upload));
66 | }
67 |
68 | public final static void main(String[] args) throws Exception {
69 |
70 | logger.debug(format("%s called with arguments: '%s'.", UploadFileToTransportSOLMAN.class.getSimpleName(),
71 | Commands.Helpers.getArgsLogString(args)));
72 |
73 | if (helpRequested(args)) {
74 | handleHelpOption(getCommandName(UploadFileToTransportSOLMAN.class), "",
75 | "Uploads the file specified by into the given transport. "
76 | + " specifies how the file needs to be handled on server side.",
77 | Opts.addOptions(new Options(), false).addOption(Commands.CMOptions.CHANGE_ID));
78 | return;
79 | }
80 |
81 | CommandLine commandLine = new DefaultParser()
82 | .parse(Opts.addOptions(new Options(), true).addOption(Commands.CMOptions.CHANGE_ID), args);
83 |
84 | new UploadFileToTransportSOLMAN(getHost(commandLine), getUser(commandLine), getPassword(commandLine),
85 | getChangeId(commandLine), getTransportId(commandLine), getApplicationId(commandLine),
86 | getFilePath(commandLine), isReturnCodeMode(commandLine)).execute();
87 | }
88 |
89 | static String getApplicationId(CommandLine commandLine) {
90 | return getArg(commandLine, 1, "applicationId");
91 | }
92 |
93 | static String getFilePath(CommandLine commandLine) {
94 | return getArg(commandLine, 2, "filePath");
95 | }
96 |
97 | @Override
98 | protected Function getAction() {
99 |
100 | return new Function() {
101 |
102 | @Override
103 | public String apply(Transport t) {
104 | try (CMODataSolmanClient client = SolmanClientFactory.getInstance().newClient(host, user, password)) {
105 |
106 | logger.debug(format(
107 | "Uploading file '%s' to transport '%s' for change document '%s' with applicationId '%s'.",
108 | upload.getAbsolutePath(), transportId, changeId, applicationId));
109 |
110 | client.uploadFileToTransport(changeId, transportId, upload.getAbsolutePath(), applicationId);
111 |
112 | logger.debug(format(
113 | "File '%s' uploaded to transport '%s' for change document '%s' with applicationId '%s'.",
114 | upload.getAbsolutePath(), transportId, changeId, applicationId));
115 |
116 | return null;
117 | } catch (Exception e) {
118 | logger.error(format(
119 | "Exception caught while uploading file '%s' to transport '%s' for change document '%s' with applicationId '%s'",
120 | upload.getAbsolutePath(), transportId, changeId, applicationId));
121 | throw new ExitException(e, 1);
122 | }
123 | }
124 | };
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/modules/lib-solman/src/test/java/sap/ai/st/cm/plugins/ciintegration/odataclient/CMODataClientTransportMarshallingTest.java:
--------------------------------------------------------------------------------
1 | package sap.ai.st.cm.plugins.ciintegration.odataclient;
2 |
3 | import static org.hamcrest.Matchers.equalTo;
4 | import static org.hamcrest.Matchers.is;
5 | import static org.hamcrest.Matchers.nullValue;
6 | import static org.junit.Assert.assertThat;
7 |
8 | import java.util.List;
9 |
10 | import org.apache.olingo.client.api.domain.ClientEntity;
11 | import org.apache.olingo.client.api.domain.ClientProperty;
12 | import org.apache.olingo.client.core.domain.ClientEntityImpl;
13 | import org.apache.olingo.client.core.domain.ClientObjectFactoryImpl;
14 | import org.apache.olingo.client.core.domain.ClientPropertyImpl;
15 | import org.apache.olingo.commons.api.edm.FullQualifiedName;
16 | import org.junit.Rule;
17 | import org.junit.Test;
18 | import org.junit.rules.ExpectedException;
19 |
20 | public class CMODataClientTransportMarshallingTest {
21 |
22 |
23 | @Rule
24 | public ExpectedException thrown = ExpectedException.none();
25 |
26 | @Test
27 | public void testMarshallTransportwithoutTransportIdFails() {
28 |
29 | thrown.expect(IllegalStateException.class);
30 | thrown.expectMessage("Transport id found to be null or empty");
31 |
32 | ClientEntity transport = new ClientEntityImpl(new FullQualifiedName("AI_CRM_GW_CM_CI_SRV.Change"));
33 | CMODataSolmanClient.toTransport("x", transport);
34 | }
35 |
36 | @Test
37 | public void testMarshallTransportWithoutModifiableFlaFail() {
38 |
39 | thrown.expect(IllegalStateException.class);
40 | thrown.expectMessage("Modifiable flag found to be null or empty");
41 |
42 | ClientEntity transport = new ClientEntityImpl(new FullQualifiedName("AI_CRM_GW_CM_CI_SRV.Change"));
43 | transport.getProperties().add(new ClientPropertyImpl("TransportID", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("8000038673").build()));
44 | transport.getProperties().add(new ClientPropertyImpl("DevelopmentSystemID", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("J01~JAVA").build()));
45 | CMODataSolmanClient.toTransport("x", transport);
46 | }
47 |
48 | @Test
49 | public void testMarshallTransportWithoutDescriptionSucceeds() {
50 |
51 | ClientEntity transportEnity = new ClientEntityImpl(new FullQualifiedName("AI_CRM_GW_CM_CI_SRV.Change"));
52 | List props = transportEnity.getProperties();
53 | props.add(new ClientPropertyImpl("TransportID", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("8000038673").build()));
54 | props.add(new ClientPropertyImpl("DevelopmentSystemID", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("J01~JAVA").build()));
55 | props.add(new ClientPropertyImpl("IsModifiable", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("true").build()));
56 | CMODataTransport transport = CMODataSolmanClient.toTransport("x", transportEnity);
57 | assertThat(transport.getDescription(), is(nullValue()));
58 | }
59 |
60 | @Test
61 | public void testMarshallTransportWithoutOwnerSucceeds() {
62 |
63 | ClientEntity transportEnity = new ClientEntityImpl(new FullQualifiedName("AI_CRM_GW_CM_CI_SRV.Change"));
64 | List props = transportEnity.getProperties();
65 | props.add(new ClientPropertyImpl("TransportID", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("8000038673").build()));
66 | props.add(new ClientPropertyImpl("DevelopmentSystemID", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("J01~JAVA").build()));
67 | props.add(new ClientPropertyImpl("IsModifiable", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("true").build()));
68 | CMODataTransport transport = CMODataSolmanClient.toTransport("x", transportEnity);
69 | assertThat(transport.getOwner(), is(nullValue()));
70 | }
71 |
72 | @Test
73 | public void testAllMembersMarshalled() {
74 |
75 | ClientEntity transportEnity = new ClientEntityImpl(new FullQualifiedName("AI_CRM_GW_CM_CI_SRV.Change"));
76 | List props = transportEnity.getProperties();
77 | props.add(new ClientPropertyImpl("TransportID", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("8000038673").build()));
78 | props.add(new ClientPropertyImpl("DevelopmentSystemID", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("J01~JAVA").build()));
79 | props.add(new ClientPropertyImpl("IsModifiable", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("true").build()));
80 | props.add(new ClientPropertyImpl("Owner", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("me").build()));
81 | props.add(new ClientPropertyImpl("Description", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("Lorem ipsum").build()));
82 |
83 | CMODataTransport transport = CMODataSolmanClient.toTransport("x", transportEnity);
84 |
85 | assertThat(transport.getTransportID(), is(equalTo("8000038673")));
86 | assertThat(transport.isModifiable(), is(equalTo(true)));
87 | assertThat(transport.getOwner(), is(equalTo("me")));
88 | assertThat(transport.getDescription(), is(equalTo("Lorem ipsum")));
89 | }
90 |
91 |
92 | }
93 |
--------------------------------------------------------------------------------
/modules/cli/src/main/java/sap/prd/cmintegration/cli/CreateTransportSOLMAN.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import static java.lang.String.format;
4 | import static org.apache.commons.lang3.StringUtils.isBlank;
5 | import static sap.prd.cmintegration.cli.Commands.Helpers.getChangeId;
6 | import static sap.prd.cmintegration.cli.Commands.Helpers.getDevelopmentSystemId;
7 | import static sap.prd.cmintegration.cli.Commands.Helpers.getCommandName;
8 | import static sap.prd.cmintegration.cli.Commands.Helpers.getHost;
9 | import static sap.prd.cmintegration.cli.Commands.Helpers.getPassword;
10 | import static sap.prd.cmintegration.cli.Commands.Helpers.getUser;
11 | import static sap.prd.cmintegration.cli.Commands.Helpers.handleHelpOption;
12 | import static sap.prd.cmintegration.cli.Commands.Helpers.helpRequested;
13 | import static sap.prd.cmintegration.cli.Commands.CMOptions.newOption;
14 |
15 | import org.apache.commons.cli.CommandLine;
16 | import org.apache.commons.cli.DefaultParser;
17 | import org.apache.commons.cli.Option;
18 | import org.apache.commons.cli.Options;
19 | import org.slf4j.Logger;
20 | import org.slf4j.LoggerFactory;
21 |
22 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient;
23 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataTransport;
24 |
25 | /**
26 | * Command for creating a transport for a change in SAP Solution Manager.
27 | */
28 | @CommandDescriptor(name = "create-transport")
29 | class CreateTransportSOLMAN extends Command {
30 |
31 | static class Opts {
32 |
33 | static Option owner = newOption("o", "owner", "The transport owner. If ommited the login user us used.", "owner", false),
34 | description = newOption("d", "description", "The description of the transport request.", "desc", false);
35 |
36 | static Options addOptions(Options opts, boolean addStandardOptions) {
37 | if(addStandardOptions) {
38 | Command.addOpts(opts);
39 | }
40 |
41 | return opts.addOption(Commands.CMOptions.CHANGE_ID)
42 | .addOption(Commands.CMOptions.DEVELOPMENT_SYSTEM_ID)
43 | .addOption(owner)
44 | .addOption(description);
45 | }
46 | }
47 |
48 | final static private Logger logger = LoggerFactory.getLogger(CreateTransportSOLMAN.class);
49 | private final String changeId, developmentSystemId, owner, description;
50 |
51 | public CreateTransportSOLMAN(String host, String user, String password, String changeId, String developmentSystemId,
52 | String owner, String description) {
53 | super(host, user, password);
54 | this.changeId = changeId;
55 | this.owner = owner;
56 | this.description = description;
57 | this.developmentSystemId = developmentSystemId;
58 | }
59 |
60 | public final static void main(String[] args) throws Exception {
61 |
62 | if(helpRequested(args)) {
63 | handleHelpOption(getCommandName(CreateTransportSOLMAN.class), "",
64 | "Creates a new transport entity. " +
65 | "Returns the ID of the transport entity. " +
66 | "If there is already an open transport, the ID of the already existing open transport might be returned.",
67 | Opts.addOptions(new Options(), false));
68 |
69 | return;
70 | }
71 |
72 | CommandLine commandLine = new DefaultParser().parse(Opts.addOptions(new Options(), true), args);
73 |
74 | new CreateTransportSOLMAN(
75 | getHost(commandLine),
76 | getUser(commandLine),
77 | getPassword(commandLine),
78 | getChangeId(commandLine),
79 | getDevelopmentSystemId(commandLine),
80 | commandLine.getOptionValue(Opts.owner.getOpt()),
81 | commandLine.getOptionValue(Opts.description.getOpt())).execute();
82 | }
83 |
84 | @Override
85 | void execute() throws Exception {
86 | try(CMODataSolmanClient client = SolmanClientFactory.getInstance().newClient(host, user, password)) {
87 | logger.debug(format("Creating transport request for changeId '%s'.", changeId));
88 |
89 | CMODataTransport transport;
90 | if(owner == null && description == null) {
91 |
92 | transport = client.createDevelopmentTransport(changeId, developmentSystemId);
93 |
94 | } else {
95 |
96 | String d = isBlank(description) ? "" : description,
97 | o = isBlank(owner) ? user : owner;
98 |
99 | logger.debug(format("Creating transport with owner '%s' and description '%s'", o, d));
100 | transport = client.createDevelopmentTransportAdvanced(
101 | changeId, developmentSystemId, d, o);
102 | }
103 | logger.debug(format("Transport '%s' created for change document '%s'. isModifiable: '%b', Owner: '%s', Description: '%s'.",
104 | transport.getTransportID(), changeId, transport.isModifiable(), transport.getOwner(), transport.getDescription()));
105 | System.out.println(transport.getTransportID());
106 | System.out.flush();
107 | } catch(final Exception e) {
108 | logger.error(format("Exception caught while created transport request for change document '%s'.",changeId), e);
109 | throw e;
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/modules/cli/src/test/java/sap/prd/cmintegration/cli/SolManBackendGetChangeTransportModifiableTest.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import static org.hamcrest.Matchers.equalTo;
4 | import static org.hamcrest.Matchers.is;
5 | import static org.junit.Assert.assertThat;
6 |
7 | import org.apache.commons.cli.MissingOptionException;
8 | import org.apache.commons.io.IOUtils;
9 | import org.apache.olingo.client.api.communication.ODataClientErrorException;
10 | import org.junit.Test;
11 |
12 | public class SolManBackendGetChangeTransportModifiableTest extends SolManBackendCMTransportTestBase {
13 |
14 | @Test
15 | public void getChangeTransportModifiableStraighForwardForNotModifiableTransport() throws Exception {
16 |
17 | setMock(setupMock("L21K900026", "J01~JAVA", "xOwner", "xDescription", false));
18 | Commands.main(new String[] {
19 | "-u", SERVICE_USER,
20 | "-p", SERVICE_PASSWORD,
21 | "-e", SERVICE_ENDPOINT,
22 | "is-transport-modifiable",
23 | "-cID", "8000038673", "-tID", "L21K900026"});
24 |
25 | assertThat(Boolean.valueOf(removeCRLF(IOUtils.toString(result.toByteArray(), "UTF-8"))),
26 | is(equalTo(false)));
27 |
28 | assertThat(changeId.getValue(), is(equalTo("8000038673")));
29 | }
30 |
31 | @Test
32 | public void getChangeTransportModifiableReturnCodeStraighForwardForNotModifiableTransport() throws Exception {
33 |
34 | thrown.expect(ExitException.class);
35 | thrown.expect(Matchers.exitCode(ExitException.ExitCodes.FALSE));
36 |
37 | setMock(setupMock("L21K900026", "J01~JAVA", "xOwner", "xDescription", false));
38 | Commands.main(new String[] {
39 | "-u", SERVICE_USER,
40 | "-p", SERVICE_PASSWORD,
41 | "-e", SERVICE_ENDPOINT,
42 | "is-transport-modifiable",
43 | "--return-code",
44 | "-cID", "8000038673", "-tID", "L21K900026"});
45 | }
46 |
47 | @Test
48 | public void getChangeTransportModifiableStraighForwardForModifiableTransport() throws Exception {
49 |
50 | setMock(setupMock("L21K900026", "J01~JAVA", "xOwner", "xDescription", true));
51 | Commands.main(new String[] {
52 | "-u", SERVICE_USER,
53 | "-p", SERVICE_PASSWORD,
54 | "-e", SERVICE_ENDPOINT,
55 | "is-transport-modifiable",
56 | "-cID", "8000038673", "-tID", "L21K900026"});
57 |
58 | assertThat(Boolean.valueOf(removeCRLF(IOUtils.toString(result.toByteArray(), "UTF-8"))),
59 | is(equalTo(true)));
60 |
61 | assertThat(changeId.getValue(), is(equalTo("8000038673")));
62 | }
63 |
64 | @Test
65 | public void getChangeTransportModifiableStraighForwardReturnCodeForModifiableTransport() throws Exception {
66 |
67 | // the absence of an ExitException means return code zero
68 | setMock(setupMock("L21K900026", "J01~JAVA", "xOwner", "xDescription", true));
69 |
70 | Commands.main(new String[] {
71 | "-u", SERVICE_USER,
72 | "-p", SERVICE_PASSWORD,
73 | "-e", SERVICE_ENDPOINT,
74 | "is-transport-modifiable",
75 | "--return-code",
76 | "-cID", "8000038673", "-tID", "L21K900026"});
77 |
78 | assertThat(removeCRLF(IOUtils.toString(result.toByteArray(), "UTF-8")),
79 | is(equalTo("")));
80 |
81 | assertThat(changeId.getValue(), is(equalTo("8000038673")));
82 | }
83 |
84 | @Test
85 | public void getChangeTransportModifiableForNotExistingTransport() throws Exception {
86 |
87 | thrown.expect(CMCommandLineException.class);
88 | thrown.expectMessage("Transport 'DOES_NOT_EXIST' not found for change '8000038673'.");
89 |
90 | setMock(setupMock("L21K900026", "J01~JAVA", "xOwner", "xDescription", false));
91 | Commands.main(new String[] {
92 | "-u", SERVICE_USER,
93 | "-p", SERVICE_PASSWORD,
94 | "-e", SERVICE_ENDPOINT,
95 | "is-transport-modifiable",
96 | "-cID", "8000038673", "-tID", "DOES_NOT_EXIST"});
97 | }
98 |
99 | @Test
100 | public void getChangeTransportModifiableForNotExistingChange() throws Exception {
101 |
102 | thrown.expect(ODataClientErrorException.class);
103 | thrown.expectMessage("400");
104 |
105 | //Comment line and asserts for the captures below in order to run against real back-end.
106 | setMock(setupMock(new ODataClientErrorException(StatusLines.BAD_REQUEST)));
107 |
108 | try {
109 | Commands.main(new String[] {
110 | "-u", SERVICE_USER,
111 | "-p", SERVICE_PASSWORD,
112 | "-e", SERVICE_ENDPOINT,
113 | "is-transport-modifiable",
114 | "-cID", "DOES_NOT_EXIST", "-tID", "NOT_NEEDED"});
115 | } catch(ODataClientErrorException ex) {
116 | assertThat(changeId.getValue(), is(equalTo("DOES_NOT_EXIST")));
117 | throw ex;
118 | }
119 | }
120 |
121 | @Test
122 | public void getChangeTransportModifiableWithoutProvidingTransportId() throws Exception {
123 |
124 | thrown.expect(MissingOptionException.class);
125 | thrown.expectMessage("tID");
126 |
127 | Commands.main(new String[] {
128 | "-u", SERVICE_USER,
129 | "-p", SERVICE_PASSWORD,
130 | "-e", SERVICE_ENDPOINT,
131 | "is-transport-modifiable",
132 | "-cID", "8000038673"});
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/modules/cli/src/test/java/sap/prd/cmintegration/cli/SolManBackendGetChangeTransportsTest.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import static java.util.Arrays.asList;
4 | import static org.easymock.EasyMock.capture;
5 | import static org.easymock.EasyMock.createMock;
6 | import static org.easymock.EasyMock.expect;
7 | import static org.easymock.EasyMock.expectLastCall;
8 | import static org.easymock.EasyMock.replay;
9 | import static org.hamcrest.Matchers.contains;
10 | import static org.hamcrest.Matchers.equalTo;
11 | import static org.hamcrest.Matchers.is;
12 | import static org.junit.Assert.assertThat;
13 |
14 | import java.util.ArrayList;
15 | import java.util.Collection;
16 | import java.util.regex.Pattern;
17 |
18 | import org.apache.commons.io.IOUtils;
19 | import org.easymock.EasyMock;
20 | import org.hamcrest.BaseMatcher;
21 | import org.hamcrest.Description;
22 | import org.junit.Test;
23 |
24 | import com.sap.cmclient.Transport;
25 |
26 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient;
27 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataTransport;
28 |
29 | public class SolManBackendGetChangeTransportsTest extends SolManBackendCMTransportTestBase {
30 |
31 | @Test
32 | public void testStraightForward() throws Exception{
33 |
34 | //
35 | // Comment line below in order to go against the real back-end as specified via -h
36 | setMock(setupMock());
37 |
38 | Commands.main(new String[] {
39 | "-u", SERVICE_USER,
40 | "-p", SERVICE_PASSWORD,
41 | "-e", SERVICE_ENDPOINT,
42 | "get-transports",
43 | "-cID", "8000038673"});
44 |
45 | Collection transportIds = asList(IOUtils.toString(result.toByteArray(), "UTF-8").split("\\r?\\n"));
46 | assertThat(transportIds, contains(
47 | "L21K900026",
48 | "L21K900028",
49 | "L21K900029",
50 | "L21K90002A",
51 | "L21K90002B",
52 | "L21K90002C",
53 | "L21K90002D",
54 | "L21K90002E"));
55 | assertThat(transportIds.size(), is(equalTo(8)));
56 |
57 | assertThat(changeId.getValue(), is(equalTo("8000038673")));
58 | }
59 |
60 | @Test
61 | public void testModifiablesOnly() throws Exception{
62 |
63 | //
64 | // Comment line below in order to go against the real back-end as specified via -h
65 | setMock(setupMock());
66 |
67 | Commands.main(new String[] {
68 | "-u", SERVICE_USER,
69 | "-p", SERVICE_PASSWORD,
70 | "-e", SERVICE_ENDPOINT,
71 | "get-transports", "-m",
72 | "-cID", "8000038673"});
73 |
74 | Collection transportIds = asList(IOUtils.toString(result.toByteArray(), "UTF-8").split("\\r?\\n"));
75 | assertThat(transportIds, contains("L21K90002E"));
76 | assertThat(transportIds.size(), is(equalTo(1)));
77 |
78 | assertThat(changeId.getValue(), is(equalTo("8000038673")));
79 | }
80 |
81 | @Test
82 | public void testHelp() throws Exception {
83 | Commands.main(new String[] {
84 | "get-transports",
85 | "--help"});
86 | String helpText = IOUtils.toString(result.toByteArray(), "UTF-8");
87 |
88 | assertThat(helpText, new BaseMatcher() {
89 |
90 | String expected = ".*-m,--modifiable-only[\\s]*Returns modifiable transports only.*";
91 | String actual;
92 |
93 | @Override
94 | public boolean matches(Object item) {
95 | if(! (item instanceof String)) {
96 | return false;
97 | }
98 |
99 | actual = (String) item;
100 | return Pattern.compile(expected, Pattern.MULTILINE).matcher(actual).find();
101 | }
102 |
103 | @Override
104 | public void describeTo(Description description) {
105 | description.appendText(String.format("Expected regex '%s' not found in '%s'.", expected, actual));
106 | }
107 | });
108 | }
109 |
110 | private SolmanClientFactory setupMock() throws Exception {
111 |
112 | ArrayList transports = new ArrayList<>();
113 | transports.add(new CMODataTransport("L21K900026", "J01~JAVA", false, "Description", "Owner"));
114 | transports.add(new CMODataTransport("L21K900028", "J01~JAVA", false, "Description", "Owner"));
115 | transports.add(new CMODataTransport("L21K900029", "J01~JAVA", false, "Description", "Owner"));
116 | transports.add(new CMODataTransport("L21K90002A", "J01~JAVA", false, "Description", "Owner"));
117 | transports.add(new CMODataTransport("L21K90002B", "J01~JAVA", false, "Description", "Owner"));
118 | transports.add(new CMODataTransport("L21K90002C", "J01~JAVA", false, "Description", "Owner"));
119 | transports.add(new CMODataTransport("L21K90002D", "J01~JAVA", false, "Description", "Owner"));
120 | transports.add(new CMODataTransport("L21K90002E", "J01~JAVA", true, "Description", "Owner"));
121 |
122 | CMODataSolmanClient clientMock = createMock(CMODataSolmanClient.class);
123 | expect(clientMock.getChangeTransports(capture(changeId))).andReturn(transports);
124 |
125 | SolmanClientFactory factoryMock = EasyMock.createMock(SolmanClientFactory.class);
126 | expect(factoryMock
127 | .newClient(capture(host),
128 | capture(user),
129 | capture(password))).andReturn(clientMock);
130 | clientMock.close(); expectLastCall();
131 |
132 | replay(clientMock, factoryMock);
133 |
134 | return factoryMock;
135 | }
136 |
137 |
138 | }
139 |
--------------------------------------------------------------------------------
/modules/cli/src/main/java/sap/prd/cmintegration/cli/GetChangeTransports.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import static java.lang.String.format;
4 | import static org.apache.commons.lang3.StringUtils.isBlank;
5 | import static sap.prd.cmintegration.cli.Commands.Helpers.getChangeId;
6 | import static sap.prd.cmintegration.cli.Commands.Helpers.getCommandName;
7 | import static sap.prd.cmintegration.cli.Commands.Helpers.getHost;
8 | import static sap.prd.cmintegration.cli.Commands.Helpers.getPassword;
9 | import static sap.prd.cmintegration.cli.Commands.Helpers.getUser;
10 | import static sap.prd.cmintegration.cli.Commands.Helpers.handleHelpOption;
11 | import static sap.prd.cmintegration.cli.Commands.Helpers.helpRequested;
12 |
13 | import java.util.List;
14 | import java.util.function.Predicate;
15 |
16 | import org.apache.commons.cli.CommandLine;
17 | import org.apache.commons.cli.DefaultParser;
18 | import org.apache.commons.cli.Option;
19 | import org.apache.commons.cli.Options;
20 | import org.slf4j.Logger;
21 | import org.slf4j.LoggerFactory;
22 |
23 | import com.google.common.base.Preconditions;
24 | import com.sap.cmclient.Transport;
25 |
26 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient;
27 |
28 | /**
29 | * Command for for retrieving the transport of a change. Depending on the options
30 | * handed over to that command only the mofifiable transports are
31 | * returned.
32 | */
33 | @CommandDescriptor(name="get-transports")
34 | class GetChangeTransports extends Command {
35 |
36 | private static class Opts {
37 |
38 | final static Option modifiableOnlyOption = new Option("m", "modifiable-only", false, "Returns modifiable transports only.");
39 |
40 | static Options addOptions(Options options, boolean includeStandardOptions) {
41 | if(includeStandardOptions) {
42 | Command.addOpts(options);
43 | }
44 | options.addOption(Commands.CMOptions.CHANGE_ID);
45 | options.addOption(modifiableOnlyOption);
46 | return options;
47 | }
48 | }
49 |
50 | final static private Logger logger = LoggerFactory.getLogger(GetChangeTransports.class);
51 | private final String changeId;
52 |
53 | private final boolean modifiableOnly;
54 |
55 | GetChangeTransports(String host, String user, String password, String changeId,
56 | boolean modifiableOnly) {
57 |
58 | super(host, user, password);
59 |
60 | Preconditions.checkArgument(! isBlank(changeId), "No changeId provided.");
61 |
62 | this.changeId = changeId;
63 | this.modifiableOnly = modifiableOnly;
64 | }
65 |
66 | public final static void main(String[] args) throws Exception {
67 |
68 | if(helpRequested(args)) {
69 | handleHelpOption(getCommandName(GetChangeTransports.class), "",
70 | "Returns the ids of the transports for the given change.",
71 | Opts.addOptions(Opts.addOptions(new Options(), false), false));
72 | return;
73 | }
74 |
75 | CommandLine commandLine = new DefaultParser().parse(Opts.addOptions(new Options(), true), args);
76 |
77 | new GetChangeTransports(
78 | getHost(commandLine),
79 | getUser(commandLine),
80 | getPassword(commandLine),
81 | getChangeId(commandLine),
82 | commandLine.hasOption(Opts.modifiableOnlyOption.getOpt())).execute();
83 | }
84 |
85 | @Override
86 | public void execute() throws Exception {
87 |
88 | if(modifiableOnly) {
89 | logger.debug(format("Flag '-%s' has been set. Only modifiable transports will be returned.", Opts.modifiableOnlyOption.getOpt()));
90 | } else {
91 | logger.debug(format("Flag '-%s' has not beem set. All transports will be returned.", Opts.modifiableOnlyOption.getOpt()));
92 | }
93 |
94 | Predicate log =
95 | it -> {
96 | logger.debug(format("Transport '%s' retrieved from host '%s'. isModifiable: '%b', Owner: '%s', Description: '%s'.",
97 | it.getTransportID(),
98 | host,
99 | it.isModifiable(),
100 | it.getOwner(),
101 | it.getDescription()));
102 | return true;};
103 |
104 | Predicate all = it -> true;
105 |
106 | Predicate modOnly = it -> {
107 | if(!it.isModifiable()) {
108 | logger.debug(format("Transport '%s' is modifiable. This transport is added to the result set.", it.getTransportID()));
109 | }
110 | else {
111 | logger.debug(format("Transport '%s' is not modifiable. This transport is not added to the result set.", it.getTransportID()));
112 | };
113 | return it.isModifiable();};
114 |
115 | try (CMODataSolmanClient client = SolmanClientFactory.getInstance().newClient(host, user, password)) {
116 | List transports = client.getChangeTransports(changeId);
117 |
118 | if(transports.isEmpty()) {
119 | logger.debug(format("No transports retrieved for change document id '%s' from host '%s'.", changeId, host));
120 | }
121 |
122 | transports.stream().filter(log)
123 | .filter(modifiableOnly ? modOnly : all)
124 | .forEach(it ->System.out.println(it.getTransportID()));
125 | } catch(Exception e) {
126 | logger.error(format("Exception caught while retrieving transports for change document '%s' from host '%s',", changeId, host), e);
127 | throw e;
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/modules/cli/src/test/java/sap/prd/cmintegration/cli/SolManBackendCreateTransportTest.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import static org.easymock.EasyMock.capture;
4 | import static org.easymock.EasyMock.createMock;
5 | import static org.easymock.EasyMock.expect;
6 | import static org.easymock.EasyMock.expectLastCall;
7 | import static org.easymock.EasyMock.replay;
8 | import static org.hamcrest.Matchers.equalTo;
9 | import static org.hamcrest.Matchers.is;
10 | import static org.junit.Assert.assertThat;
11 |
12 | import org.apache.commons.io.IOUtils;
13 | import org.easymock.Capture;
14 | import org.junit.After;
15 | import org.junit.Before;
16 | import org.junit.Test;
17 |
18 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient;
19 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataTransport;
20 |
21 | public class SolManBackendCreateTransportTest extends CMSolmanTestBase {
22 |
23 | private Capture owner, description,
24 | developmentSystemId;
25 |
26 | @Before
27 | public void setup() throws Exception {
28 | super.setup();
29 | owner = Capture.newInstance();
30 | description = Capture.newInstance();
31 | developmentSystemId = Capture.newInstance();
32 | }
33 |
34 | @After
35 | public void tearDown() throws Exception {
36 | owner = null;
37 | description = null;
38 | super.tearDown();
39 | }
40 |
41 | @Test
42 | public void testStraightForward() throws Exception {
43 |
44 | setMock(setupStraightForwardMock());
45 |
46 | Commands.main(new String[] {
47 | "-u", SERVICE_USER,
48 | "-p", SERVICE_PASSWORD,
49 | "-e", SERVICE_ENDPOINT,
50 | "create-transport",
51 | "-cID", "8000038673",
52 | "-dID", "J01~JAVA"});
53 |
54 | assertThat(changeId.getValue(), is(equalTo("8000038673")));
55 | assertThat(developmentSystemId.getValue(), is(equalTo("J01~JAVA")));
56 | assertThat(owner.hasCaptured(), is(equalTo(false)));
57 | assertThat(description.hasCaptured(), is(equalTo(false)));
58 |
59 | assertThat(IOUtils.toString(result.toByteArray(), "UTF-8").replaceAll("\\r?\\n", ""),
60 | is(equalTo("myTransport")));
61 | }
62 |
63 | @Test
64 | public void testStraightForwardWithOwnerAndDescription() throws Exception {
65 |
66 | setMock(setupStraightForwardMock("me", "lorem ipsum"));
67 |
68 | Commands.main(new String[] {
69 | "-u", SERVICE_USER,
70 | "-p", SERVICE_PASSWORD,
71 | "-e", SERVICE_ENDPOINT,
72 | "create-transport",
73 | "--owner", "me",
74 | "--description", "lorem ipsum",
75 | "-cID", "8000038673",
76 | "-dID", "J01~JAVA"});
77 |
78 | assertThat(changeId.getValue(), is(equalTo("8000038673")));
79 | assertThat(owner.getValue(), is(equalTo("me")));
80 | assertThat(description.getValue(), is(equalTo("lorem ipsum")));
81 |
82 | assertThat(IOUtils.toString(result.toByteArray(), "UTF-8").replaceAll("\\r?\\n", ""),
83 | is(equalTo("myTransport")));
84 | }
85 |
86 | @Test
87 | public void testStraightForwardWithOwnerAndWithoutDescription() throws Exception {
88 |
89 | setMock(setupStraightForwardMock("me", "lorem ipsum"));
90 |
91 | Commands.main(new String[] {
92 | "-u", SERVICE_USER,
93 | "-p", SERVICE_PASSWORD,
94 | "-e", SERVICE_ENDPOINT,
95 | "create-transport",
96 | "--owner", "me",
97 | "-cID", "8000038673",
98 | "-dID", "J01~JAVA"});
99 |
100 | assertThat(changeId.getValue(), is(equalTo("8000038673")));
101 | assertThat(owner.getValue(), is(equalTo("me")));
102 | assertThat(description.getValue(), is(equalTo("")));
103 |
104 | assertThat(IOUtils.toString(result.toByteArray(), "UTF-8").replaceAll("\\r?\\n", ""),
105 | is(equalTo("myTransport")));
106 | }
107 |
108 | @Test
109 | public void testStraightForwardWithoutOwnerAndWithDescription() throws Exception {
110 |
111 | setMock(setupStraightForwardMock("me", "lorem ipsum"));
112 |
113 | Commands.main(new String[] {
114 | "-u", SERVICE_USER,
115 | "-p", SERVICE_PASSWORD,
116 | "-e", SERVICE_ENDPOINT,
117 | "create-transport",
118 | "--description", "lorem ipsum",
119 | "-cID", "8000038673",
120 | "-dID", "J01~JAVA"});
121 |
122 | assertThat(changeId.getValue(), is(equalTo("8000038673")));
123 | assertThat(owner.getValue(), is(equalTo(SERVICE_USER)));
124 | assertThat(description.getValue(), is(equalTo("lorem ipsum")));
125 |
126 | assertThat(IOUtils.toString(result.toByteArray(), "UTF-8").replaceAll("\\r?\\n", ""),
127 | is(equalTo("myTransport")));
128 | }
129 |
130 | private SolmanClientFactory setupStraightForwardMock() throws Exception {
131 | return setupStraightForwardMock(null, null);
132 | }
133 |
134 | private SolmanClientFactory setupStraightForwardMock(String owner, String description) throws Exception {
135 |
136 | CMODataTransport transport = new CMODataTransport("myTransport", "J01~JAVA", true, description, owner);
137 |
138 | CMODataSolmanClient clientMock = createMock(CMODataSolmanClient.class);
139 | if(owner != null && description != null) {
140 | expect(clientMock.createDevelopmentTransportAdvanced(
141 | capture(this.changeId), capture(this.developmentSystemId), capture(this.description), capture(this.owner))).andReturn(transport);
142 | } else {
143 | expect(clientMock.createDevelopmentTransport(capture(this.changeId), capture(this.developmentSystemId))).andReturn(transport);
144 | }
145 |
146 | clientMock.close(); expectLastCall();
147 | SolmanClientFactory factoryMock = createMock(SolmanClientFactory.class);
148 | expect(factoryMock
149 | .newClient(capture(host),
150 | capture(user),
151 | capture(password))).andReturn(clientMock);
152 |
153 | replay(clientMock, factoryMock);
154 | return factoryMock;
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/modules/cli/src/test/java/sap/prd/cmintegration/cli/CommandsTest.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import static java.lang.String.format;
4 | import static org.hamcrest.Matchers.containsString;
5 | import static org.hamcrest.Matchers.equalTo;
6 | import static org.hamcrest.Matchers.is;
7 | import static org.junit.Assert.assertThat;
8 |
9 | import java.io.File;
10 | import java.io.FileInputStream;
11 | import java.util.Properties;
12 |
13 | import org.apache.commons.io.IOUtils;
14 | import org.hamcrest.Matchers;
15 | import org.junit.Assume;
16 | import org.junit.Test;
17 |
18 | public class CommandsTest extends CMTestBase {
19 |
20 | @Test
21 | public void testGetVersionLongOption() throws Exception {
22 | /*
23 | * Here we depend on a maven build. Before executing this test in
24 | * an IDE mvn process-resources needs to be invoked.
25 | */
26 | File version = new File("target/classes/version");
27 | Assume.assumeTrue(version.isFile());
28 |
29 | Commands.main(new String[] {"--version"});
30 |
31 | versionAsserts(version);
32 | }
33 |
34 | @Test
35 | public void testGetVersionShortOption() throws Exception {
36 | /*
37 | * Here we depend on a maven build. Before executing this test in
38 | * an IDE mvn process-resources needs to be invoked.
39 | */
40 | File version = new File("target/classes/version");
41 | Assume.assumeTrue(version.isFile());
42 |
43 | Commands.main(new String[] {"-v"});
44 |
45 | versionAsserts(version);
46 | }
47 |
48 |
49 | @Test
50 | public void testPrintVersionWithSubcommand() throws Exception {
51 | /*
52 | * Here we depend on a maven build. Before executing this test in
53 | * an IDE mvn process-resources needs to be invoked.
54 | */
55 | File version = new File("../lib-common/target/classes/VERSION"); // not so nice, we go outside the submodule folder (../lib)
56 | Assume.assumeTrue(version.isFile());
57 |
58 | Commands.main(new String[] {"--version", "is-transport-modifiable"});
59 |
60 | versionAsserts(version);
61 | }
62 | private void versionAsserts(File versionFile) throws Exception {
63 | Properties vProps = new Properties();
64 | vProps.load(new FileInputStream(versionFile));
65 | String theVersion = format("%s : %s", vProps.getProperty("mvnProjectVersion"), vProps.getProperty("gitCommitId"));
66 | assertThat(removeCRLF(IOUtils.toString(result.toByteArray(), "UTF-8")),
67 | is(equalTo(theVersion)));
68 | }
69 |
70 |
71 | @Test
72 | public void testGetGlobalHelpShortOption() throws Exception {
73 |
74 | Commands.main(new String[] {"-h"});
75 | globalHelpAssert(removeCRLF(IOUtils.toString(result.toByteArray(), "UTF-8")));
76 | }
77 |
78 | @Test
79 | public void testGetGlobalHelpLongOption() throws Exception {
80 |
81 | Commands.main(new String[] {"--help"});
82 | globalHelpAssert(removeCRLF(IOUtils.toString(result.toByteArray(), "UTF-8")));
83 | }
84 |
85 | @Test
86 | public void testPrintHelpWithSubcommandHelpBeforeCommand() throws Exception {
87 | Commands.main(new String[] {"--help", "is-change-in-development"});
88 | String help = IOUtils.toString(result.toByteArray(), "UTF-8");
89 | assertThat(help, Matchers.containsString("usage: [COMMON_OPTIONS] is-change-in-development"));
90 | }
91 |
92 | @Test
93 | public void testPrintHelpWithSubcommandHelpAfterCommand() throws Exception {
94 | Commands.main(new String[] {"is-change-in-development", "--help"});
95 | String help = IOUtils.toString(result.toByteArray(), "UTF-8");
96 | assertThat(help, Matchers.containsString("usage: [COMMON_OPTIONS] is-change-in-development"));
97 | }
98 |
99 | @Test
100 | public void testPrintHelp() throws Exception {
101 | Commands.main(new String[] {"--help"});
102 | String help = IOUtils.toString(result.toByteArray(), "UTF-8");
103 | assertThat(help, Matchers.containsString("Prints this help."));
104 | }
105 |
106 | private void globalHelpAssert(String helpOutput) {
107 | assertThat(helpOutput, containsString("usage: [COMMON_OPTIONS...] [SUBCOMMAND_OPTIONS]")); // too long ..., linebreak.
108 | assertThat(helpOutput, containsString("Subcommands:"));
109 | assertThat(helpOutput, containsString("Type ' --help' for more details."));
110 | }
111 |
112 | @Test
113 | public void testGetCommandHelpLongOption() throws Exception {
114 |
115 | Commands.main(new String[] {"is-change-in-development", "--help"});
116 | commandHelpAssert();
117 | }
118 |
119 | @Test
120 | public void testGetCommandHelpShortOption() throws Exception {
121 |
122 | Commands.main(new String[] {"is-change-in-development", "-h"});
123 | commandHelpAssert();
124 | }
125 |
126 | private void commandHelpAssert() throws Exception {
127 | assertThat(removeCRLF(IOUtils.toString(result.toByteArray(), "UTF-8")), containsString("usage"));
128 | }
129 |
130 | @Test
131 | public void testExecuteNotExistingCommand() throws Exception {
132 | thrown.expect(CMCommandLineException.class);
133 | thrown.expectMessage("Command 'does-not-exist' not found.");
134 | Commands.main(new String[] {"does-not-exist"});
135 | }
136 |
137 | @Test
138 | public void testExecuteWithOptionsBeforeSubcommandName() throws Exception {
139 | thrown.expect(CMCommandLineException.class);
140 | thrown.expectMessage("Command 'does-not-exist' not found.");
141 | Commands.main(new String[] {"-e", "https://www.example.org/mypath",
142 | "-u", "nobody",
143 | "-p", "secret",
144 | "does-not-exist"});
145 | }
146 |
147 | @Test
148 | public void testExecuteWithOptionsAndWithoutSubcommandName() throws Exception {
149 | thrown.expect(CMCommandLineException.class);
150 | thrown.expectMessage("Cannot extract command name from arguments");
151 | Commands.main(new String[] {"-e", "https://www.example.org/mypath",
152 | "-u", "nobody",
153 | "-p", "secret"});
154 | }
155 |
156 | @Test
157 | public void testPrintHelpWithNotExistingSubcommand() throws Exception {
158 | thrown.expect(CMCommandLineException.class);
159 | thrown.expectMessage("Command 'does-not-exist' not found.");
160 | Commands.main(new String[] {"--help", "does-not-exist"});
161 | }
162 |
163 | @Test
164 | public void testExecuteWithoutParameters() throws Exception {
165 | thrown.expect(CMCommandLineException.class);
166 | thrown.expectMessage("Called without arguments.");
167 | Commands.main(new String[] {});
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/modules/lib-solman/src/test/java/sap/ai/st/cm/plugins/ciintegration/odataclient/CMODataClientReleaseTransportTest.java:
--------------------------------------------------------------------------------
1 | package sap.ai.st.cm.plugins.ciintegration.odataclient;
2 |
3 | import static org.easymock.EasyMock.capture;
4 | import static org.easymock.EasyMock.createMock;
5 | import static org.easymock.EasyMock.createMockBuilder;
6 | import static org.easymock.EasyMock.eq;
7 | import static org.easymock.EasyMock.expect;
8 | import static org.easymock.EasyMock.expectLastCall;
9 | import static org.easymock.EasyMock.replay;
10 | import static org.hamcrest.Matchers.equalTo;
11 | import static org.hamcrest.Matchers.is;
12 | import static sap.ai.st.cm.plugins.ciintegration.odataclient.Matchers.carriesStatusCode;
13 | import static sap.ai.st.cm.plugins.ciintegration.odataclient.Matchers.hasServerSideErrorMessage;
14 | import static sap.ai.st.cm.plugins.ciintegration.odataclient.MockHelper.getConfiguration;
15 |
16 | import org.apache.olingo.client.api.ODataClient;
17 | import org.apache.olingo.client.api.communication.ODataClientErrorException;
18 | import org.apache.olingo.client.api.communication.request.invoke.InvokeRequestFactory;
19 | import org.apache.olingo.client.api.communication.request.invoke.ODataInvokeRequest;
20 | import org.apache.olingo.client.api.communication.response.ODataInvokeResponse;
21 | import org.apache.olingo.client.api.domain.ClientEntity;
22 | import org.apache.olingo.client.core.ODataClientImpl;
23 | import org.apache.olingo.commons.api.ex.ODataError;
24 | import org.easymock.Capture;
25 | import org.junit.After;
26 | import org.junit.Assert;
27 | import org.junit.Before;
28 | import org.junit.Test;
29 |
30 | public class CMODataClientReleaseTransportTest extends CMODataClientBaseTest {
31 |
32 | Capture contentType = Capture.newInstance();
33 |
34 | @Before
35 | public void setup() throws Exception {
36 | super.setup();
37 | }
38 |
39 | @After
40 | public void tearDown() throws Exception {
41 | super.tearDown();
42 | }
43 |
44 | @Test
45 | public void testReleaseTransportStraightForward() throws Exception {
46 |
47 | /*
48 | * comment line below for testing against real backend.
49 | *
50 | * Of course against the real back-end it works only once. A transport containing at least
51 | * one item in the object list needs to be prepared in advance. Other test might be used
52 | * for that.
53 | *
54 | * Assert for the captures below needs to be commented also in this case.
55 | */
56 | setMock(examinee, setupMock());
57 |
58 | examinee.releaseDevelopmentTransport("8000042445", "L21K90002K");
59 |
60 | Assert.assertThat(address.getValue().toASCIIString(),
61 | is(equalTo(SERVICE_ENDPOINT + "releaseTransport?ChangeID='8000042445'&TransportID='L21K90002K'")));
62 | }
63 |
64 | @Test
65 | public void testReleaseTransportFailsDueTransportHasAlreadyBeenReleased() throws Exception {
66 |
67 | thrown.expect(ODataClientErrorException.class);
68 | thrown.expect(carriesStatusCode(400)); // TODO: 404 would be better
69 | thrown.expect(hasServerSideErrorMessage("Transport request L21K900026 can no longer be changed."));
70 |
71 | setMock(examinee, setupMock(new ODataClientErrorException(
72 | StatusLines.BAD_REQUEST,
73 | new ODataError().setMessage("Transport request L21K900026 can no longer be changed."))));
74 | examinee.releaseDevelopmentTransport("8000038673", "L21K900026");
75 | }
76 |
77 | @Test
78 | public void testReleaseTransportFailsDueToNotExistingChange() throws Exception {
79 |
80 | thrown.expect(ODataClientErrorException.class);
81 | thrown.expect(carriesStatusCode(400)); // TODO: 404 would be better
82 | thrown.expect(hasServerSideErrorMessage("CHANGE_ID_ not found."));
83 |
84 | // comment statement below for testing against real backend.
85 | setMock(examinee, setupMock(new ODataClientErrorException(
86 | StatusLines.BAD_REQUEST,
87 | new ODataError().setMessage("CHANGE_ID_ not found."))));
88 | examinee.releaseDevelopmentTransport("CHANGE_ID_DOES_NOT_EXIST", "TRANSPORT_REQUEST_DOES_ALSO_NOT_EXIST");
89 | }
90 |
91 | @Test
92 | public void testReleaseTransportFailsDueToNotExistingTransport() throws Exception {
93 |
94 | thrown.expect(ODataClientErrorException.class);
95 | thrown.expect(carriesStatusCode(400)); // TODO: 404 would be better
96 | thrown.expect(hasServerSideErrorMessage("DOES_NOT_EXIST not found."));
97 |
98 | // comment statement below for testing against real backend.
99 | setMock(examinee, setupMock(new ODataClientErrorException(
100 | StatusLines.BAD_REQUEST,
101 | new ODataError().setMessage("DOES_NOT_EXIST not found."))));
102 |
103 | examinee.releaseDevelopmentTransport("8000038673", "DOES_NOT_EXIST");
104 | }
105 |
106 | @Test
107 | public void testReleaseTransportWithoutChangeID() throws Exception {
108 | thrown.expect(IllegalArgumentException.class);
109 | thrown.expectMessage("ChangeID is null or blank:");
110 | examinee.releaseDevelopmentTransport("", "");
111 | }
112 |
113 | @Test
114 | public void testReleaseTransportWithoutTransportID() throws Exception {
115 | thrown.expect(IllegalArgumentException.class);
116 | thrown.expectMessage("TransportID is null or blank:");
117 | examinee.releaseDevelopmentTransport("xx", "");
118 | }
119 |
120 | @Test
121 | public void testReleaseTransportOnClosedClient() throws Exception{
122 | thrown.expect(IllegalStateException.class);
123 | thrown.expectMessage("has been closed");
124 | examinee.close();
125 | examinee.releaseDevelopmentTransport("xx", "xx");
126 | }
127 |
128 | private ODataClient setupMock() {
129 | return setupMock(null);
130 | }
131 |
132 | @SuppressWarnings("unchecked")
133 | private ODataClient setupMock(Exception e) {
134 |
135 | ODataInvokeRequest functionInvokeRequest = createMock(ODataInvokeRequest.class);
136 | expect(functionInvokeRequest.setAccept(capture(contentType))).andReturn(functionInvokeRequest);
137 |
138 | if(e != null) {
139 | expect(functionInvokeRequest.execute())
140 | .andThrow(e);
141 | } else {
142 | ODataInvokeResponse responseMock = createMock(ODataInvokeResponse.class);
143 | expect(responseMock.getStatusCode()).andReturn(200);
144 | responseMock.close(); expectLastCall();
145 | replay(responseMock);
146 | expect(functionInvokeRequest.execute()).andReturn(responseMock);
147 | }
148 |
149 | InvokeRequestFactory invokeRequestFactoryMock = createMock(InvokeRequestFactory.class);
150 | expect(invokeRequestFactoryMock.getFunctionInvokeRequest(capture(address), eq(ClientEntity.class))).andReturn(functionInvokeRequest);
151 |
152 | ODataClient clientMock = createMockBuilder(ODataClientImpl.class)
153 | .addMockedMethod("getInvokeRequestFactory")
154 | .addMockedMethod("getConfiguration").createMock();
155 | expect(clientMock.getInvokeRequestFactory()).andReturn(invokeRequestFactoryMock);
156 | expect(clientMock.getConfiguration()).andReturn(getConfiguration());
157 |
158 | replay(functionInvokeRequest, invokeRequestFactoryMock, clientMock);
159 |
160 | return clientMock;
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/modules/lib-solman/src/test/java/sap/ai/st/cm/plugins/ciintegration/odataclient/CMODataClientChangesTest.java:
--------------------------------------------------------------------------------
1 | package sap.ai.st.cm.plugins.ciintegration.odataclient;
2 |
3 | import static org.easymock.EasyMock.capture;
4 | import static org.easymock.EasyMock.createMock;
5 | import static org.easymock.EasyMock.createMockBuilder;
6 | import static org.easymock.EasyMock.expect;
7 | import static org.easymock.EasyMock.expectLastCall;
8 | import static org.easymock.EasyMock.replay;
9 | import static org.hamcrest.Matchers.equalTo;
10 | import static org.hamcrest.Matchers.is;
11 | import static org.junit.Assert.assertThat;
12 | import static sap.ai.st.cm.plugins.ciintegration.odataclient.Matchers.carriesStatusCode;
13 | import static sap.ai.st.cm.plugins.ciintegration.odataclient.Matchers.hasServerSideErrorMessage;
14 | import static sap.ai.st.cm.plugins.ciintegration.odataclient.MockHelper.getConfiguration;
15 |
16 | import org.apache.olingo.client.api.ODataClient;
17 | import org.apache.olingo.client.api.communication.ODataClientErrorException;
18 | import org.apache.olingo.client.api.communication.request.retrieve.ODataEntityRequest;
19 | import org.apache.olingo.client.api.communication.request.retrieve.RetrieveRequestFactory;
20 | import org.apache.olingo.client.api.communication.response.ODataRetrieveResponse;
21 | import org.apache.olingo.client.api.domain.ClientEntity;
22 | import org.apache.olingo.client.core.ODataClientImpl;
23 | import org.apache.olingo.client.core.domain.ClientEntityImpl;
24 | import org.apache.olingo.client.core.domain.ClientObjectFactoryImpl;
25 | import org.apache.olingo.client.core.domain.ClientPropertyImpl;
26 | import org.apache.olingo.commons.api.edm.FullQualifiedName;
27 | import org.apache.olingo.commons.api.ex.ODataError;
28 | import org.easymock.Capture;
29 | import org.junit.After;
30 | import org.junit.Before;
31 | import org.junit.Test;
32 |
33 | public class CMODataClientChangesTest extends CMODataClientBaseTest {
34 |
35 | private Capture contentType;
36 |
37 | @Before
38 | public void setup() throws Exception {
39 | super.setup();
40 | contentType = Capture.newInstance();
41 | }
42 |
43 | @After
44 | public void tearDown() throws Exception {
45 | contentType = null;
46 | super.tearDown();
47 | }
48 |
49 | @Test
50 | public void testChangeStraightForward() throws Exception {
51 |
52 | // comment line below for testing against real backend.
53 | // Assert for the captures below needs to be commented also in this case.
54 | setMock(examinee, setupStraightForwardMock());
55 |
56 | CMODataChange change = examinee.getChange("8000038673");
57 |
58 | assertThat(change.isInDevelopment(), is(equalTo(true)));
59 | assertThat(change.getChangeID(), is(equalTo("8000038673")));
60 | assertThat(contentType.getValue(), is(equalTo("application/atom+xml")));
61 | assertThat(address.getValue().toASCIIString(),
62 | is(equalTo(
63 | SERVICE_ENDPOINT + "Changes('8000038673')")));
64 | }
65 |
66 | @Test
67 | public void testChangeDoesNotExist() throws Exception {
68 |
69 | thrown.expect(ODataClientErrorException.class);
70 | thrown.expect(carriesStatusCode(404));
71 | thrown.expect(hasServerSideErrorMessage("Resource not found for segment ''."));
72 |
73 | // comment line below for testing against real backend.
74 | // Assert for the captures below needs to be commented also in this case.
75 | setMock(examinee, setupChangeDoesNotExistMock());
76 |
77 | try {
78 | examinee.getChange("001");
79 | } catch(Exception e) {
80 | assertThat(address.getValue().toASCIIString(),
81 | is(equalTo(
82 | SERVICE_ENDPOINT + "Changes('001')")));
83 | throw e;
84 | }
85 | }
86 |
87 | @Test
88 | public void testChangeBadCredentials() throws Exception {
89 |
90 | thrown.expect(ODataClientErrorException.class);
91 | thrown.expect(carriesStatusCode(401));
92 | // we cannot check for the server side error message in this case since
93 | // we get a generic html page in this case explaining to a human user that
94 | // there was a problem with the credentials.
95 |
96 | CMODataSolmanClient examinee = new CMODataSolmanClient(
97 | SERVICE_ENDPOINT,
98 | "NOBODY",
99 | SERVICE_PASSWORD);
100 |
101 | // comment line below for testing against real backend.
102 | setMock(examinee, setupBadCredentialsMock());
103 |
104 | examinee.getChange("8000038673");
105 | }
106 |
107 | @Test
108 | public void testGetChangeOnClosedClient() throws Exception{
109 | thrown.expect(IllegalStateException.class);
110 | thrown.expectMessage("has been closed");
111 | examinee.close();
112 | examinee.getChange("xx");
113 | }
114 |
115 | private ODataClient setupStraightForwardMock() {
116 | return setupMock(null);
117 | }
118 |
119 | private ODataClient setupBadCredentialsMock() {
120 | return setupMock(
121 | new ODataClientErrorException(
122 | StatusLines.UNAUTHORIZED));
123 | }
124 |
125 | private ODataClient setupChangeDoesNotExistMock() {
126 | return setupMock(
127 | new ODataClientErrorException(
128 | StatusLines.NOT_FOUND,
129 | new ODataError().setMessage("Resource not found for segment ''.")));
130 | }
131 |
132 | @SuppressWarnings("unchecked")
133 | private ODataClient setupMock(ODataClientErrorException e) {
134 |
135 | class MockHelpers {
136 |
137 | private ClientEntity setupClientEntityMock() {
138 |
139 | ClientEntity clientEntity = new ClientEntityImpl(new FullQualifiedName("AI_CRM_GW_CM_CI_SRV.Change"));
140 |
141 | clientEntity.getProperties().add(
142 | new ClientPropertyImpl("ChangeID",
143 | new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("8000038673").build()));
144 |
145 | clientEntity.getProperties().add(
146 | new ClientPropertyImpl("IsInDevelopment",
147 | new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("true").build()));
148 |
149 | return clientEntity;
150 | }
151 |
152 | ODataRetrieveResponse setupResponseMock() {
153 | ODataRetrieveResponse responseMock = createMock(ODataRetrieveResponse.class);
154 | expect(responseMock.getBody()).andReturn(setupClientEntityMock());
155 | responseMock.close();
156 | expectLastCall().once();
157 | replay(responseMock);
158 | return responseMock;
159 | }
160 | }
161 |
162 | ODataEntityRequest oDataEntityRequestMock = createMock(ODataEntityRequest.class);
163 | expect(oDataEntityRequestMock.setAccept(capture(contentType))).andReturn(oDataEntityRequestMock);
164 |
165 | if(e != null) {
166 | expect(oDataEntityRequestMock.execute()).andThrow(e);
167 | } else {
168 | expect(oDataEntityRequestMock.execute()).andReturn(new MockHelpers().setupResponseMock());
169 | }
170 |
171 | RetrieveRequestFactory retrieveRequestFactoryMock = createMock(RetrieveRequestFactory.class);
172 | expect(retrieveRequestFactoryMock.getEntityRequest(capture(address))).andReturn(oDataEntityRequestMock);
173 |
174 | ODataClient clientMock = createMockBuilder(ODataClientImpl.class)
175 | .addMockedMethod("getConfiguration")
176 | .addMockedMethod("getRetrieveRequestFactory").createMock();
177 |
178 | expect(clientMock.getConfiguration()).andReturn(getConfiguration());
179 | expect(clientMock.getRetrieveRequestFactory()).andReturn(retrieveRequestFactoryMock);
180 |
181 | replay(oDataEntityRequestMock, retrieveRequestFactoryMock, clientMock);
182 |
183 | return clientMock;
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/modules/cli/src/main/java/sap/prd/cmintegration/cli/TransportRelatedSOLMAN.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import static com.google.common.base.Preconditions.checkArgument;
4 | import static java.lang.String.format;
5 | import static org.apache.commons.lang3.StringUtils.isBlank;
6 | import static sap.prd.cmintegration.cli.Commands.CMOptions.newOption;
7 | import static sap.prd.cmintegration.cli.Commands.Helpers.getChangeId;
8 | import static sap.prd.cmintegration.cli.Commands.Helpers.getHost;
9 | import static sap.prd.cmintegration.cli.Commands.Helpers.getPassword;
10 | import static sap.prd.cmintegration.cli.Commands.Helpers.getUser;
11 | import static sap.prd.cmintegration.cli.Commands.Helpers.handleHelpOption;
12 | import static sap.prd.cmintegration.cli.Commands.Helpers.helpRequested;
13 |
14 | import java.lang.reflect.InvocationTargetException;
15 | import java.util.Optional;
16 | import java.util.function.Function;
17 |
18 | import org.apache.commons.cli.CommandLine;
19 | import org.apache.commons.cli.DefaultParser;
20 | import org.apache.commons.cli.Option;
21 | import org.apache.commons.cli.Options;
22 | import org.apache.commons.lang3.StringUtils;
23 | import org.slf4j.Logger;
24 | import org.slf4j.LoggerFactory;
25 |
26 | import com.sap.cmclient.Transport;
27 |
28 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient;
29 |
30 | abstract class TransportRelatedSOLMAN extends Command {
31 |
32 | private final boolean returnCodeMode;
33 | protected final String transportId;
34 | protected final String changeId;
35 |
36 | protected final static Logger logger = LoggerFactory.getLogger(TransportRelatedSOLMAN.class);
37 |
38 | static class Opts {
39 | protected final static Option TRANSPORT_ID = newOption("tID", "transport-id", "transportID.", "tId", true);
40 |
41 | static Options addOptions(Options opts, boolean includeStandardOpts) {
42 | if (includeStandardOpts) {
43 | Command.addOpts(opts);
44 | }
45 | opts.addOption(TRANSPORT_ID);
46 |
47 | opts.addOption(Commands.CMOptions.CHANGE_ID);
48 | return opts;
49 | }
50 | }
51 |
52 | protected TransportRelatedSOLMAN(String host, String user, String password, String changeId, String transportId,
53 | boolean returnCodeMode) {
54 |
55 | super(host, user, password);
56 | checkArgument(!isBlank(transportId), "No transportId provided.");
57 | this.transportId = transportId;
58 | this.returnCodeMode = returnCodeMode;
59 |
60 | checkArgument(!isBlank(changeId), "No changeId provided.");
61 | this.changeId = changeId;
62 | }
63 |
64 | private static class FollowUp {
65 | private final static Function printToStdout = new Function() {
66 |
67 | @Override
68 | public Void apply(String output) {
69 | if (output != null)
70 | System.out.println(output);
71 | return null;
72 | }
73 | }, raiseFriendlyExitException = new Function() {
74 |
75 | // yes, this is some kind of miss-use of exceptions.
76 |
77 | @Override
78 | public Void apply(String output) {
79 | if (output != null && !Boolean.valueOf(output))
80 | throw new ExitException(ExitException.ExitCodes.FALSE);
81 | return null;
82 | }
83 | };
84 | }
85 |
86 | protected final static Function getDescription = new Function() {
87 |
88 | @Override
89 | public String apply(Transport t) {
90 | String description = t.getDescription();
91 | if (StringUtils.isBlank(description)) {
92 | logger.debug(
93 | format("Description of transport '%s' is blank. Nothing will be emitted.", t.getTransportID()));
94 | return null;
95 | } else {
96 | logger.debug(format("Description of transport '%s' is not blank. Description '%s' will be emitted.",
97 | t.getTransportID(), t.getDescription()));
98 | return description;
99 | }
100 | };
101 | };
102 |
103 | protected final static Function isModifiable = new Function() {
104 |
105 | @Override
106 | public String apply(Transport t) {
107 | return String.valueOf(t.isModifiable());
108 | }
109 | };
110 |
111 | protected abstract Function getAction();
112 |
113 | protected final static Function getOwner = new Function() {
114 |
115 | @Override
116 | public String apply(Transport t) {
117 |
118 | String owner = t.getOwner();
119 | if (StringUtils.isBlank(owner)) {
120 | logger.debug(String.format("Owner attribute for transport '%s' is blank. Nothing will be emitted.",
121 | t.getTransportID()));
122 | return null;
123 | } else {
124 | logger.debug(String.format("Owner '%s' has been emitted for transport '%s'.", t.getOwner(),
125 | t.getTransportID()));
126 | return owner;
127 | }
128 | };
129 | };
130 |
131 | static String getTransportId(CommandLine commandLine) {
132 | String transportID = commandLine.getOptionValue(Opts.TRANSPORT_ID.getOpt());
133 | if (StringUtils.isEmpty(transportID)) {
134 | throw new CMCommandLineException("No transportId specified.");
135 | }
136 | return transportID;
137 | }
138 |
139 | protected static boolean isReturnCodeMode(CommandLine commandLine) {
140 | return commandLine.hasOption(Commands.CMOptions.RETURN_CODE.getOpt());
141 | }
142 |
143 | protected static void main(Class extends TransportRelatedSOLMAN> clazz, Options options, String[] args,
144 | String subCommandName, String argumentDocu, String helpText) throws Exception {
145 |
146 | logger.debug(
147 | format("%s called with arguments: %s", clazz.getSimpleName(), Commands.Helpers.getArgsLogString(args)));
148 |
149 | if (helpRequested(args)) {
150 | handleHelpOption(subCommandName, argumentDocu, helpText,
151 | TransportRelatedSOLMAN.Opts.addOptions(new Options(), false));
152 | return;
153 | }
154 |
155 | TransportRelatedSOLMAN.Opts.addOptions(options, true);
156 | CommandLine commandLine = new DefaultParser().parse(options, args);
157 |
158 | newInstance(clazz, getHost(commandLine), getUser(commandLine), getPassword(commandLine),
159 | getChangeId(commandLine), getTransportId(commandLine), isReturnCodeMode(commandLine)).execute();
160 | }
161 |
162 | protected void execute() throws Exception {
163 | try {
164 | Optional transport = getTransport();
165 | if (!transport.isPresent()) {
166 | throw new TransportNotFoundException(transportId, format("Transport '%s' not found.", transportId));
167 | }
168 |
169 | Transport t = transport.get();
170 |
171 | if (!t.getTransportID().trim().equals(transportId.trim())) {
172 | throw new CMCommandLineException(
173 | format("TransportId of resolved transport ('%s') does not match requested transport id ('%s').",
174 | t.getTransportID(), transportId));
175 | }
176 |
177 | logger.debug(format("Transport '%s' has been found. isModifiable: '%b', Owner: '%s', Description: '%s'.",
178 | transportId, t.isModifiable(), t.getOwner(), t.getDescription()));
179 |
180 | getAction().andThen(returnCodeMode ? FollowUp.raiseFriendlyExitException : FollowUp.printToStdout).apply(t);
181 | } catch (TransportNotFoundException e) {
182 | throw new CMCommandLineException(
183 | format("Transport '%s' not found for change '%s'.", e.getTransportId(), changeId), e);
184 | }
185 | }
186 |
187 | private static TransportRelatedSOLMAN newInstance(Class extends TransportRelatedSOLMAN> clazz, String host,
188 | String user, String password, String changeId, String transportId, boolean returnCodeMode) {
189 | try {
190 | return clazz.getDeclaredConstructor(
191 | new Class[] { String.class, String.class, String.class, String.class, String.class, Boolean.TYPE })
192 | .newInstance(new Object[] { host, user, password, changeId, transportId, returnCodeMode });
193 | } catch (NoSuchMethodException | IllegalAccessException | InstantiationException
194 | | InvocationTargetException e) {
195 | throw new RuntimeException(format("Cannot instanciate class '%s'.", clazz.getName()), e);
196 | }
197 | }
198 |
199 | protected Optional getTransport() {
200 | try (CMODataSolmanClient client = SolmanClientFactory.getInstance().newClient(host, user, password)) {
201 | return client.getChangeTransports(changeId).stream().filter(it -> it.getTransportID().equals(transportId))
202 | .findFirst();
203 | }
204 | }
205 | }
206 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | com.sap.devops.cmclient
5 | module
6 | 0.0.2-SNAPSHOT
7 | pom
8 | Change Management Client
9 | Command line client that can interact with SAP Solution Manager 7.2 to run various change management tasks.
10 |
11 |
12 | UTF-8
13 | 1.7.25
14 | 2.0.10
15 | 4.7.1
16 |
17 |
18 |
19 |
20 |
21 | true
22 | ossrh
23 | Maven Central Release
24 | https://oss.sonatype.org/service/local/staging/deploy/maven2
25 | default
26 |
27 |
28 | true
29 | ossrh-snapshots
30 | Maven Central Snapshot
31 | https://oss.sonatype.org/content/repositories/snapshots
32 | default
33 |
34 |
35 |
36 |
37 | scm:git:git://github.com/SAP/devops-cm-client.git
38 | scm:git:ssh://github.com:SAP/devops-cm-client.git
39 | http://github.com/SAP/devops-cm-client/tree/master
40 |
41 |
42 |
43 |
44 | Thomas Hoffmann
45 | tho.hoffmann@sap.com
46 | SAP
47 | http://www.sap.com
48 |
49 |
50 | Marcus Holl
51 | marcus.holl@sap.com
52 | SAP
53 | http://www.sap.com
54 |
55 |
56 |
57 | http://github.com/SAP/devops-cm-client/
58 |
59 |
60 |
61 | The Apache License, Version 2.0
62 | http://www.apache.org/licenses/LICENSE-2.0.txt
63 |
64 |
65 |
66 |
67 | modules/lib-solman
68 | modules/lib-common
69 | modules/cli
70 | modules/dist.cli
71 | modules/testutils
72 |
73 |
74 |
75 |
76 | org.apache.olingo
77 | odata-client-core
78 | ${olingo.v4.version}
79 |
80 |
81 | org.apache.olingo
82 | olingo-odata2-api
83 | ${olingo.v2.version}
84 |
85 |
86 | org.apache.olingo
87 | olingo-odata2-core
88 | ${olingo.v2.version}
89 |
90 |
91 | org.apache.httpcomponents
92 | httpclient
93 | 4.5.13
94 |
95 |
96 | com.google.guava
97 | guava
98 | 29.0-jre
99 |
100 |
101 | org.slf4j
102 | slf4j-api
103 | ${slf4j.version}
104 |
105 |
106 | org.slf4j
107 | slf4j-nop
108 | ${slf4j.version}
109 |
110 |
111 | org.slf4j
112 | slf4j-simple
113 | ${slf4j.version}
114 | test
115 |
116 |
117 | junit
118 | junit
119 | 4.13.1
120 | test
121 |
122 |
123 | org.easymock
124 | easymock
125 | 3.4
126 | test
127 |
128 |
129 | org.hamcrest
130 | hamcrest-library
131 | 1.3
132 | test
133 |
134 |
135 | org.hamcrest
136 | hamcrest-core
137 | 1.3
138 | test
139 |
140 |
141 | com.github.tomakehurst
142 | wiremock
143 | 2.15.0
144 | test
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 | maven-compiler-plugin
154 | 3.6.2
155 |
156 |
157 | org.apache.maven.plugins
158 | maven-source-plugin
159 | 3.0.1
160 |
161 |
162 | attach-sources
163 |
164 | jar
165 |
166 |
167 |
168 |
169 |
170 | org.apache.maven.plugins
171 | maven-javadoc-plugin
172 | 3.0.0
173 |
174 |
175 | generate-javadoc
176 |
177 | jar
178 |
179 |
180 | all
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 | org.sonatype.plugins
190 | nexus-staging-maven-plugin
191 | 1.6.7
192 | true
193 |
194 | https://oss.sonatype.org
195 | ossrh
196 |
197 |
198 |
199 |
200 |
201 |
202 | noop
203 |
204 |
205 |
206 | signing
207 |
208 |
209 |
210 | org.apache.maven.plugins
211 | maven-gpg-plugin
212 | 1.6
213 |
214 |
215 | sign-artifacts
216 | verify
217 |
218 | sign
219 |
220 |
221 |
222 | --pinentry-mode
223 | loopback
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
--------------------------------------------------------------------------------
/modules/lib-solman/src/test/java/sap/ai/st/cm/plugins/ciintegration/odataclient/CMODataClientGetTransportsTest.java:
--------------------------------------------------------------------------------
1 | package sap.ai.st.cm.plugins.ciintegration.odataclient;
2 |
3 | import static org.apache.commons.lang3.StringUtils.join;
4 | import static org.easymock.EasyMock.capture;
5 | import static org.easymock.EasyMock.createMock;
6 | import static org.easymock.EasyMock.createMockBuilder;
7 | import static org.easymock.EasyMock.expect;
8 | import static org.easymock.EasyMock.expectLastCall;
9 | import static org.easymock.EasyMock.replay;
10 | import static org.hamcrest.Matchers.allOf;
11 | import static org.hamcrest.Matchers.containsString;
12 | import static org.hamcrest.Matchers.equalTo;
13 | import static org.hamcrest.Matchers.is;
14 | import static org.junit.Assert.assertThat;
15 | import static sap.ai.st.cm.plugins.ciintegration.odataclient.Matchers.carriesStatusCode;
16 | import static sap.ai.st.cm.plugins.ciintegration.odataclient.Matchers.hasServerSideErrorMessage;
17 | import static sap.ai.st.cm.plugins.ciintegration.odataclient.MockHelper.getConfiguration;
18 |
19 | import java.util.Collection;
20 | import java.util.HashMap;
21 | import java.util.HashSet;
22 | import java.util.List;
23 | import java.util.Map;
24 | import java.util.Set;
25 |
26 | import org.apache.olingo.client.api.ODataClient;
27 | import org.apache.olingo.client.api.communication.ODataClientErrorException;
28 | import org.apache.olingo.client.api.communication.request.retrieve.ODataEntitySetIteratorRequest;
29 | import org.apache.olingo.client.api.communication.request.retrieve.RetrieveRequestFactory;
30 | import org.apache.olingo.client.api.communication.response.ODataRetrieveResponse;
31 | import org.apache.olingo.client.api.domain.ClientEntity;
32 | import org.apache.olingo.client.api.domain.ClientEntitySet;
33 | import org.apache.olingo.client.api.domain.ClientEntitySetIterator;
34 | import org.apache.olingo.client.api.domain.ClientProperty;
35 | import org.apache.olingo.client.core.ODataClientImpl;
36 | import org.apache.olingo.client.core.domain.ClientObjectFactoryImpl;
37 | import org.apache.olingo.client.core.domain.ClientPropertyImpl;
38 | import org.apache.olingo.commons.api.ex.ODataError;
39 | import org.easymock.Capture;
40 | import org.junit.After;
41 | import org.junit.Before;
42 | import org.junit.Test;
43 |
44 | import com.google.common.collect.Sets;
45 | import com.sap.cmclient.Transport;
46 |
47 | public class CMODataClientGetTransportsTest extends CMODataClientBaseTest {
48 |
49 |
50 | private static class TransportDescriptor {
51 | String transportId, developmentSystemId;
52 | boolean modifiable;
53 | static TransportDescriptor create(String transportId, String developmentSystemId, boolean modifiable) {
54 | TransportDescriptor t = new TransportDescriptor();
55 | t.transportId = transportId;
56 | t.developmentSystemId = developmentSystemId;
57 | t.modifiable = modifiable;
58 | return t;
59 | }
60 | }
61 | private Capture contentType = Capture.newInstance();
62 | @Before
63 | public void setup() throws Exception {
64 | super.setup();
65 | }
66 |
67 | @After
68 | public void tearDown() throws Exception{
69 | super.tearDown();
70 | }
71 |
72 | @Test
73 | public void testGetTransportsStraightForward() throws Exception {
74 |
75 | setMock(examinee, setupMock());
76 | List changeTransports = examinee.getChangeTransports("8000042445");
77 |
78 | assertThat(join(getTransportIds(changeTransports), " "), allOf(
79 | containsString("L21K90002J"),
80 | containsString("L21K90002L"),
81 | containsString("L21K90002N")));
82 |
83 | assertThat(changeTransports.get(0).getDescription(), is(equalTo("S 8000038673: HCP CI Jenkins Deploy UC 1")));
84 | assertThat(changeTransports.get(0).getOwner(), is(equalTo(SERVICE_USER)));
85 |
86 | assertThat(address.getValue().toASCIIString(),
87 | is(equalTo(SERVICE_ENDPOINT + "Changes('8000042445')/Transports")));
88 | }
89 |
90 | /*
91 | * No java 8 streams for the lib since the lib is also used from the jenkins plugin which
92 | * has (for some reasons (?)) a constrain to java 7.
93 | */
94 | private static Collection getTransportIds(Collection transports) {
95 | Collection transportIds = Sets.newHashSet();
96 | for(Transport t : transports) {
97 | transportIds.add(t.getTransportID());
98 | }
99 | return transportIds;
100 | }
101 |
102 | @Test
103 | public void testGetTransportsChangeIdDoesNotExist() throws Exception {
104 |
105 | thrown.expect(ODataClientErrorException.class);
106 | thrown.expect(carriesStatusCode(400)); // TODO 404 would be better
107 | thrown.expect(hasServerSideErrorMessage("Resource not found for segment ''"));
108 |
109 | // comment statement below for testing against real backend.
110 | // Assert for the captures below needs to be commented also in this case.
111 | setMock(examinee, setupMock(
112 | new ODataClientErrorException(
113 | StatusLines.BAD_REQUEST,
114 | new ODataError().setMessage("Resource not found for segment ''"))));
115 |
116 | try {
117 | examinee.getChangeTransports("DOES_NOT_EXIST");
118 | } catch(Exception e) {
119 | assertThat(address.getValue().toASCIIString(),
120 | is(equalTo(SERVICE_ENDPOINT + "Changes('DOES_NOT_EXIST')/Transports")));
121 | throw e;
122 | }
123 | }
124 |
125 | @Test
126 | public void testGetTransportsChangeIdNotProvided() throws Exception {
127 |
128 | thrown.expect(IllegalArgumentException.class);
129 | thrown.expectMessage("changeID was null or empty");
130 |
131 | setMock(examinee, setupMock());
132 |
133 | examinee.getChangeTransports(null);
134 | }
135 |
136 | @Test
137 | public void testGetChangeTransportsOnClosedClient() throws Exception{
138 | thrown.expect(IllegalStateException.class);
139 | thrown.expectMessage("has been closed");
140 | examinee.close();
141 | examinee.getChangeTransports("xx");
142 | }
143 |
144 | @Test
145 | public void testGetChangeTransportWithEmptyDevelopmentSystemId() throws Exception{
146 | Set transports = new HashSet();
147 | transports.add(TransportDescriptor.create("L21K90002N", "", false));
148 | setMock(examinee, setupMock(null, transports));
149 | List t = examinee.getChangeTransports("L21K90002N");
150 | assertThat(((CMODataTransport)t.get(0)).getDevelopmentSystemID(), is(equalTo("")));
151 | }
152 |
153 | private ODataClient setupMock() throws Exception {
154 | return setupMock(null);
155 | }
156 |
157 | private ODataClient setupMock(Exception e) {
158 | Set transports = new HashSet();
159 | transports.add(TransportDescriptor.create("L21K90002N", "xxx~123", false));
160 | transports.add(TransportDescriptor.create("L21K90002L", "xxx~123", false));
161 | transports.add(TransportDescriptor.create("L21K90002J", "xxx~123", true));
162 | return setupMock(e, transports);
163 | }
164 |
165 | @SuppressWarnings("unchecked")
166 | private ODataClient setupMock(Exception e, Set transports) {
167 |
168 | ODataRetrieveResponse> responseMock = createMock(ODataRetrieveResponse.class);
169 |
170 | if(e != null) {
171 | expect(responseMock.getBody()).andThrow(e);
172 | } else {
173 |
174 | ClientEntitySetIterator iteratorMock = createMock(ClientEntitySetIterator.class);
175 |
176 | for(TransportDescriptor t : transports) {
177 | expect(iteratorMock.hasNext()).andReturn(true);
178 | expect(iteratorMock.next()).andReturn(setupTransportMock(t.transportId, t.developmentSystemId, t.modifiable));
179 | }
180 | expect(iteratorMock.hasNext()).andReturn(false);
181 |
182 | expect(responseMock.getBody()).andReturn(iteratorMock);
183 | replay(iteratorMock);
184 | }
185 |
186 | responseMock.close();
187 | expectLastCall();
188 |
189 | ODataEntitySetIteratorRequest oDataEntityRequestMock = createMock(ODataEntitySetIteratorRequest.class);
190 | expect(oDataEntityRequestMock.setAccept(capture(contentType))).andReturn(oDataEntityRequestMock);
191 | expect(oDataEntityRequestMock.execute()).andReturn(responseMock);
192 |
193 | RetrieveRequestFactory retrieveRequestFactoryMock = createMock(RetrieveRequestFactory.class);
194 | expect(retrieveRequestFactoryMock.getEntitySetIteratorRequest(capture(address))).andReturn(oDataEntityRequestMock);
195 |
196 | ODataClient clientMock = createMockBuilder(ODataClientImpl.class)
197 | .addMockedMethod("getRetrieveRequestFactory")
198 | .addMockedMethod("getConfiguration").createMock();
199 | expect(clientMock.getRetrieveRequestFactory()).andReturn(retrieveRequestFactoryMock);
200 | expect(clientMock.getConfiguration()).andReturn(getConfiguration());
201 | replay(responseMock, oDataEntityRequestMock, retrieveRequestFactoryMock, clientMock);
202 | return clientMock;
203 | }
204 |
205 | private static ClientEntity setupTransportMock(String transportId, String developmentSystemId, boolean isModifiable) {
206 |
207 | ClientEntity transportMock = createMock(ClientEntity.class);
208 |
209 | Map props = new HashMap();
210 |
211 | props.put("TransportID", transportId);
212 | props.put("DevelopmentSystemID", developmentSystemId);
213 | props.put("IsModifiable", Boolean.valueOf(isModifiable).toString());
214 | props.put("Description", "S 8000038673: HCP CI Jenkins Deploy UC 1");
215 | props.put("Owner", SERVICE_USER);
216 |
217 | for(Map.Entry e : props.entrySet()) {
218 | ClientProperty cp = new ClientPropertyImpl(e.getKey(),
219 | new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue(e.getValue()).build());
220 | expect(transportMock.getProperty(e.getKey())).andReturn(cp);
221 | }
222 | replay(transportMock);
223 | return transportMock;
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/modules/lib-solman/src/test/java/sap/ai/st/cm/plugins/ciintegration/odataclient/CMODataClientFileUploadTest.java:
--------------------------------------------------------------------------------
1 | package sap.ai.st.cm.plugins.ciintegration.odataclient;
2 |
3 | import static com.sap.cmclient.Matchers.hasRootCause;
4 | import static org.easymock.EasyMock.anyObject;
5 | import static org.easymock.EasyMock.capture;
6 | import static org.easymock.EasyMock.createMock;
7 | import static org.easymock.EasyMock.createMockBuilder;
8 | import static org.easymock.EasyMock.expect;
9 | import static org.easymock.EasyMock.expectLastCall;
10 | import static org.easymock.EasyMock.replay;
11 | import static org.hamcrest.Matchers.equalTo;
12 | import static org.hamcrest.Matchers.is;
13 | import static org.junit.Assert.assertThat;
14 | import static sap.ai.st.cm.plugins.ciintegration.odataclient.Matchers.carriesStatusCode;
15 | import static sap.ai.st.cm.plugins.ciintegration.odataclient.Matchers.hasServerSideErrorMessage;
16 |
17 | import java.io.File;
18 | import java.io.IOException;
19 | import java.io.InputStream;
20 | import java.net.URI;
21 | import java.util.Arrays;
22 | import java.util.UUID;
23 |
24 | import org.apache.commons.io.FileUtils;
25 | import org.apache.olingo.client.api.ODataClient;
26 | import org.apache.olingo.client.api.communication.ODataClientErrorException;
27 | import org.apache.olingo.client.api.communication.request.ODataPayloadManager;
28 | import org.apache.olingo.client.api.communication.request.cud.CUDRequestFactory;
29 | import org.apache.olingo.client.api.communication.request.retrieve.ODataEntitySetIteratorRequest;
30 | import org.apache.olingo.client.api.communication.request.retrieve.RetrieveRequestFactory;
31 | import org.apache.olingo.client.api.communication.request.streamed.ODataMediaEntityUpdateRequest;
32 | import org.apache.olingo.client.api.communication.response.ODataResponse;
33 | import org.apache.olingo.client.api.communication.response.ODataRetrieveResponse;
34 | import org.apache.olingo.client.api.domain.ClientEntity;
35 | import org.apache.olingo.client.api.domain.ClientEntitySet;
36 | import org.apache.olingo.client.api.domain.ClientEntitySetIterator;
37 | import org.apache.olingo.client.api.http.HttpClientException;
38 | import org.apache.olingo.client.core.ODataClientImpl;
39 | import org.apache.olingo.commons.api.ex.ODataError;
40 | import org.apache.olingo.commons.api.format.ContentType;
41 | import org.easymock.EasyMock;
42 | import org.junit.After;
43 | import org.junit.Before;
44 | import org.junit.Rule;
45 | import org.junit.Test;
46 | import org.junit.rules.TemporaryFolder;
47 |
48 | public class CMODataClientFileUploadTest extends CMODataClientBaseTest {
49 |
50 | private File testFile;
51 |
52 | @Rule
53 | public TemporaryFolder tmp = new TemporaryFolder();
54 |
55 | @Before
56 | public void setup() throws Exception {
57 | super.setup();
58 | prepareTestFile();
59 | }
60 |
61 | @After
62 | public void tearDown() throws Exception {
63 | testFile = null;
64 | super.tearDown();
65 | }
66 |
67 | private void prepareTestFile() throws IOException {
68 | testFile = tmp.newFile(UUID.randomUUID().toString() + ".txt");
69 | FileUtils.write(testFile, "{\"description\": \"Created by unit test.\"}");
70 | }
71 |
72 | @Test
73 | public void testUploadFileStraightForward() throws Exception {
74 |
75 | // comment line below for testing against real backend.
76 | // Assert for the captures below needs to be commented also in this case.
77 | setMock(examinee, setupUploadFileSucceedsMock());
78 |
79 | examinee.uploadFileToTransport("8000042445", "L21K900035", testFile.getAbsolutePath(), "HCP");
80 |
81 | assertThat(address.getValue().toASCIIString(),
82 | is(equalTo(SERVICE_ENDPOINT + "Files(ChangeID='8000042445',TransportID='L21K900035',FileID='" +
83 | testFile.getName() + "',ApplicationID='HCP')")));
84 | }
85 |
86 | @Test
87 | public void testUploadFileToClosedTransportFails() throws Exception {
88 |
89 | thrown.expect(HttpClientException.class);
90 | thrown.expect(hasRootCause(ODataClientErrorException.class));
91 | thrown.expect(carriesStatusCode(400));
92 | thrown.expect(hasServerSideErrorMessage(
93 | "Internal Error - assertion skipped (see long text). "
94 | + "Diagnosis An invalid system status was reached "
95 | + "in the Change and Transport Organizer. "
96 | + "System Response The internal check using an assertion "
97 | + "was ignored due to the setti."));
98 |
99 | // comment statement below for testing against real backend.
100 | setMock(examinee, setupUploadFileFailsMock(new HttpClientException(
101 | new RuntimeException(new ODataClientErrorException(
102 | StatusLines.BAD_REQUEST,
103 | new ODataError().setMessage(
104 | "Internal Error - assertion skipped (see long text). "
105 | + "Diagnosis An invalid system status was reached "
106 | + "in the Change and Transport Organizer. "
107 | + "System Response The internal check using an assertion "
108 | + "was ignored due to the setti."))))));
109 |
110 | //transport 'L21K900026' exists, but is closed.
111 | examinee.uploadFileToTransport("8000038673", "L21K900026", testFile.getAbsolutePath(), "HCP");
112 | }
113 |
114 | @Test
115 | public void testUploadFileToNonExistingTransportFails() throws Exception {
116 |
117 | thrown.expect(HttpClientException.class);
118 | thrown.expect(hasRootCause(ODataClientErrorException.class));
119 | thrown.expect(carriesStatusCode(400));
120 | thrown.expect(hasServerSideErrorMessage("Resource not found for segment 'Transport'."));
121 |
122 | // comment statement below for testing against real backend.
123 | setMock(examinee, setupUploadFileFailsMock(new HttpClientException(
124 | new RuntimeException(new ODataClientErrorException(
125 | StatusLines.BAD_REQUEST,
126 | new ODataError().setMessage("Resource not found for segment 'Transport'."))))));
127 |
128 | //transport 'L21K900XFG' does not exist
129 | examinee.uploadFileToTransport("8000042445", "L21K900XFG", testFile.getAbsolutePath(), "HCP");
130 | }
131 |
132 | @Test
133 | public void testUploadFileCalledOnClosedClient() throws Exception{
134 | thrown.expect(IllegalStateException.class);
135 | thrown.expectMessage("has been closed");
136 | examinee.close();
137 | examinee.uploadFileToTransport("xx", "xx", "xx", "xx");
138 | }
139 |
140 | private ODataClient setupUploadFileSucceedsMock() {
141 | return setupMock(null);
142 | }
143 |
144 | private ODataClient setupUploadFileFailsMock(Exception e) {
145 | return setupMock(e);
146 | }
147 |
148 | @SuppressWarnings({ "rawtypes", "unchecked" })
149 | private ODataClient setupMock(Exception e) {
150 |
151 | class MockHelpers {
152 |
153 | ODataResponse setupResponseMock() {
154 | ODataResponse responseMock = createMock(ODataResponse.class);
155 | expect(responseMock.getStatusCode()).andReturn(204);
156 | responseMock.close(); expectLastCall();
157 | replay(responseMock);
158 | return responseMock;
159 | }
160 |
161 | RetrieveRequestFactory setupCSRFResponseMock() {
162 | ODataRetrieveResponse> responseMock = createMock(ODataRetrieveResponse.class);
163 | expect(responseMock.getHeader("X-CSRF-Token")).andReturn(Arrays.asList("yyy"));
164 |
165 | ODataEntitySetIteratorRequest oDataEntitySetIteratorRequestMock = createMock(ODataEntitySetIteratorRequest.class);
166 | expect(oDataEntitySetIteratorRequestMock.addCustomHeader("X-CSRF-Token", "Fetch")).andReturn(oDataEntitySetIteratorRequestMock);
167 | expect(oDataEntitySetIteratorRequestMock.setAccept("application/xml")).andReturn(oDataEntitySetIteratorRequestMock);
168 | expect(oDataEntitySetIteratorRequestMock.execute()).andReturn(responseMock);
169 |
170 | RetrieveRequestFactory retrieveRequestFactoryMock = createMock(RetrieveRequestFactory.class);
171 | expect(retrieveRequestFactoryMock.getEntitySetIteratorRequest(EasyMock.anyObject(URI.class))).andReturn(oDataEntitySetIteratorRequestMock);
172 |
173 | replay(responseMock, oDataEntitySetIteratorRequestMock, retrieveRequestFactoryMock);
174 | return retrieveRequestFactoryMock;
175 | }
176 |
177 | ODataMediaEntityUpdateRequest setupEntityUpdateRequestMock(ODataPayloadManager payloadManagerMock) {
178 | ODataMediaEntityUpdateRequest entityUpdateRequestMock = createMock(ODataMediaEntityUpdateRequest.class);
179 | expect(entityUpdateRequestMock.addCustomHeader("x-csrf-token", "yyy")).andReturn(entityUpdateRequestMock);
180 | entityUpdateRequestMock.setFormat(ContentType.APPLICATION_ATOM_XML); expectLastCall();
181 | expect(entityUpdateRequestMock.setContentType("text/plain")).andReturn(entityUpdateRequestMock);
182 | expect(entityUpdateRequestMock.payloadManager()).andReturn(payloadManagerMock);
183 | replay(entityUpdateRequestMock);
184 | return entityUpdateRequestMock;
185 | }
186 | }
187 |
188 | MockHelpers helpers = new MockHelpers();
189 |
190 | ODataPayloadManager payloadManagerMock = createMock(ODataPayloadManager.class);
191 | if(e != null) {
192 | expect(payloadManagerMock.getResponse()).andThrow(e);
193 | } else {
194 | expect(payloadManagerMock.getResponse()).andReturn(helpers.setupResponseMock());
195 | }
196 |
197 | CUDRequestFactory cudRequestFactoryMock = createMock(CUDRequestFactory.class);
198 | expect(cudRequestFactoryMock.getMediaEntityUpdateRequest(capture(address), anyObject(InputStream.class)))
199 | .andReturn(helpers.setupEntityUpdateRequestMock(payloadManagerMock));
200 |
201 | ODataClient clientMock = createMockBuilder(ODataClientImpl.class)
202 | .addMockedMethod("getConfiguration")
203 | .addMockedMethod("getRetrieveRequestFactory")
204 | .addMockedMethod("getCUDRequestFactory").createMock();
205 |
206 | expect(clientMock.getCUDRequestFactory()).andReturn(cudRequestFactoryMock);
207 | expect(clientMock.getConfiguration()).andReturn(MockHelper.getConfiguration()).times(2);
208 | expect(clientMock.getRetrieveRequestFactory()).andReturn(helpers.setupCSRFResponseMock());
209 |
210 | replay(payloadManagerMock, cudRequestFactoryMock, clientMock);
211 | return clientMock;
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/LICENSES/Apache-2.0.txt:
--------------------------------------------------------------------------------
1 | Apache License
2 |
3 | Version 2.0, January 2004
4 |
5 | http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION,
6 | AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 |
11 |
12 | "License" shall mean the terms and conditions for use, reproduction, and distribution
13 | as defined by Sections 1 through 9 of this document.
14 |
15 |
16 |
17 | "Licensor" shall mean the copyright owner or entity authorized by the copyright
18 | owner that is granting the License.
19 |
20 |
21 |
22 | "Legal Entity" shall mean the union of the acting entity and all other entities
23 | that control, are controlled by, or are under common control with that entity.
24 | For the purposes of this definition, "control" means (i) the power, direct
25 | or indirect, to cause the direction or management of such entity, whether
26 | by contract or otherwise, or (ii) ownership of fifty percent (50%) or more
27 | of the outstanding shares, or (iii) beneficial ownership of such entity.
28 |
29 |
30 |
31 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions
32 | granted by this License.
33 |
34 |
35 |
36 | "Source" form shall mean the preferred form for making modifications, including
37 | but not limited to software source code, documentation source, and configuration
38 | files.
39 |
40 |
41 |
42 | "Object" form shall mean any form resulting from mechanical transformation
43 | or translation of a Source form, including but not limited to compiled object
44 | code, generated documentation, and conversions to other media types.
45 |
46 |
47 |
48 | "Work" shall mean the work of authorship, whether in Source or Object form,
49 | made available under the License, as indicated by a copyright notice that
50 | is included in or attached to the work (an example is provided in the Appendix
51 | below).
52 |
53 |
54 |
55 | "Derivative Works" shall mean any work, whether in Source or Object form,
56 | that is based on (or derived from) the Work and for which the editorial revisions,
57 | annotations, elaborations, or other modifications represent, as a whole, an
58 | original work of authorship. For the purposes of this License, Derivative
59 | Works shall not include works that remain separable from, or merely link (or
60 | bind by name) to the interfaces of, the Work and Derivative Works thereof.
61 |
62 |
63 |
64 | "Contribution" shall mean any work of authorship, including the original version
65 | of the Work and any modifications or additions to that Work or Derivative
66 | Works thereof, that is intentionally submitted to Licensor for inclusion in
67 | the Work by the copyright owner or by an individual or Legal Entity authorized
68 | to submit on behalf of the copyright owner. For the purposes of this definition,
69 | "submitted" means any form of electronic, verbal, or written communication
70 | sent to the Licensor or its representatives, including but not limited to
71 | communication on electronic mailing lists, source code control systems, and
72 | issue tracking systems that are managed by, or on behalf of, the Licensor
73 | for the purpose of discussing and improving the Work, but excluding communication
74 | that is conspicuously marked or otherwise designated in writing by the copyright
75 | owner as "Not a Contribution."
76 |
77 |
78 |
79 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf
80 | of whom a Contribution has been received by Licensor and subsequently incorporated
81 | within the Work.
82 |
83 | 2. Grant of Copyright License. Subject to the terms and conditions of this
84 | License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,
85 | no-charge, royalty-free, irrevocable copyright license to reproduce, prepare
86 | Derivative Works of, publicly display, publicly perform, sublicense, and distribute
87 | the Work and such Derivative Works in Source or Object form.
88 |
89 | 3. Grant of Patent License. Subject to the terms and conditions of this License,
90 | each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,
91 | no-charge, royalty-free, irrevocable (except as stated in this section) patent
92 | license to make, have made, use, offer to sell, sell, import, and otherwise
93 | transfer the Work, where such license applies only to those patent claims
94 | licensable by such Contributor that are necessarily infringed by their Contribution(s)
95 | alone or by combination of their Contribution(s) with the Work to which such
96 | Contribution(s) was submitted. If You institute patent litigation against
97 | any entity (including a cross-claim or counterclaim in a lawsuit) alleging
98 | that the Work or a Contribution incorporated within the Work constitutes direct
99 | or contributory patent infringement, then any patent licenses granted to You
100 | under this License for that Work shall terminate as of the date such litigation
101 | is filed.
102 |
103 | 4. Redistribution. You may reproduce and distribute copies of the Work or
104 | Derivative Works thereof in any medium, with or without modifications, and
105 | in Source or Object form, provided that You meet the following conditions:
106 |
107 | (a) You must give any other recipients of the Work or Derivative Works a copy
108 | of this License; and
109 |
110 | (b) You must cause any modified files to carry prominent notices stating that
111 | You changed the files; and
112 |
113 | (c) You must retain, in the Source form of any Derivative Works that You distribute,
114 | all copyright, patent, trademark, and attribution notices from the Source
115 | form of the Work, excluding those notices that do not pertain to any part
116 | of the Derivative Works; and
117 |
118 | (d) If the Work includes a "NOTICE" text file as part of its distribution,
119 | then any Derivative Works that You distribute must include a readable copy
120 | of the attribution notices contained within such NOTICE file, excluding those
121 | notices that do not pertain to any part of the Derivative Works, in at least
122 | one of the following places: within a NOTICE text file distributed as part
123 | of the Derivative Works; within the Source form or documentation, if provided
124 | along with the Derivative Works; or, within a display generated by the Derivative
125 | Works, if and wherever such third-party notices normally appear. The contents
126 | of the NOTICE file are for informational purposes only and do not modify the
127 | License. You may add Your own attribution notices within Derivative Works
128 | that You distribute, alongside or as an addendum to the NOTICE text from the
129 | Work, provided that such additional attribution notices cannot be construed
130 | as modifying the License.
131 |
132 | You may add Your own copyright statement to Your modifications and may provide
133 | additional or different license terms and conditions for use, reproduction,
134 | or distribution of Your modifications, or for any such Derivative Works as
135 | a whole, provided Your use, reproduction, and distribution of the Work otherwise
136 | complies with the conditions stated in this License.
137 |
138 | 5. Submission of Contributions. Unless You explicitly state otherwise, any
139 | Contribution intentionally submitted for inclusion in the Work by You to the
140 | Licensor shall be under the terms and conditions of this License, without
141 | any additional terms or conditions. Notwithstanding the above, nothing herein
142 | shall supersede or modify the terms of any separate license agreement you
143 | may have executed with Licensor regarding such Contributions.
144 |
145 | 6. Trademarks. This License does not grant permission to use the trade names,
146 | trademarks, service marks, or product names of the Licensor, except as required
147 | for reasonable and customary use in describing the origin of the Work and
148 | reproducing the content of the NOTICE file.
149 |
150 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to
151 | in writing, Licensor provides the Work (and each Contributor provides its
152 | Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
153 | KIND, either express or implied, including, without limitation, any warranties
154 | or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR
155 | A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness
156 | of using or redistributing the Work and assume any risks associated with Your
157 | exercise of permissions under this License.
158 |
159 | 8. Limitation of Liability. In no event and under no legal theory, whether
160 | in tort (including negligence), contract, or otherwise, unless required by
161 | applicable law (such as deliberate and grossly negligent acts) or agreed to
162 | in writing, shall any Contributor be liable to You for damages, including
163 | any direct, indirect, special, incidental, or consequential damages of any
164 | character arising as a result of this License or out of the use or inability
165 | to use the Work (including but not limited to damages for loss of goodwill,
166 | work stoppage, computer failure or malfunction, or any and all other commercial
167 | damages or losses), even if such Contributor has been advised of the possibility
168 | of such damages.
169 |
170 | 9. Accepting Warranty or Additional Liability. While redistributing the Work
171 | or Derivative Works thereof, You may choose to offer, and charge a fee for,
172 | acceptance of support, warranty, indemnity, or other liability obligations
173 | and/or rights consistent with this License. However, in accepting such obligations,
174 | You may act only on Your own behalf and on Your sole responsibility, not on
175 | behalf of any other Contributor, and only if You agree to indemnify, defend,
176 | and hold each Contributor harmless for any liability incurred by, or claims
177 | asserted against, such Contributor by reason of your accepting any such warranty
178 | or additional liability. END OF TERMS AND CONDITIONS
179 |
180 | APPENDIX: How to apply the Apache License to your work.
181 |
182 | To apply the Apache License to your work, attach the following boilerplate
183 | notice, with the fields enclosed by brackets "[]" replaced with your own identifying
184 | information. (Don't include the brackets!) The text should be enclosed in
185 | the appropriate comment syntax for the file format. We also recommend that
186 | a file or class name and description of purpose be included on the same "printed
187 | page" as the copyright notice for easier identification within third-party
188 | archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 |
194 | you may not use this file except in compliance with the License.
195 |
196 | You may obtain a copy of the License at
197 |
198 | http://www.apache.org/licenses/LICENSE-2.0
199 |
200 | Unless required by applicable law or agreed to in writing, software
201 |
202 | distributed under the License is distributed on an "AS IS" BASIS,
203 |
204 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
205 |
206 | See the License for the specific language governing permissions and
207 |
208 | limitations under the License.
209 |
--------------------------------------------------------------------------------
/modules/cli/src/test/java/sap/prd/cmintegration/cli/SolManBackendGetChangeStatusTest.java:
--------------------------------------------------------------------------------
1 | package sap.prd.cmintegration.cli;
2 |
3 | import static org.easymock.EasyMock.capture;
4 | import static org.easymock.EasyMock.createMock;
5 | import static org.easymock.EasyMock.expect;
6 | import static org.easymock.EasyMock.expectLastCall;
7 | import static org.easymock.EasyMock.replay;
8 | import static org.hamcrest.Matchers.equalTo;
9 | import static org.hamcrest.Matchers.is;
10 | import static org.junit.Assert.assertThat;
11 |
12 | import java.io.BufferedReader;
13 | import java.io.ByteArrayInputStream;
14 | import java.io.InputStream;
15 | import java.io.InputStreamReader;
16 |
17 | import org.apache.commons.cli.MissingOptionException;
18 | import org.apache.commons.io.IOUtils;
19 | import org.apache.olingo.client.api.communication.ODataClientErrorException;
20 | import org.junit.Test;
21 |
22 | import com.sap.cmclient.Matchers;
23 |
24 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataChange;
25 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient;
26 |
27 | public class SolManBackendGetChangeStatusTest extends CMSolmanTestBase {
28 |
29 | private SolmanClientFactory setupMock() throws Exception {
30 | return setupMock(true, null);
31 | }
32 |
33 | private SolmanClientFactory setupMock(boolean isInDevelopment) throws Exception {
34 | return setupMock(isInDevelopment, null);
35 | }
36 |
37 | private SolmanClientFactory setupMock(Exception ex) throws Exception {
38 | return setupMock(true, ex);
39 | }
40 |
41 | private SolmanClientFactory setupMock(boolean isInDevelopment, Exception ex) throws Exception {
42 | CMODataSolmanClient clientMock = createMock(CMODataSolmanClient.class);
43 | clientMock.close(); expectLastCall();
44 | if(ex == null) {
45 | CMODataChange change = new CMODataChange("8000038673", isInDevelopment);
46 | expect(clientMock.getChange(capture(changeId))).andReturn(change);
47 | } else {
48 | expect(clientMock.getChange(capture(changeId))).andThrow(ex);
49 | }
50 | SolmanClientFactory factoryMock = createMock(SolmanClientFactory.class);
51 | expect(factoryMock
52 | .newClient(capture(host),
53 | capture(user),
54 | capture(password))).andReturn(clientMock);
55 |
56 | replay(clientMock, factoryMock);
57 | return factoryMock;
58 | }
59 |
60 | @Test
61 | public void testGetChangeStatusStraightForwardViaStdout() throws Exception {
62 |
63 | //
64 | // Comment line below in order to go against the real back-end as specified via -h
65 | setMock(setupMock());
66 |
67 | Commands.main(new String[] {
68 | "-u", SERVICE_USER,
69 | "-p", SERVICE_PASSWORD,
70 | "-e", SERVICE_ENDPOINT,
71 | "is-change-in-development",
72 | "-cID", "8000038673"});
73 |
74 | assertThat(changeId.getValue(), is(equalTo("8000038673")));
75 | assertThat(user.getValue(), is(equalTo(SERVICE_USER)));
76 | assertThat(password.getValue(), is(equalTo(SERVICE_PASSWORD)));
77 | assertThat(host.getValue(), is(equalTo(SERVICE_ENDPOINT)));
78 |
79 | assertThat(new BufferedReader(new InputStreamReader(new ByteArrayInputStream(result.toByteArray()), "UTF-8")).readLine(), equalTo("true"));
80 | }
81 |
82 | @Test
83 | public void testGetChangeStatusStraightForwardViaStdoutReturnsFalseWhenChangeIsNotInDevelopment() throws Exception {
84 |
85 | //
86 | // Comment line below in order to go against the real back-end as specified via -h
87 | setMock(setupMock(false));
88 |
89 | Commands.main(new String[] {
90 | "-u", SERVICE_USER,
91 | "-p", SERVICE_PASSWORD,
92 | "-e", SERVICE_ENDPOINT,
93 | "is-change-in-development",
94 | "-cID", "8000038673"});
95 |
96 | assertThat(changeId.getValue(), is(equalTo("8000038673")));
97 | assertThat(user.getValue(), is(equalTo(SERVICE_USER)));
98 | assertThat(password.getValue(), is(equalTo(SERVICE_PASSWORD)));
99 | assertThat(host.getValue(), is(equalTo(SERVICE_ENDPOINT)));
100 |
101 | assertThat(new BufferedReader(new InputStreamReader(new ByteArrayInputStream(result.toByteArray()), "UTF-8")).readLine(), equalTo("false"));
102 | }
103 |
104 | @Test
105 | public void testGetChangeStatusReturnsTrueStraightForwardViaReturnCode() throws Exception {
106 |
107 | // The absence of an exception means "change is in development"
108 |
109 | // Comment line below in order to go against the real back-end as specified via -h
110 | setMock(setupMock());
111 |
112 | Commands.main(new String[] {
113 | "-u", SERVICE_USER,
114 | "-p", SERVICE_PASSWORD,
115 | "-e", SERVICE_ENDPOINT,
116 | "is-change-in-development",
117 | "--return-code",
118 | "-cID", "8000038673"});
119 |
120 | assertThat(changeId.getValue(), is(equalTo("8000038673")));
121 | assertThat(user.getValue(), is(equalTo(SERVICE_USER)));
122 | assertThat(password.getValue(), is(equalTo(SERVICE_PASSWORD)));
123 | assertThat(host.getValue(), is(equalTo(SERVICE_ENDPOINT)));
124 |
125 | assertThat(IOUtils.toString(new ByteArrayInputStream(result.toByteArray()), "UTF-8"), equalTo(""));
126 | }
127 |
128 | @Test
129 | public void testGetChangeStatusThrowsExceptionStraightForwardViaReturnCode() throws Exception {
130 |
131 | // The absence of an exception means "change is in development"
132 |
133 | thrown.expect(ExitException.class);
134 | thrown.expect(sap.prd.cmintegration.cli.Matchers.exitCode(3));
135 |
136 | // Comment line below in order to go against the real back-end as specified via -h
137 | setMock(setupMock(false));
138 |
139 | Commands.main(new String[] {
140 | "-u", SERVICE_USER,
141 | "-p", SERVICE_PASSWORD,
142 | "-e", SERVICE_ENDPOINT,
143 | "is-change-in-development",
144 | "--return-code",
145 | "-cID", "8000038673"});
146 | }
147 |
148 |
149 | @Test
150 | public void testGetChangeStatusWithBadCredentials() throws Exception {
151 |
152 | thrown.expect(ExitException.class);
153 | thrown.expect(Matchers.hasRootCause(ODataClientErrorException.class));
154 | thrown.expect(Matchers.rootCauseMessageContains("401"));
155 |
156 | //
157 | // Comment line below in order to go against the real back-end as specified via -h
158 | setMock(setupMock(new ODataClientErrorException(StatusLines.UNAUTHORIZED)));
159 |
160 | Commands.main(new String[] {
161 | "-u", "DOES_NOT_EXIST",
162 | "-p", "********",
163 | "-e", SERVICE_ENDPOINT,
164 | "is-change-in-development",
165 | "-cID", "8000038673"});
166 | }
167 |
168 | @Test
169 | public void testGetChangeStatusForNotExistingChange() throws Exception {
170 |
171 | thrown.expect(ODataClientErrorException.class);
172 | thrown.expectMessage("404");
173 | //
174 | // Comment line below in order to go against the real back-end as specified via -h
175 | setMock(setupMock(new ODataClientErrorException(StatusLines.NOT_FOUND)));
176 |
177 | try {
178 | Commands.main(new String[] {
179 | "-u", SERVICE_USER,
180 | "-p", SERVICE_PASSWORD,
181 | "-e", SERVICE_ENDPOINT,
182 | "is-change-in-development",
183 | "-cID", "DOES_NOT_EXIST"});
184 | } catch(Exception e) {
185 | assertThat(changeId.getValue(), is(equalTo("DOES_NOT_EXIST")));
186 | throw e;
187 | }
188 | }
189 |
190 | @Test
191 | public void testGetChangeStatusWithoutChangeId() throws Exception {
192 |
193 | thrown.expect(CMCommandLineException.class);
194 | thrown.expectMessage("No changeId specified.");
195 | //
196 | // Comment line below in order to go against the real back-end as specified via -h
197 | setMock(setupMock());
198 |
199 | Commands.main(new String[] {
200 | "-u", SERVICE_USER,
201 | "-p", SERVICE_PASSWORD,
202 | "-e", SERVICE_ENDPOINT,
203 | "is-change-in-development"});
204 | }
205 |
206 | @Test
207 | public void testGetChangeStatusPasswordViaStdin() throws Exception {
208 |
209 | InputStream oldIn = System.in;
210 | System.setIn(new ByteArrayInputStream(SERVICE_PASSWORD.getBytes()));
211 |
212 | //
213 | // Comment line below in order to go against the real back-end as specified via -h
214 | setMock(setupMock());
215 |
216 | try {
217 | Commands.main(new String[] {
218 | "-u", SERVICE_USER,
219 | "-p", "-",
220 | "-e", SERVICE_ENDPOINT,
221 | "is-change-in-development",
222 | "-cID", "8000038673"});
223 | } finally {
224 | System.setIn(oldIn);
225 | }
226 |
227 | assertThat(password.getValue(), is(equalTo(SERVICE_PASSWORD)));
228 | }
229 |
230 | @Test
231 | public void testGetChangeStatusMultilinePasswordViaStdin() throws Exception {
232 |
233 | thrown.expect(CMCommandLineException.class);
234 | thrown.expectMessage("Multiline passwords are not supported.");
235 |
236 | InputStream oldIn = System.in;
237 | System.setIn(new ByteArrayInputStream(SERVICE_PASSWORD.concat("\r\nPWDAGAIN").getBytes()));
238 |
239 | //
240 | // Comment line below in order to go against the real back-end as specified via -h
241 | setMock(setupMock());
242 |
243 | try {
244 | Commands.main(new String[] {
245 | "-u", SERVICE_USER,
246 | "-p", "-",
247 | "-e", SERVICE_ENDPOINT,
248 | "is-change-in-development",
249 | "8000038673"});
250 | } finally {
251 | System.setIn(oldIn);
252 | }
253 | }
254 | @Test
255 | public void testGetChangeStatusEmptyPasswordViaStdin() throws Exception {
256 |
257 | thrown.expect(CMCommandLineException.class);
258 | thrown.expectMessage("Empty password found.");
259 |
260 | InputStream oldIn = System.in;
261 | System.setIn(new ByteArrayInputStream("".getBytes()));
262 |
263 | //
264 | // Comment line below in order to go against the real back-end as specified via -h
265 | setMock(setupMock());
266 |
267 | try {
268 | Commands.main(new String[] {
269 | "-u", SERVICE_USER,
270 | "-p", "-",
271 | "-e", SERVICE_ENDPOINT,
272 | "is-change-in-development",
273 | "8000038673"});
274 | } finally {
275 | System.setIn(oldIn);
276 | }
277 | }
278 |
279 | @Test
280 | public void testGetChangeStatusNoPassword() throws Exception {
281 |
282 | thrown.expect(MissingOptionException.class);
283 | thrown.expectMessage("Missing required option: p");
284 |
285 | //
286 | // Comment line below in order to go against the real back-end as specified via -h
287 | setMock(setupMock());
288 |
289 | Commands.main(new String[] {
290 | "-u", SERVICE_USER,
291 | "-e", SERVICE_ENDPOINT,
292 | "is-change-in-development",
293 | "8000038673"});
294 | }
295 | }
296 |
--------------------------------------------------------------------------------