Introducing new step: "gctsCreateRepository" (#1424)

With the step gctsCreateRepository it is possible to create a local gCTS repository on an ABAP server

Co-authored-by: Marcus Holl <marcus.holl@sap.com>
This commit is contained in:
Chris Bo
2020-04-24 15:31:41 +02:00
committed by GitHub
co-authored by Marcus Holl
parent 96439972e2
commit fb4cfd84ec
11 changed files with 653 additions and 1 deletions
+128
View File
@@ -0,0 +1,128 @@
package cmd
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/cookiejar"
gabs "github.com/Jeffail/gabs/v2"
"github.com/SAP/jenkins-library/pkg/command"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/pkg/errors"
)
func gctsCreateRepository(config gctsCreateRepositoryOptions, telemetryData *telemetry.CustomData) {
// for command execution use Command
c := command.Command{}
// reroute command output to logging framework
c.Stdout(log.Entry().Writer())
c.Stderr(log.Entry().Writer())
// for http calls import piperhttp "github.com/SAP/jenkins-library/pkg/http"
// and use a &piperhttp.Client{} in a custom system
// Example: step checkmarxExecuteScan.go
httpClient := &piperhttp.Client{}
// error situations should stop execution through log.Entry().Fatal() call which leads to an os.Exit(1) in the end
err := createRepository(&config, telemetryData, &c, httpClient)
if err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
}
}
func createRepository(config *gctsCreateRepositoryOptions, telemetryData *telemetry.CustomData, command execRunner, httpClient piperhttp.Sender) error {
cookieJar, cookieErr := cookiejar.New(nil)
if cookieErr != nil {
return errors.Wrapf(cookieErr, "creating repository on the ABAP system %v failed", config.Host)
}
clientOptions := piperhttp.ClientOptions{
CookieJar: cookieJar,
Username: config.Username,
Password: config.Password,
}
httpClient.SetOptions(clientOptions)
type repoData struct {
RID string `json:"rid"`
Name string `json:"name"`
Role string `json:"role"`
Type string `json:"type"`
VSID string `json:"vsid"`
RemoteRepositoryURL string `json:"url"`
}
type createRequestBody struct {
Repository string `json:"repository"`
Data repoData `json:"data"`
}
reqBody := createRequestBody{
Repository: config.Repository,
Data: repoData{
RID: config.Repository,
Name: config.Repository,
Role: config.Role,
Type: config.Type,
VSID: config.VSID,
RemoteRepositoryURL: config.RemoteRepositoryURL,
},
}
jsonBody, marshalErr := json.Marshal(reqBody)
if marshalErr != nil {
return errors.Wrapf(marshalErr, "creating repository on the ABAP system %v failed", config.Host)
}
header := make(http.Header)
header.Set("Content-Type", "application/json")
header.Add("Accept", "application/json")
url := config.Host + "/sap/bc/cts_abapvcs/repository?sap-client=" + config.Client
resp, httpErr := httpClient.SendRequest("POST", url, bytes.NewBuffer(jsonBody), header, nil)
defer func() {
if resp != nil && resp.Body != nil {
resp.Body.Close()
}
}()
if resp == nil {
return errors.Errorf("creating repository on the ABAP system %v failed: %v", config.Host, httpErr)
}
bodyText, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil {
return errors.Wrapf(readErr, "creating repository on the ABAP system %v failed", config.Host)
}
response, parsingErr := gabs.ParseJSON([]byte(bodyText))
if parsingErr != nil {
return errors.Wrapf(parsingErr, "creating repository on the ABAP system %v failed", config.Host)
}
if httpErr != nil {
if resp.StatusCode == 500 {
if exception, ok := response.Path("exception").Data().(string); ok && exception == "Repository already exists" {
log.Entry().
WithField("repository", config.Repository).
Infof("the repository already exists on the ABAP system %v", config.Host)
return nil
}
}
log.Entry().Errorf("a HTTP error occured! Response body: %v", response)
return errors.Wrapf(httpErr, "creating repository on the ABAP system %v failed", config.Host)
}
log.Entry().
WithField("repository", config.Repository).
Infof("successfully created the repository on ABAP system %v", config.Host)
return nil
}
+172
View File
@@ -0,0 +1,172 @@
// Code generated by piper's step-generator. DO NOT EDIT.
package cmd
import (
"fmt"
"os"
"time"
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/spf13/cobra"
)
type gctsCreateRepositoryOptions struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Repository string `json:"repository,omitempty"`
Host string `json:"host,omitempty"`
Client string `json:"client,omitempty"`
RemoteRepositoryURL string `json:"remoteRepositoryURL,omitempty"`
Role string `json:"role,omitempty"`
VSID string `json:"vSID,omitempty"`
Type string `json:"type,omitempty"`
}
// GctsCreateRepositoryCommand Creates a Git repository on an ABAP system
func GctsCreateRepositoryCommand() *cobra.Command {
metadata := gctsCreateRepositoryMetadata()
var stepConfig gctsCreateRepositoryOptions
var startTime time.Time
var createGctsCreateRepositoryCmd = &cobra.Command{
Use: "gctsCreateRepository",
Short: "Creates a Git repository on an ABAP system",
Long: `Creates a local Git repository on an ABAP system if it does not already exist.`,
PreRunE: func(cmd *cobra.Command, args []string) error {
startTime = time.Now()
log.SetStepName("gctsCreateRepository")
log.SetVerbose(GeneralConfig.Verbose)
err := PrepareConfig(cmd, &metadata, "gctsCreateRepository", &stepConfig, config.OpenPiperFile)
if err != nil {
return err
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
telemetryData := telemetry.CustomData{}
telemetryData.ErrorCode = "1"
handler := func() {
telemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds())
telemetry.Send(&telemetryData)
}
log.DeferExitHandler(handler)
defer handler()
telemetry.Initialize(GeneralConfig.NoTelemetry, "gctsCreateRepository")
gctsCreateRepository(stepConfig, &telemetryData)
telemetryData.ErrorCode = "0"
},
}
addGctsCreateRepositoryFlags(createGctsCreateRepositoryCmd, &stepConfig)
return createGctsCreateRepositoryCmd
}
func addGctsCreateRepositoryFlags(cmd *cobra.Command, stepConfig *gctsCreateRepositoryOptions) {
cmd.Flags().StringVar(&stepConfig.Username, "username", os.Getenv("PIPER_username"), "Username to authenticate to the ABAP system")
cmd.Flags().StringVar(&stepConfig.Password, "password", os.Getenv("PIPER_password"), "Password to authenticate to the ABAP system")
cmd.Flags().StringVar(&stepConfig.Repository, "repository", os.Getenv("PIPER_repository"), "Specifies the name (ID) of the local repository on the ABAP system")
cmd.Flags().StringVar(&stepConfig.Host, "host", os.Getenv("PIPER_host"), "Specifies the protocol and host adress, including the port. Please provide in the format '<protocol>://<host>:<port>'")
cmd.Flags().StringVar(&stepConfig.Client, "client", os.Getenv("PIPER_client"), "Specifies the client of the ABAP system to be adressed")
cmd.Flags().StringVar(&stepConfig.RemoteRepositoryURL, "remoteRepositoryURL", os.Getenv("PIPER_remoteRepositoryURL"), "URL of the corresponding remote repository")
cmd.Flags().StringVar(&stepConfig.Role, "role", os.Getenv("PIPER_role"), "Role of the local repository. Choose between 'TARGET' and 'SOURCE'. Local repositories with a TARGET role will NOT be able to be the source of code changes")
cmd.Flags().StringVar(&stepConfig.VSID, "vSID", os.Getenv("PIPER_vSID"), "Virtual SID of the local repository. The vSID corresponds to the transport route that delivers content to the remote Git repository")
cmd.Flags().StringVar(&stepConfig.Type, "type", "GIT", "Type of the used source code management tool")
cmd.MarkFlagRequired("username")
cmd.MarkFlagRequired("password")
cmd.MarkFlagRequired("repository")
cmd.MarkFlagRequired("host")
cmd.MarkFlagRequired("client")
}
// retrieve step metadata
func gctsCreateRepositoryMetadata() config.StepData {
var theMetaData = config.StepData{
Metadata: config.StepMetadata{
Name: "gctsCreateRepository",
Aliases: []config.Alias{},
},
Spec: config.StepSpec{
Inputs: config.StepInputs{
Parameters: []config.StepParameters{
{
Name: "username",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "password",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "repository",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "host",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "client",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "remoteRepositoryURL",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "role",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "vSID",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "type",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
},
},
},
}
return theMetaData
}
@@ -0,0 +1,16 @@
package cmd
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGctsCreateRepositoryCommand(t *testing.T) {
testCmd := GctsCreateRepositoryCommand()
// only high level testing performed - details are tested in step generation procudure
assert.Equal(t, "gctsCreateRepository", testCmd.Use, "command name incorrect")
}
+186
View File
@@ -0,0 +1,186 @@
package cmd
import (
"bytes"
"errors"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/stretchr/testify/assert"
"io"
"io/ioutil"
"net/http"
"testing"
)
func TestGctsCreateRepositorySuccess(t *testing.T) {
config := gctsCreateRepositoryOptions{
Host: "http://testHost.com:50000",
Client: "000",
Repository: "testRepo",
Username: "testUser",
Password: "testPassword",
RemoteRepositoryURL: "https://github.com/org/testRepo",
Role: "SOURCE",
VSID: "TST",
}
t.Run("creating repository on ABAP system successfull", func(t *testing.T) {
httpClient := httpMockGcts{StatusCode: 200, ResponseBody: `{
"repository": {
"rid": "my-repository",
"name": "Example repository",
"role": "SOURCE",
"type": "GIT",
"vsid": "GI7",
"status": "READY",
"branch": "master",
"url": "https://github.com/git/git",
"version": "1.0.1",
"objects": 1337,
"currentCommit": "f1cdb6a032c1d8187c0990b51e94e8d8bb9898b2",
"connection": "ssl",
"config": [
{
"key": "CLIENT_VCS_URI",
"value": "git@github.com/example.git"
}
]
},
"log": [
{
"time": 20180606130524,
"user": "JENKINS",
"section": "REPOSITORY_FACTORY",
"action": "CREATE_REPOSITORY",
"severity": "INFO",
"message": "Start action CREATE_REPOSITORY review",
"code": "GCTS.API.410"
}
]
}`}
err := createRepository(&config, nil, nil, &httpClient)
if assert.NoError(t, err) {
t.Run("check url", func(t *testing.T) {
assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository?sap-client=000", httpClient.URL)
})
t.Run("check method", func(t *testing.T) {
assert.Equal(t, "POST", httpClient.Method)
})
t.Run("check user", func(t *testing.T) {
assert.Equal(t, "testUser", httpClient.Options.Username)
})
t.Run("check password", func(t *testing.T) {
assert.Equal(t, "testPassword", httpClient.Options.Password)
})
}
})
t.Run("repository already exists on ABAP system", func(t *testing.T) {
httpClient := httpMockGcts{StatusCode: 500, ResponseBody: `{
"exception": "Repository already exists"
}`}
err := createRepository(&config, nil, nil, &httpClient)
assert.NoError(t, err)
})
}
func TestGctsCreateRepositoryFailure(t *testing.T) {
config := gctsCreateRepositoryOptions{
Host: "http://testHost.com:50000",
Client: "000",
Repository: "testRepo",
Username: "testUser",
Password: "testPassword",
RemoteRepositoryURL: "https://github.com/org/testRepo",
Role: "SOURCE",
VSID: "TST",
}
t.Run("a http error occurred", func(t *testing.T) {
httpClient := httpMockGcts{StatusCode: 500, ResponseBody: `{
"log": [
{
"time": 20180606130524,
"user": "JENKINS",
"section": "REPOSITORY_FACTORY",
"action": "CREATE_REPOSITORY",
"severity": "INFO",
"message": "Start action CREATE_REPOSITORY review",
"code": "GCTS.API.410"
}
],
"errorLog": [
{
"time": 20180606130524,
"user": "JENKINS",
"section": "REPOSITORY_FACTORY",
"action": "CREATE_REPOSITORY",
"severity": "INFO",
"message": "Start action CREATE_REPOSITORY review",
"code": "GCTS.API.410"
}
],
"exception": {
"message": "repository_not_found",
"description": "Repository not found",
"code": 404
}
}`}
err := createRepository(&config, nil, nil, &httpClient)
assert.EqualError(t, err, "creating repository on the ABAP system http://testHost.com:50000 failed: a http error occurred")
})
}
type httpMockGcts struct {
Method string // is set during test execution
URL string // is set before test execution
Header map[string][]string // is set before test execution
ResponseBody string // is set before test execution
Options piperhttp.ClientOptions // is set during test
StatusCode int // is set during test
}
func (c *httpMockGcts) SetOptions(options piperhttp.ClientOptions) {
c.Options = options
}
func (c *httpMockGcts) SendRequest(method string, url string, r io.Reader, header http.Header, cookies []*http.Cookie) (*http.Response, error) {
c.Method = method
c.URL = url
if r != nil {
_, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
}
res := http.Response{
StatusCode: c.StatusCode,
Header: c.Header,
Body: ioutil.NopCloser(bytes.NewReader([]byte(c.ResponseBody))),
}
if c.StatusCode >= 400 {
return &res, errors.New("a http error occurred")
}
return &res, nil
}
+1
View File
@@ -66,6 +66,7 @@ func Execute() {
rootCmd.AddCommand(MavenBuildCommand())
rootCmd.AddCommand(MavenExecuteStaticCodeChecksCommand())
rootCmd.AddCommand(NexusUploadCommand())
rootCmd.AddCommand(GctsCreateRepositoryCommand())
rootCmd.AddCommand(MalwareExecuteScanCommand())
addRootFlags(rootCmd)
@@ -0,0 +1,47 @@
# ${docGenStepName}
## ${docGenDescription}
## Prerequisites
With this step you can create a local git-enabled CTS (gCTS) repository on an ABAP server.
Learn more about gCTS [here](https://help.sap.com/viewer/4a368c163b08418890a406d413933ba7/201909.001/en-US/f319b168e87e42149e25e13c08d002b9.html).
## ${docGenParameters}
## ${docGenConfiguration}
## ${docJenkinsPluginDependencies}
## Example
Example configuration for the use in a Jenkinsfile.
```groovy
gctsCreateRepository(
script: this,
host: "abap.server.com:port",
client: "000",
credentialsId: 'ABAPUserPasswordCredentialsId',
repository: "myrepo",
remoteRepositoryURL: "https://github.com/user/myrepo",
role: "SOURCE",
vSID: "ABC"
)
```
Example configuration for the use in a yaml config file (such as `.pipeline/config.yaml`).
```yaml
steps:
<...>
gctsCreateRepository:
host: "abap.server.com:port"
client: "000"
username: "ABAPUsername"
password: "ABAPPassword"
repository: "myrepo"
remoteRepositoryURL: "https://github.com/user/myrepo",
role: "SOURCE",
vSID: "ABC"
```
+1
View File
@@ -4,6 +4,7 @@ go 1.13
require (
github.com/GoogleContainerTools/container-diff v0.15.0
github.com/Jeffail/gabs/v2 v2.5.0
github.com/Microsoft/hcsshim v0.8.7 // indirect
github.com/bmatcuk/doublestar v1.2.4
github.com/containerd/containerd v1.3.4 // indirect
+2
View File
@@ -25,6 +25,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14=
github.com/GoogleContainerTools/container-diff v0.15.0 h1:fyYoSoJuefWUhVLNAfnSacs/4WWCUR4+2JkiU9xzqOY=
github.com/GoogleContainerTools/container-diff v0.15.0/go.mod h1:tJLKT7P37rapR5AYos9z/zkajzfShGULJtUr2rGHGKY=
github.com/Jeffail/gabs/v2 v2.5.0 h1:ERXffrksCEPjKVDWbZDBcOwrpXctXfeFGXxOQh1umOE=
github.com/Jeffail/gabs/v2 v2.5.0/go.mod h1:xCn81vdHKxFUuWWAaD5jCTQDNPBMh5pPs9IJ+NcziBI=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA=
@@ -0,0 +1,82 @@
metadata:
name: gctsCreateRepository
description: Creates a Git repository on an ABAP system
longDescription: |
Creates a local Git repository on an ABAP system if it does not already exist.
spec:
inputs:
secrets:
- name: abapCredentialsId
description: Jenkins credentials ID containing username and password for authentication to the ABAP system on which you want to create the repository
type: jenkins
params:
- name: username
type: string
description: Username to authenticate to the ABAP system
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: true
- name: password
type: string
description: Password to authenticate to the ABAP system
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: true
- name: repository
type: string
description: Specifies the name (ID) of the local repository on the ABAP system
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: true
- name: host
type: string
description: Specifies the protocol and host adress, including the port. Please provide in the format '<protocol>://<host>:<port>'
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: true
- name: client
type: string
description: Specifies the client of the ABAP system to be adressed
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: true
- name: remoteRepositoryURL
type: string
description: URL of the corresponding remote repository
scope:
- PARAMETERS
- STAGES
- STEPS
- name: role
type: string
description: Role of the local repository. Choose between 'TARGET' and 'SOURCE'. Local repositories with a TARGET role will NOT be able to be the source of code changes
scope:
- PARAMETERS
- STAGES
- STEPS
- name: vSID
type: string
description: Virtual SID of the local repository. The vSID corresponds to the transport route that delivers content to the remote Git repository
scope:
- PARAMETERS
- STAGES
- STEPS
- name: type
type: string
description: Type of the used source code management tool
scope:
- PARAMETERS
- STAGES
- STEPS
default: GIT
+3 -1
View File
@@ -59,7 +59,8 @@ public class CommonStepsTest extends BasePiperTest{
'setupCommonPipelineEnvironment',
'buildSetResult',
'mavenExecuteStaticCodeChecks',
'cloudFoundryCreateServiceKey'
'cloudFoundryCreateServiceKey',
'gctsCreateRepository'
]
List steps = getSteps().stream()
@@ -137,6 +138,7 @@ public class CommonStepsTest extends BasePiperTest{
'nexusUpload', //implementing new golang pattern without fields
'piperPipelineStageArtifactDeployment', //stage without step flags
'sonarExecuteScan', //implementing new golang pattern without fields
'gctsCreateRepository', //implementing new golang pattern without fields
]
@Test
+15
View File
@@ -0,0 +1,15 @@
import com.sap.piper.PiperGoUtils
import com.sap.piper.Utils
import groovy.transform.Field
import static com.sap.piper.Prerequisites.checkScript
@Field String STEP_NAME = getClass().getName()
@Field String METADATA_FILE = 'metadata/gctsCreateRepository.yaml'
void call(Map parameters = [:]) {
List credentials = [
[type: 'usernamePassword', id: 'abapCredentialsId', env: ['PIPER_username', 'PIPER_password']]
]
piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials)
}