mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-11-28 08:49:44 +02:00
Add abapEnvironmentRunATCCheck step (#1454)
* Minor changes * Changed groovy file * Changed generated file * Changed yaml with container config * Changed groovy config * minor changes * minor changes * Changed yaml with aliases * minor changes * minor changes * minor changes * minor changes * minor changes * minor changes * minor changes * minor changes * minor changes * minor changes * Changed yaml aliases * Adapted naming conventions * Removed error code at the end * Adapted configuration * Minor changes * Minor changes * Minor changes * Removed spaces * Removed docker-related config from groovy file * Minor changes * Minor changes * Removed container config * Corrected testing function name * Deleted unnecessary parts * Changed service deletion message * Changed service deletion message * Logging out before throwing error service deletion step fails * Minor changes * Minor changes * Minor changes * Delete .DS_Store * Delete .DS_Store * Delete .DS_Store * Delete .DS_Store * Minor changes * Minor changes * Minor changes * Added newline at end of file * Added newline at end of file * Changes for Pull request optimization * added documentaion * Adapted documentation * Adapted documentation * Adapted documentation * Adapted documentation * Adapted documentation * Added CFDeleteServiceKeys * Added ServiceKey deletion tests * added cfServiceKeys flag explanation to documentation * removed trailing spaces from documentation * resolving conflicts * Changed deletion message an variable naming * Changed tests * Changed tests * Changed tests * Changed tests * Changed CloudFoundryDeleteServiceOptions to options * Changed CloudFoundryDeleteServiceOptions to options * Minor changes * Minor changes * Changed variable naming * Changed error handling * Changed error handling and logging * Changed documentation * Simplified code * Fixed CodeClimate issues * Changed from returning err to nil where no errur returned needed * Add cloudFoundryCreateServiceKey Go Step * Changed Groovy File * Changed aliases * Removed unneccessary parts * Minor changes * Minor changes * Adapted documentation * Adapted tests * Adapted Groovy File * Changed Groovy file * Minor changes * Minor changes * Minor changes * Minor changes * Minor changes * Minor changes * Minor changes * Minor changes * Minor changes * Minor changes * Minor changes * Minor changes * Minor changes * Minor changes * Minor changes * Minor changes * Minor changes * Minor changes * Minor changes * Minor changes * Removed Groovy Tests for cfCreateServiceKey * Minor changes * Added ATC Check YAML * Added ATC Check generated files * Added test class * Added abapEnvironmentRunATCCheck * Minor changes * Minor changes * Changed groovy * Minor changes * Changed groovy * Changed groovy * Minor changes * Adapted Groovy imports * Adapted Groovy imports * Adapted Groovy imports * Adapted Groovy * Getting ATC results * Changed error message * changed groovy * removed trailing spaces * Added login check * Minor changes * Added step to whitelistScriptReference * Added ATC error message handling * Added groovy file * Added step to groovy tests * corrected metadata file * Debugging * Debugging * Added yaml config parameter for ATC run * Adapted file location of ATC run config to jenkins specific location * Implementing universal pipeline logic for finding yaml config regardless of pipeline * Changed error handling for reading config yaml file * Changed atcrunconfig alias * minor changes * Minor changes * Minor changes * Changed back to dynamic file reading * Minor changes * filepath changes * Removing CF Login * Minor changes * Minor changes * Minor changes * Minor changes * Minor changes * Minor changes * Removed whitespaces * Added CF functions unit tests * Added invalid parameter handling * Removed package and SC flag * Minor changes * Changed tests * Changed tests * Changed tests * Minor changes * Changed tests * removed unnecessary logout * Added documentation * Changed docu * Changed docu * Changed docu * Changed docu * Changed docu * Changed docu * Changed docu * Changed docu * Changed docu * Changed docu * Removed trailing spaces * Added newline at end of file * code climate fixes * code climate fixes * code climate fixes * Minor changes * Minor changes * Minor changes * Changed tests * Test changes * Splitted Cloud Foundry functions into two classes * Removed two steps from whtielistScriptReference * removed atcrunConfig alias * issue fixes * Changed docu * Changed docu * Changed docu * Removed trailing spaced from docu * Changed docu * Go generator run * Issue fixes * Remove unnecessary imports Co-authored-by: Christopher Fenner <26137398+CCFenner@users.noreply.github.com> * Update whitelistScript Co-authored-by: Christopher Fenner <26137398+CCFenner@users.noreply.github.com> * Adding piperutils for writing xml file * Persisting ATC Results with piperutils * Set failonMissingReports to true * Refactoring for CodeClimate * Changed result file name * Changed credentials aliases * changing secret name * Removing trailing spaces * Added secret name and alias to docu Co-authored-by: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com> Co-authored-by: Christopher Fenner <26137398+CCFenner@users.noreply.github.com>
This commit is contained in:
parent
4105f81f71
commit
ac732b3065
360
cmd/abapEnvironmentRunATCCheck.go
Normal file
360
cmd/abapEnvironmentRunATCCheck.go
Normal file
@ -0,0 +1,360 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/cloudfoundry"
|
||||
"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/piperutils"
|
||||
"github.com/SAP/jenkins-library/pkg/telemetry"
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func abapEnvironmentRunATCCheck(config abapEnvironmentRunATCCheckOptions, telemetryData *telemetry.CustomData) {
|
||||
|
||||
var c = command.Command{}
|
||||
|
||||
var err error
|
||||
|
||||
c.Stdout(log.Entry().Writer())
|
||||
c.Stderr(log.Entry().Writer())
|
||||
|
||||
client := piperhttp.Client{}
|
||||
cookieJar, _ := cookiejar.New(nil)
|
||||
clientOptions := piperhttp.ClientOptions{
|
||||
CookieJar: cookieJar,
|
||||
}
|
||||
client.SetOptions(clientOptions)
|
||||
|
||||
var details connectionDetailsHTTP
|
||||
var abapEndpoint string
|
||||
//If Host flag is empty read ABAP endpoint from Service Key instead. Otherwise take ABAP system endpoint from config instead
|
||||
if err == nil {
|
||||
details, err = checkHost(config, details)
|
||||
}
|
||||
var resp *http.Response
|
||||
//Fetch Xcrsf-Token
|
||||
if err == nil {
|
||||
abapEndpoint = details.URL
|
||||
credentialsOptions := piperhttp.ClientOptions{
|
||||
Username: details.User,
|
||||
Password: details.Password,
|
||||
CookieJar: cookieJar,
|
||||
}
|
||||
client.SetOptions(credentialsOptions)
|
||||
details.XCsrfToken, err = fetchXcsrfToken("GET", details, nil, &client)
|
||||
}
|
||||
if err == nil {
|
||||
resp, err = triggerATCrun(config, details, &client, abapEndpoint)
|
||||
}
|
||||
if err == nil {
|
||||
err = handleATCresults(resp, details, &client, abapEndpoint)
|
||||
}
|
||||
if err != nil {
|
||||
log.Entry().WithError(err).Fatal("step execution failed")
|
||||
}
|
||||
|
||||
log.Entry().Info("ATC run completed succesfully. The respective run results are listed above.")
|
||||
}
|
||||
|
||||
func handleATCresults(resp *http.Response, details connectionDetailsHTTP, client piperhttp.Sender, abapEndpoint string) error {
|
||||
var err error
|
||||
location := resp.Header.Get("Location")
|
||||
details.URL = abapEndpoint + location
|
||||
location, err = pollATCRun(details, nil, client)
|
||||
if err == nil {
|
||||
details.URL = abapEndpoint + location
|
||||
resp, err = getResultATCRun("GET", details, nil, client)
|
||||
}
|
||||
//Parse response
|
||||
var body []byte
|
||||
if err == nil {
|
||||
body, err = ioutil.ReadAll(resp.Body)
|
||||
}
|
||||
if err == nil {
|
||||
defer resp.Body.Close()
|
||||
err = parseATCResult(body)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("Handling ATC result failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func triggerATCrun(config abapEnvironmentRunATCCheckOptions, details connectionDetailsHTTP, client piperhttp.Sender, abapEndpoint string) (*http.Response, error) {
|
||||
var atcConfigyamlFile []byte
|
||||
filelocation, err := filepath.Glob(config.AtcConfig)
|
||||
//Parse YAML ATC run configuration as body for ATC run trigger
|
||||
if err == nil {
|
||||
filename, _ := filepath.Abs(filelocation[0])
|
||||
atcConfigyamlFile, err = ioutil.ReadFile(filename)
|
||||
}
|
||||
var ATCConfig ATCconfig
|
||||
if err == nil {
|
||||
var result []byte
|
||||
result, err = yaml.YAMLToJSON(atcConfigyamlFile)
|
||||
json.Unmarshal(result, &ATCConfig)
|
||||
}
|
||||
var packageString string
|
||||
var softwareComponentString string
|
||||
if err == nil {
|
||||
packageString, softwareComponentString, err = buildATCCheckBody(ATCConfig)
|
||||
}
|
||||
|
||||
//Trigger ATC run
|
||||
var resp *http.Response
|
||||
var bodyString = `<?xml version="1.0" encoding="UTF-8"?><atc:runparameters xmlns:atc="http://www.sap.com/adt/atc" xmlns:obj="http://www.sap.com/adt/objectset"><obj:objectSet>` + softwareComponentString + packageString + `</obj:objectSet></atc:runparameters>`
|
||||
var body = []byte(bodyString)
|
||||
if err == nil {
|
||||
details.URL = abapEndpoint + "/sap/bc/adt/api/atc/runs?clientWait=false"
|
||||
resp, err = runATC("POST", details, body, client)
|
||||
}
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("Triggering ATC result failed: %w", err)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func buildATCCheckBody(ATCConfig ATCconfig) (string, string, error) {
|
||||
if len(ATCConfig.Objects.Package) == 0 && len(ATCConfig.Objects.SoftwareComponent) == 0 {
|
||||
return "", "", fmt.Errorf("Error while parsing ATC run config. Please provide the packages and/or the software components to be checked! %w", errors.New("No Package or Software Component specified. Please provide either one or both of them"))
|
||||
}
|
||||
|
||||
var packageString string
|
||||
var softwareComponentString string
|
||||
|
||||
//Build Package XML body
|
||||
if len(ATCConfig.Objects.Package) != 0 {
|
||||
packageString += "<obj:packages>"
|
||||
for _, s := range ATCConfig.Objects.Package {
|
||||
packageString += `<obj:package value="` + s.Name + `" includeSubpackages="` + strconv.FormatBool(s.IncludeSubpackages) + `"/>`
|
||||
}
|
||||
packageString += "</obj:packages>"
|
||||
}
|
||||
|
||||
//Build SC XML body
|
||||
if len(ATCConfig.Objects.SoftwareComponent) != 0 {
|
||||
softwareComponentString += "<obj:softwarecomponents>"
|
||||
for _, s := range ATCConfig.Objects.SoftwareComponent {
|
||||
softwareComponentString += `<obj:softwarecomponent value="` + s.Name + `"/>`
|
||||
}
|
||||
softwareComponentString += "</obj:softwarecomponents>"
|
||||
}
|
||||
return packageString, softwareComponentString, nil
|
||||
}
|
||||
|
||||
func parseATCResult(body []byte) error {
|
||||
if len(body) == 0 {
|
||||
return fmt.Errorf("Parsing ATC result failed: %w", errors.New("Body is empty, can't parse empty body"))
|
||||
}
|
||||
parsedXML := new(Result)
|
||||
xml.Unmarshal([]byte(body), &parsedXML)
|
||||
err := ioutil.WriteFile("ATCResults.xml", body, 0644)
|
||||
if err == nil {
|
||||
var reports []piperutils.Path
|
||||
reports = append(reports, piperutils.Path{Target: "ATCResults.xml", Name: "ATC Results", Mandatory: true})
|
||||
piperutils.PersistReportsAndLinks("abapEnvironmentRunATCCheck", "", reports, nil)
|
||||
for _, s := range parsedXML.Files {
|
||||
for _, t := range s.ATCErrors {
|
||||
log.Entry().Error("Error in file " + s.Key + ": " + t.Key)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("Writing results to XML file failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runATC(requestType string, details connectionDetailsHTTP, body []byte, client piperhttp.Sender) (*http.Response, error) {
|
||||
|
||||
log.Entry().WithField("ABAP endpoint: ", details.URL).Info("Triggering ATC run")
|
||||
|
||||
header := make(map[string][]string)
|
||||
header["X-Csrf-Token"] = []string{details.XCsrfToken}
|
||||
header["Content-Type"] = []string{"application/vnd.sap.atc.run.parameters.v1+xml; charset=utf-8;"}
|
||||
|
||||
req, err := client.SendRequest(requestType, details.URL, bytes.NewBuffer(body), header, nil)
|
||||
if err != nil {
|
||||
return req, fmt.Errorf("Triggering ATC run failed: %w", err)
|
||||
}
|
||||
defer req.Body.Close()
|
||||
return req, err
|
||||
}
|
||||
|
||||
func fetchXcsrfToken(requestType string, details connectionDetailsHTTP, body []byte, client piperhttp.Sender) (string, error) {
|
||||
|
||||
log.Entry().WithField("ABAP Endpoint: ", details.URL).Info("Fetching Xcrsf-Token")
|
||||
|
||||
details.URL += "/sap/bc/adt/api/atc/runs/00000000000000000000000000000000"
|
||||
details.XCsrfToken = "fetch"
|
||||
header := make(map[string][]string)
|
||||
header["X-Csrf-Token"] = []string{details.XCsrfToken}
|
||||
header["Accept"] = []string{"application/vnd.sap.atc.run.v1+xml"}
|
||||
req, err := client.SendRequest(requestType, details.URL, bytes.NewBuffer(body), header, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Fetching Xcsrf-Token failed: %w", err)
|
||||
}
|
||||
defer req.Body.Close()
|
||||
token := req.Header.Get("X-Csrf-Token")
|
||||
return token, err
|
||||
}
|
||||
|
||||
func checkHost(config abapEnvironmentRunATCCheckOptions, details connectionDetailsHTTP) (connectionDetailsHTTP, error) {
|
||||
|
||||
var err error
|
||||
|
||||
if config.Host == "" {
|
||||
cfconfig := cloudfoundry.ServiceKeyOptions{
|
||||
CfAPIEndpoint: config.CfAPIEndpoint,
|
||||
CfOrg: config.CfOrg,
|
||||
CfSpace: config.CfSpace,
|
||||
Username: config.Username,
|
||||
Password: config.Password,
|
||||
CfServiceInstance: config.CfServiceInstance,
|
||||
CfServiceKey: config.CfServiceKeyName,
|
||||
}
|
||||
if cfconfig.CfServiceInstance == "" || cfconfig.CfOrg == "" || cfconfig.CfAPIEndpoint == "" || cfconfig.CfSpace == "" || cfconfig.CfServiceKey == "" {
|
||||
return details, errors.New("Parameters missing. Please provide EITHER the Host of the ABAP server OR the Cloud Foundry ApiEndpoint, Organization, Space, Service Instance and a corresponding Service Key for the Communication Scenario SAP_COM_0510")
|
||||
}
|
||||
var abapServiceKey cloudfoundry.ServiceKey
|
||||
abapServiceKey, err = cloudfoundry.ReadServiceKeyAbapEnvironment(cfconfig, true)
|
||||
if err != nil {
|
||||
return details, fmt.Errorf("Reading Service Key failed: %w", err)
|
||||
}
|
||||
details.User = abapServiceKey.Abap.Username
|
||||
details.Password = abapServiceKey.Abap.Password
|
||||
details.URL = abapServiceKey.URL
|
||||
return details, err
|
||||
}
|
||||
details.User = config.Username
|
||||
details.Password = config.Password
|
||||
details.URL = config.Host
|
||||
return details, err
|
||||
}
|
||||
|
||||
func pollATCRun(details connectionDetailsHTTP, body []byte, client piperhttp.Sender) (string, error) {
|
||||
|
||||
log.Entry().WithField("ABAP endpoint", details.URL).Info("Polling ATC run status")
|
||||
|
||||
for {
|
||||
resp, err := getHTTPResponseATCRun("GET", details, nil, client)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Getting HTTP response failed: %w", err)
|
||||
}
|
||||
bodyText, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Reading response body failed: %w", err)
|
||||
}
|
||||
|
||||
x := new(Run)
|
||||
xml.Unmarshal(bodyText, &x)
|
||||
log.Entry().WithField("StatusCode", resp.StatusCode).Info("Status: " + x.Status)
|
||||
|
||||
if x.Status == "Not Created" {
|
||||
return "", err
|
||||
}
|
||||
if x.Status == "Completed" {
|
||||
return x.Link[0].Key, err
|
||||
}
|
||||
if x.Status == "" {
|
||||
return "", fmt.Errorf("Could not get any response from ATC poll: %w", errors.New("Status from ATC run is empty. Either it's not an ABAP system or ATC run hasn't started"))
|
||||
}
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func getHTTPResponseATCRun(requestType string, details connectionDetailsHTTP, body []byte, client piperhttp.Sender) (*http.Response, error) {
|
||||
|
||||
log.Entry().WithField("ABAP Endpoint: ", details.URL).Info("Polling ATC run status")
|
||||
|
||||
header := make(map[string][]string)
|
||||
header["Accept"] = []string{"application/vnd.sap.atc.run.v1+xml"}
|
||||
|
||||
req, err := client.SendRequest(requestType, details.URL, bytes.NewBuffer(body), header, nil)
|
||||
if err != nil {
|
||||
return req, fmt.Errorf("Getting ATC run status failed: %w", err)
|
||||
}
|
||||
return req, err
|
||||
}
|
||||
|
||||
func getResultATCRun(requestType string, details connectionDetailsHTTP, body []byte, client piperhttp.Sender) (*http.Response, error) {
|
||||
|
||||
log.Entry().WithField("ABAP Endpoint: ", details.URL).Info("Getting ATC results")
|
||||
|
||||
header := make(map[string][]string)
|
||||
header["x-csrf-token"] = []string{details.XCsrfToken}
|
||||
header["Accept"] = []string{"application/vnd.sap.atc.checkstyle.v1+xml"}
|
||||
|
||||
req, err := client.SendRequest(requestType, details.URL, bytes.NewBuffer(body), header, nil)
|
||||
if err != nil {
|
||||
return req, fmt.Errorf("Getting ATC run results failed: %w", err)
|
||||
}
|
||||
return req, err
|
||||
}
|
||||
|
||||
//ATCconfig object for parsing yaml config of software components and packages
|
||||
type ATCconfig struct {
|
||||
Objects ATCObjects `json:"atcobjects"`
|
||||
}
|
||||
|
||||
//ATCObjects in form of packages and software components to be checked
|
||||
type ATCObjects struct {
|
||||
Package []Package `json:"package"`
|
||||
SoftwareComponent []SoftwareComponent `json:"softwarecomponent"`
|
||||
}
|
||||
|
||||
//Package for ATC run to be checked
|
||||
type Package struct {
|
||||
Name string `json:"name"`
|
||||
IncludeSubpackages bool `json:"includesubpackage"`
|
||||
}
|
||||
|
||||
//SoftwareComponent for ATC run to be checked
|
||||
type SoftwareComponent struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
//Run Object for parsing XML
|
||||
type Run struct {
|
||||
XMLName xml.Name `xml:"run"`
|
||||
Status string `xml:"status,attr"`
|
||||
Link []Link `xml:"link"`
|
||||
}
|
||||
|
||||
//Link of XML object
|
||||
type Link struct {
|
||||
Key string `xml:"href,attr"`
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
//Result from ATC check for all files that were checked
|
||||
type Result struct {
|
||||
XMLName xml.Name `xml:"checkstyle"`
|
||||
Files []File `xml:"file"`
|
||||
}
|
||||
|
||||
//File that contains ATC check with error for checked file
|
||||
type File struct {
|
||||
Key string `xml:"name,attr"`
|
||||
Value string `xml:",chardata"`
|
||||
ATCErrors []ATCError `xml:"error"`
|
||||
}
|
||||
|
||||
//ATCError with message
|
||||
type ATCError struct {
|
||||
Key string `xml:"message,attr"`
|
||||
Value string `xml:",chardata"`
|
||||
}
|
183
cmd/abapEnvironmentRunATCCheck_generated.go
Normal file
183
cmd/abapEnvironmentRunATCCheck_generated.go
Normal file
@ -0,0 +1,183 @@
|
||||
// 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 abapEnvironmentRunATCCheckOptions struct {
|
||||
AtcConfig string `json:"atcConfig,omitempty"`
|
||||
CfAPIEndpoint string `json:"cfApiEndpoint,omitempty"`
|
||||
CfOrg string `json:"cfOrg,omitempty"`
|
||||
CfServiceInstance string `json:"cfServiceInstance,omitempty"`
|
||||
CfServiceKeyName string `json:"cfServiceKeyName,omitempty"`
|
||||
CfSpace string `json:"cfSpace,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Host string `json:"host,omitempty"`
|
||||
}
|
||||
|
||||
// AbapEnvironmentRunATCCheckCommand Runs an ATC Check
|
||||
func AbapEnvironmentRunATCCheckCommand() *cobra.Command {
|
||||
const STEP_NAME = "abapEnvironmentRunATCCheck"
|
||||
|
||||
metadata := abapEnvironmentRunATCCheckMetadata()
|
||||
var stepConfig abapEnvironmentRunATCCheckOptions
|
||||
var startTime time.Time
|
||||
|
||||
var createAbapEnvironmentRunATCCheckCmd = &cobra.Command{
|
||||
Use: STEP_NAME,
|
||||
Short: "Runs an ATC Check",
|
||||
Long: `Run ATC Check`,
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
startTime = time.Now()
|
||||
log.SetStepName(STEP_NAME)
|
||||
log.SetVerbose(GeneralConfig.Verbose)
|
||||
|
||||
path, _ := os.Getwd()
|
||||
fatalHook := &log.FatalHook{CorrelationID: GeneralConfig.CorrelationID, Path: path}
|
||||
log.RegisterHook(fatalHook)
|
||||
|
||||
err := PrepareConfig(cmd, &metadata, STEP_NAME, &stepConfig, config.OpenPiperFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(GeneralConfig.HookConfig.SentryConfig.Dsn) > 0 {
|
||||
sentryHook := log.NewSentryHook(GeneralConfig.HookConfig.SentryConfig.Dsn, GeneralConfig.CorrelationID)
|
||||
log.RegisterHook(&sentryHook)
|
||||
}
|
||||
|
||||
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, STEP_NAME)
|
||||
abapEnvironmentRunATCCheck(stepConfig, &telemetryData)
|
||||
telemetryData.ErrorCode = "0"
|
||||
},
|
||||
}
|
||||
|
||||
addAbapEnvironmentRunATCCheckFlags(createAbapEnvironmentRunATCCheckCmd, &stepConfig)
|
||||
return createAbapEnvironmentRunATCCheckCmd
|
||||
}
|
||||
|
||||
func addAbapEnvironmentRunATCCheckFlags(cmd *cobra.Command, stepConfig *abapEnvironmentRunATCCheckOptions) {
|
||||
cmd.Flags().StringVar(&stepConfig.AtcConfig, "atcConfig", os.Getenv("PIPER_atcConfig"), "Path to a YAML configuration file for Packages and/or Software Components to be checked during ATC run")
|
||||
cmd.Flags().StringVar(&stepConfig.CfAPIEndpoint, "cfApiEndpoint", os.Getenv("PIPER_cfApiEndpoint"), "Cloud Foundry API endpoint")
|
||||
cmd.Flags().StringVar(&stepConfig.CfOrg, "cfOrg", os.Getenv("PIPER_cfOrg"), "CF org")
|
||||
cmd.Flags().StringVar(&stepConfig.CfServiceInstance, "cfServiceInstance", os.Getenv("PIPER_cfServiceInstance"), "Parameter of ServiceInstance Name to delete CloudFoundry Service")
|
||||
cmd.Flags().StringVar(&stepConfig.CfServiceKeyName, "cfServiceKeyName", os.Getenv("PIPER_cfServiceKeyName"), "Parameter of CloudFoundry Service Key to be created")
|
||||
cmd.Flags().StringVar(&stepConfig.CfSpace, "cfSpace", os.Getenv("PIPER_cfSpace"), "CF Space")
|
||||
cmd.Flags().StringVar(&stepConfig.Username, "username", os.Getenv("PIPER_username"), "User or E-Mail for CF")
|
||||
cmd.Flags().StringVar(&stepConfig.Password, "password", os.Getenv("PIPER_password"), "User Password for CF User")
|
||||
cmd.Flags().StringVar(&stepConfig.Host, "host", os.Getenv("PIPER_host"), "Specifies the host address of the SAP Cloud Platform ABAP Environment system")
|
||||
|
||||
cmd.MarkFlagRequired("atcConfig")
|
||||
cmd.MarkFlagRequired("username")
|
||||
cmd.MarkFlagRequired("password")
|
||||
}
|
||||
|
||||
// retrieve step metadata
|
||||
func abapEnvironmentRunATCCheckMetadata() config.StepData {
|
||||
var theMetaData = config.StepData{
|
||||
Metadata: config.StepMetadata{
|
||||
Name: "abapEnvironmentRunATCCheck",
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{
|
||||
Name: "atcConfig",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "cfApiEndpoint",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{{Name: "cloudFoundry/apiEndpoint"}},
|
||||
},
|
||||
{
|
||||
Name: "cfOrg",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{{Name: "cloudFoundry/org"}},
|
||||
},
|
||||
{
|
||||
Name: "cfServiceInstance",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{{Name: "cloudFoundry/serviceInstance"}},
|
||||
},
|
||||
{
|
||||
Name: "cfServiceKeyName",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{{Name: "cloudFoundry/serviceKeyName"}},
|
||||
},
|
||||
{
|
||||
Name: "cfSpace",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{{Name: "cloudFoundry/space"}},
|
||||
},
|
||||
{
|
||||
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: "host",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return theMetaData
|
||||
}
|
16
cmd/abapEnvironmentRunATCCheck_generated_test.go
Normal file
16
cmd/abapEnvironmentRunATCCheck_generated_test.go
Normal file
@ -0,0 +1,16 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAbapEnvironmentRunATCCheckCommand(t *testing.T) {
|
||||
|
||||
testCmd := AbapEnvironmentRunATCCheckCommand()
|
||||
|
||||
// only high level testing performed - details are tested in step generation procudure
|
||||
assert.Equal(t, "abapEnvironmentRunATCCheck", testCmd.Use, "command name incorrect")
|
||||
|
||||
}
|
316
cmd/abapEnvironmentRunATCCheck_test.go
Normal file
316
cmd/abapEnvironmentRunATCCheck_test.go
Normal file
@ -0,0 +1,316 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHostConfig(t *testing.T) {
|
||||
t.Run("Check Host: ABAP Endpoint", func(t *testing.T) {
|
||||
config := abapEnvironmentRunATCCheckOptions{
|
||||
Username: "testUser",
|
||||
Password: "testPassword",
|
||||
Host: "https://api.endpoint.com",
|
||||
}
|
||||
var con connectionDetailsHTTP
|
||||
con, error := checkHost(config, con)
|
||||
if error == nil {
|
||||
assert.Equal(t, "testUser", con.User)
|
||||
assert.Equal(t, "testPassword", con.Password)
|
||||
assert.Equal(t, "https://api.endpoint.com", con.URL)
|
||||
assert.Equal(t, "", con.XCsrfToken)
|
||||
}
|
||||
})
|
||||
t.Run("No host/ServiceKey configuration", func(t *testing.T) {
|
||||
//Testing without CfOrg parameter
|
||||
config := abapEnvironmentRunATCCheckOptions{
|
||||
CfAPIEndpoint: "https://api.endpoint.com",
|
||||
CfSpace: "testSpace",
|
||||
CfServiceInstance: "testInstance",
|
||||
CfServiceKeyName: "testServiceKey",
|
||||
Username: "testUser",
|
||||
Password: "testPassword",
|
||||
}
|
||||
var con connectionDetailsHTTP
|
||||
con, err := checkHost(config, con)
|
||||
assert.EqualError(t, err, "Parameters missing. Please provide EITHER the Host of the ABAP server OR the Cloud Foundry ApiEndpoint, Organization, Space, Service Instance and a corresponding Service Key for the Communication Scenario SAP_COM_0510")
|
||||
//Testing without ABAP Host
|
||||
config = abapEnvironmentRunATCCheckOptions{
|
||||
Username: "testUser",
|
||||
Password: "testPassword",
|
||||
}
|
||||
con, err = checkHost(config, con)
|
||||
assert.EqualError(t, err, "Parameters missing. Please provide EITHER the Host of the ABAP server OR the Cloud Foundry ApiEndpoint, Organization, Space, Service Instance and a corresponding Service Key for the Communication Scenario SAP_COM_0510")
|
||||
})
|
||||
t.Run("Check Host: CF Service Key", func(t *testing.T) {
|
||||
config := abapEnvironmentRunATCCheckOptions{
|
||||
CfAPIEndpoint: "https://api.endpoint.com",
|
||||
CfSpace: "testSpace",
|
||||
CfOrg: "Test",
|
||||
CfServiceInstance: "testInstance",
|
||||
CfServiceKeyName: "testServiceKey",
|
||||
Username: "testUser",
|
||||
Password: "testPassword",
|
||||
}
|
||||
var con connectionDetailsHTTP
|
||||
con, error := checkHost(config, con)
|
||||
if error == nil {
|
||||
assert.Equal(t, "", con.User)
|
||||
assert.Equal(t, "", con.Password)
|
||||
assert.Equal(t, "", con.URL)
|
||||
assert.Equal(t, "", con.XCsrfToken)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestATCTrigger(t *testing.T) {
|
||||
t.Run("Trigger ATC run test", func(t *testing.T) {
|
||||
tokenExpected := "myToken"
|
||||
|
||||
client := &clientMock{
|
||||
Body: `ATC trigger test`,
|
||||
Token: tokenExpected,
|
||||
}
|
||||
|
||||
con := connectionDetailsHTTP{
|
||||
User: "Test",
|
||||
Password: "Test",
|
||||
URL: "https://api.endpoint.com/Entity/",
|
||||
}
|
||||
resp, error := runATC("GET", con, []byte(client.Body), client)
|
||||
if error == nil {
|
||||
assert.Equal(t, tokenExpected, resp.Header["X-Csrf-Token"][0])
|
||||
assert.Equal(t, int64(0), resp.ContentLength)
|
||||
assert.Equal(t, []string([]string(nil)), resp.Header["Location"])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestFetchXcsrfToken(t *testing.T) {
|
||||
t.Run("FetchXcsrfToken Test", func(t *testing.T) {
|
||||
tokenExpected := "myToken"
|
||||
|
||||
client := &clientMock{
|
||||
Body: `Xcsrf Token test`,
|
||||
Token: tokenExpected,
|
||||
}
|
||||
|
||||
con := connectionDetailsHTTP{
|
||||
User: "Test",
|
||||
Password: "Test",
|
||||
URL: "https://api.endpoint.com/Entity/",
|
||||
}
|
||||
token, error := fetchXcsrfToken("GET", con, []byte(client.Body), client)
|
||||
if error == nil {
|
||||
assert.Equal(t, tokenExpected, token)
|
||||
}
|
||||
})
|
||||
t.Run("failure case: fetch token", func(t *testing.T) {
|
||||
tokenExpected := ""
|
||||
|
||||
client := &clientMock{
|
||||
Body: `Xcsrf Token test`,
|
||||
Token: "",
|
||||
}
|
||||
|
||||
con := connectionDetailsHTTP{
|
||||
User: "Test",
|
||||
Password: "Test",
|
||||
URL: "https://api.endpoint.com/Entity/",
|
||||
}
|
||||
token, error := fetchXcsrfToken("GET", con, []byte(client.Body), client)
|
||||
if error == nil {
|
||||
assert.Equal(t, tokenExpected, token)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestPollATCRun(t *testing.T) {
|
||||
t.Run("ATC run Poll Test", func(t *testing.T) {
|
||||
tokenExpected := "myToken"
|
||||
|
||||
client := &clientMock{
|
||||
Body: `ATC Poll test`,
|
||||
Token: tokenExpected,
|
||||
}
|
||||
|
||||
con := connectionDetailsHTTP{
|
||||
User: "Test",
|
||||
Password: "Test",
|
||||
URL: "https://api.endpoint.com/Entity/",
|
||||
}
|
||||
resp, err := pollATCRun(con, []byte(client.Body), client)
|
||||
if err != nil {
|
||||
assert.Equal(t, "", resp)
|
||||
assert.EqualError(t, err, "Could not get any response from ATC poll: Status from ATC run is empty. Either it's not an ABAP system or ATC run hasn't started")
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetHTTPResponseATCRun(t *testing.T) {
|
||||
t.Run("Get HTTP Response from ATC run Test", func(t *testing.T) {
|
||||
client := &clientMock{
|
||||
Body: `HTTP response test`,
|
||||
}
|
||||
|
||||
con := connectionDetailsHTTP{
|
||||
User: "Test",
|
||||
Password: "Test",
|
||||
URL: "https://api.endpoint.com/Entity/",
|
||||
}
|
||||
resp, err := getHTTPResponseATCRun("GET", con, []byte(client.Body), client)
|
||||
defer resp.Body.Close()
|
||||
if err == nil {
|
||||
assert.Equal(t, int64(0), resp.ContentLength)
|
||||
assert.Equal(t, []string([]string(nil)), resp.Header["X-Crsf-Token"])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetResultATCRun(t *testing.T) {
|
||||
t.Run("Get HTTP Response from ATC run Test", func(t *testing.T) {
|
||||
client := &clientMock{
|
||||
BodyList: []string{
|
||||
`ATC result body`,
|
||||
},
|
||||
}
|
||||
|
||||
con := connectionDetailsHTTP{
|
||||
User: "Test",
|
||||
Password: "Test",
|
||||
URL: "https://api.endpoint.com/Entity/",
|
||||
}
|
||||
resp, err := getResultATCRun("GET", con, []byte(client.Body), client)
|
||||
defer resp.Body.Close()
|
||||
if err == nil {
|
||||
assert.Equal(t, int64(0), resp.ContentLength)
|
||||
assert.Equal(t, []string([]string(nil)), resp.Header["X-Crsf-Token"])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseATCResult(t *testing.T) {
|
||||
t.Run("succes case: test parsing example XML result", func(t *testing.T) {
|
||||
bodyString := `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<checkstyle>
|
||||
<file name="testFile">
|
||||
<error message="testMessage">
|
||||
</error>
|
||||
<error message="testMessage2">
|
||||
</error>
|
||||
</file>
|
||||
<file name="testFile2">
|
||||
<error message="testMessage3">
|
||||
</error>
|
||||
</file>
|
||||
</checkstyle>`
|
||||
body := []byte(bodyString)
|
||||
|
||||
err := parseATCResult(body)
|
||||
assert.Equal(t, nil, err)
|
||||
})
|
||||
t.Run("failure case: parsing empty xml", func(t *testing.T) {
|
||||
var bodyString string
|
||||
body := []byte(bodyString)
|
||||
|
||||
err := parseATCResult(body)
|
||||
assert.EqualError(t, err, "Parsing ATC result failed: Body is empty, can't parse empty body")
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildATCCheckBody(t *testing.T) {
|
||||
t.Run("Test build body with no software component and package", func(t *testing.T) {
|
||||
expectedpackagestring := ""
|
||||
expectedsoftwarecomponentstring := ""
|
||||
|
||||
var err error
|
||||
var config ATCconfig
|
||||
var packageString, softwarecomponentString string
|
||||
|
||||
packageString, softwarecomponentString, err = buildATCCheckBody(config)
|
||||
|
||||
assert.Equal(t, expectedpackagestring, packageString)
|
||||
assert.Equal(t, expectedsoftwarecomponentstring, softwarecomponentString)
|
||||
assert.EqualError(t, err, "Error while parsing ATC run config. Please provide the packages and/or the software components to be checked! No Package or Software Component specified. Please provide either one or both of them")
|
||||
})
|
||||
t.Run("success case: Test build body with example yaml config", func(t *testing.T) {
|
||||
expectedpackagestring := "<obj:packages><obj:package value=\"testPackage\" includeSubpackages=\"true\"/><obj:package value=\"testPackage2\" includeSubpackages=\"false\"/></obj:packages>"
|
||||
expectedsoftwarecomponentstring := "<obj:softwarecomponents><obj:softwarecomponent value=\"testSoftwareComponent\"/><obj:softwarecomponent value=\"testSoftwareComponent2\"/></obj:softwarecomponents>"
|
||||
|
||||
var err error
|
||||
var config ATCconfig
|
||||
|
||||
config = ATCconfig{
|
||||
ATCObjects{
|
||||
Package: []Package{
|
||||
Package{Name: "testPackage", IncludeSubpackages: true},
|
||||
Package{Name: "testPackage2", IncludeSubpackages: false},
|
||||
},
|
||||
SoftwareComponent: []SoftwareComponent{
|
||||
SoftwareComponent{Name: "testSoftwareComponent"},
|
||||
SoftwareComponent{Name: "testSoftwareComponent2"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var packageString, softwarecomponentString string
|
||||
|
||||
packageString, softwarecomponentString, err = buildATCCheckBody(config)
|
||||
|
||||
assert.Equal(t, expectedpackagestring, packageString)
|
||||
assert.Equal(t, expectedsoftwarecomponentstring, softwarecomponentString)
|
||||
assert.Equal(t, nil, err)
|
||||
})
|
||||
t.Run("failure case: Test build body with example yaml config with only packages and no software components", func(t *testing.T) {
|
||||
expectedpackagestring := `<obj:packages><obj:package value="testPackage" includeSubpackages="true"/><obj:package value="testPackage2" includeSubpackages="false"/></obj:packages>`
|
||||
expectedsoftwarecomponentstring := ""
|
||||
|
||||
var err error
|
||||
var config ATCconfig
|
||||
|
||||
config = ATCconfig{
|
||||
ATCObjects{
|
||||
Package: []Package{
|
||||
Package{Name: "testPackage", IncludeSubpackages: true},
|
||||
Package{Name: "testPackage2", IncludeSubpackages: false},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var packageString, softwarecomponentString string
|
||||
|
||||
packageString, softwarecomponentString, err = buildATCCheckBody(config)
|
||||
|
||||
assert.Equal(t, expectedpackagestring, packageString)
|
||||
assert.Equal(t, expectedsoftwarecomponentstring, softwarecomponentString)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
})
|
||||
t.Run("success case: Test build body with example yaml config with no packages and only software components", func(t *testing.T) {
|
||||
expectedpackagestring := ""
|
||||
expectedsoftwarecomponentstring := `<obj:softwarecomponents><obj:softwarecomponent value="testSoftwareComponent"/><obj:softwarecomponent value="testSoftwareComponent2"/></obj:softwarecomponents>`
|
||||
|
||||
var err error
|
||||
var config ATCconfig
|
||||
|
||||
config = ATCconfig{
|
||||
ATCObjects{
|
||||
SoftwareComponent: []SoftwareComponent{
|
||||
SoftwareComponent{Name: "testSoftwareComponent"},
|
||||
SoftwareComponent{Name: "testSoftwareComponent2"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var packageString, softwarecomponentString string
|
||||
|
||||
packageString, softwarecomponentString, err = buildATCCheckBody(config)
|
||||
|
||||
assert.Equal(t, expectedpackagestring, packageString)
|
||||
assert.Equal(t, expectedsoftwarecomponentstring, softwarecomponentString)
|
||||
assert.Equal(t, nil, err)
|
||||
})
|
||||
}
|
@ -64,7 +64,6 @@ func runCloudFoundryCreateServiceKey(config *cloudFoundryCreateServiceKeyOptions
|
||||
} else {
|
||||
cfCreateServiceKeyScript = []string{"create-service-key", config.CfServiceInstance, config.CfServiceKeyName, "-c", config.CfServiceKeyConfig}
|
||||
}
|
||||
|
||||
err := c.RunExecutable("cf", cfCreateServiceKeyScript...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to Create Service Key: %w", err)
|
||||
|
@ -79,6 +79,7 @@ func Execute() {
|
||||
rootCmd.AddCommand(MavenBuildCommand())
|
||||
rootCmd.AddCommand(MavenExecuteStaticCodeChecksCommand())
|
||||
rootCmd.AddCommand(NexusUploadCommand())
|
||||
rootCmd.AddCommand(AbapEnvironmentRunATCCheckCommand())
|
||||
rootCmd.AddCommand(NpmExecuteScriptsCommand())
|
||||
rootCmd.AddCommand(GctsCreateRepositoryCommand())
|
||||
rootCmd.AddCommand(MalwareExecuteScanCommand())
|
||||
|
106
documentation/docs/steps/abapEnvironmentRunATCCheck.md
Normal file
106
documentation/docs/steps/abapEnvironmentRunATCCheck.md
Normal file
@ -0,0 +1,106 @@
|
||||
# ${docGenStepName}
|
||||
|
||||
## ${docGenDescription}
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* This step is for triggering an ATC run on an ABAP system.
|
||||
* You can either provide the ABAP endpoint config to directly trigger ann ATC run on the ABAP system or optionally provide the Cloud Foundry parameters with your credentials to read a Service Key of a SAP Cloud Platform ABAP Environment instance in Cloud Foundry that contains all the details to trigger an ATC run.
|
||||
* Regardless if you chose an ABAP endpoint directly or reading a Cloud Foundry Service Key you have to provide the configuration of the packages and software components you want to be checked in an ATC run in a .yml or .yaml file. This file must be stored in the same folder as the Jenkinsfile defining the pipeline.
|
||||
|
||||
Examples will be listed below.
|
||||
|
||||
## ${docGenParameters}
|
||||
|
||||
## ${docGenConfiguration}
|
||||
|
||||
## ${docJenkinsPluginDependencies}
|
||||
|
||||
## Examples
|
||||
|
||||
* ### ATC run via Cloud Foundry Service Key example in Jenkinsfile
|
||||
|
||||
The following example triggers an ATC run via reading the Service Key of an ABAP instance in Cloud Foundry.
|
||||
|
||||
You can store the credentials in Jenkins and use the cfCredentialsId parameter to authenticate to Cloud Foundry.
|
||||
The username and password to authenticate to ABAP system will then be read from the Cloud Foundry Service Key that is bound to the ABAP instance.
|
||||
|
||||
This can be done accordingly:
|
||||
|
||||
```groovy
|
||||
abapEnvironmentRunATCCheck(
|
||||
cfApiEndpoint : 'https://test.server.com',
|
||||
cfOrg : 'cfOrg',
|
||||
cfSpace: 'cfSpace',
|
||||
cfServiceInstance: 'myServiceInstance',
|
||||
cfSserviceKeyName: 'myServiceKey',
|
||||
cfCredentialsId: 'cfCredentialsId',
|
||||
atcConfig: 'atcconfig.yml',
|
||||
script: this,
|
||||
)
|
||||
```
|
||||
|
||||
To trigger the ATC run an ATC config file `atcconfig.yml` will be needed. Check section 'ATC config file example' for more information.
|
||||
|
||||
* ### ATC run via direct ABAP endpoint configuration in Jenkinsfile
|
||||
|
||||
This example triggers an ATC run directly on the ABAP endpoint.
|
||||
|
||||
In order to trigger the ATC run you have to pass the username and password for authentication to the ABAP endpoint via parameters as well as the ABAP endpoint/host. You can store the credentials in Jenkins and use the abapCredentialsId parameter to authenticate to the ABAP endpoint/host.
|
||||
|
||||
This must be configured as following:
|
||||
|
||||
```groovy
|
||||
abapEnvironmentRunATCCheck(
|
||||
abapCredentialsId: 'abapCredentialsId',
|
||||
host: 'https://myABAPendpoint.com',
|
||||
atcConfig: 'atcconfig.yml',
|
||||
script: this,
|
||||
)
|
||||
```
|
||||
|
||||
To trigger the ATC run an ATC config file `atcconfig.yml` will be needed. Check section 'ATC config file example' for more information.
|
||||
|
||||
* ### ATC config file example
|
||||
|
||||
The following section contains an example of an `atcconfig.yml` file.
|
||||
This file must be stored in the same Git folder where the `Jenkinsfile` is stored to run the pipeline. This folder must be taken as a SCM in the Jenkins pipeline to run the pipeline.
|
||||
|
||||
You can specify a list of packages and/or software components to be checked. This must be in the same format as below example for a `atcconfig.yml` file.
|
||||
For each package that has to be checked you can configure if you want the subpackages to be included in checks or not.
|
||||
Please note that if you chose to provide both packages and software components to be checked with the `atcconfig.yml` file, the set of packages and the set of software components will be combinend by the API using a logical AND operation.
|
||||
Therefore, we advise to specify either the Software Components or Packages.
|
||||
|
||||
See below example for an `atcconfig.yml` file with both packages and software components to be checked:
|
||||
|
||||
```yaml
|
||||
atcobjects:
|
||||
package:
|
||||
- name: "TestPackage"
|
||||
includesubpackage: false
|
||||
- name: "TestPackage2"
|
||||
includesubpackage: true
|
||||
softwarecomponent:
|
||||
- name: "TestComponent"
|
||||
- name: "TestComponent2"
|
||||
```
|
||||
|
||||
The following example of an `atcconfig.yml` file that only contains packages to be checked:
|
||||
|
||||
```yaml
|
||||
atcobjects:
|
||||
package:
|
||||
- name: "TestPackage"
|
||||
includesubpackage: false
|
||||
- name: "TestPackage2"
|
||||
includesubpackage: true
|
||||
```
|
||||
|
||||
The following example of an `atcconfig.yml` file that only contains software components to be checked:
|
||||
|
||||
```yaml
|
||||
atcobjects:
|
||||
softwarecomponent:
|
||||
- name: "TestComponent"
|
||||
- name: "TestComponent2"
|
||||
```
|
110
pkg/cloudfoundry/Authentication.go
Normal file
110
pkg/cloudfoundry/Authentication.go
Normal file
@ -0,0 +1,110 @@
|
||||
package cloudfoundry
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/command"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
)
|
||||
|
||||
var c = command.Command{}
|
||||
|
||||
//LoginCheck checks if user is logged in to Cloud Foundry.
|
||||
//If user is not logged in 'cf api' command will return string that contains 'User is not logged in' only if user is not logged in.
|
||||
//If the returned string doesn't contain the substring 'User is not logged in' we know he is logged in.
|
||||
func LoginCheck(options LoginOptions) (bool, error) {
|
||||
var err error
|
||||
|
||||
if options.CfAPIEndpoint == "" {
|
||||
return false, errors.New("Cloud Foundry API endpoint parameter missing. Please provide the Cloud Foundry Endpoint")
|
||||
}
|
||||
|
||||
//Check if logged in --> Cf api command responds with "not logged in" if positive
|
||||
var cfCheckLoginScript = []string{"api", options.CfAPIEndpoint}
|
||||
|
||||
var cfLoginBytes bytes.Buffer
|
||||
c.Stdout(&cfLoginBytes)
|
||||
|
||||
var result string
|
||||
|
||||
err = c.RunExecutable("cf", cfCheckLoginScript...)
|
||||
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Failed to check if logged in: %w", err)
|
||||
}
|
||||
|
||||
result = cfLoginBytes.String()
|
||||
log.Entry().WithField("result: ", result).Info("Login check")
|
||||
|
||||
//Logged in
|
||||
if strings.Contains(result, "Not logged in") == false {
|
||||
log.Entry().Info("Login check indicates you are already logged in to Cloud Foundry")
|
||||
return true, err
|
||||
}
|
||||
|
||||
//Not logged in
|
||||
log.Entry().Info("Login check indicates you are not yet logged in to Cloud Foundry")
|
||||
return false, err
|
||||
}
|
||||
|
||||
//Login logs user in to Cloud Foundry via cf cli.
|
||||
//Checks if user is logged in first, if not perform 'cf login' command with appropriate parameters
|
||||
func Login(options LoginOptions) error {
|
||||
|
||||
var err error
|
||||
|
||||
if options.CfAPIEndpoint == "" || options.CfOrg == "" || options.CfSpace == "" || options.Username == "" || options.Password == "" {
|
||||
return fmt.Errorf("Failed to login to Cloud Foundry: %w", errors.New("Parameters missing. Please provide the Cloud Foundry Endpoint, Org, Space, Username and Password"))
|
||||
}
|
||||
|
||||
var loggedIn bool
|
||||
|
||||
loggedIn, err = LoginCheck(options)
|
||||
|
||||
if loggedIn == true {
|
||||
return err
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
log.Entry().Info("Logging in to Cloud Foundry")
|
||||
|
||||
var cfLoginScript = []string{"login", "-a", options.CfAPIEndpoint, "-o", options.CfOrg, "-s", options.CfSpace, "-u", options.Username, "-p", options.Password}
|
||||
|
||||
log.Entry().WithField("cfAPI:", options.CfAPIEndpoint).WithField("cfOrg", options.CfOrg).WithField("space", options.CfSpace).Info("Logging into Cloud Foundry..")
|
||||
|
||||
err = c.RunExecutable("cf", cfLoginScript...)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to login to Cloud Foundry: %w", err)
|
||||
}
|
||||
log.Entry().Info("Logged in successfully to Cloud Foundry..")
|
||||
return nil
|
||||
}
|
||||
|
||||
//Logout logs User out of Cloud Foundry
|
||||
//Logout can be perforned via 'cf logout' command regardless if user is logged in or not
|
||||
func Logout() error {
|
||||
var cfLogoutScript = "logout"
|
||||
|
||||
log.Entry().Info("Logging out of Cloud Foundry")
|
||||
|
||||
err := c.RunExecutable("cf", cfLogoutScript)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to Logout of Cloud Foundry: %w", err)
|
||||
}
|
||||
log.Entry().Info("Logged out successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
//LoginOptions for logging in to CF
|
||||
type LoginOptions struct {
|
||||
CfAPIEndpoint string
|
||||
CfOrg string
|
||||
CfSpace string
|
||||
Username string
|
||||
Password string
|
||||
}
|
83
pkg/cloudfoundry/CloudFoundry_test.go
Normal file
83
pkg/cloudfoundry/CloudFoundry_test.go
Normal file
@ -0,0 +1,83 @@
|
||||
package cloudfoundry
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCloudFoundryLoginCheck(t *testing.T) {
|
||||
t.Run("CF Login check: missing parameter", func(t *testing.T) {
|
||||
cfconfig := LoginOptions{}
|
||||
loggedIn, err := LoginCheck(cfconfig)
|
||||
assert.Equal(t, false, loggedIn)
|
||||
assert.EqualError(t, err, "Cloud Foundry API endpoint parameter missing. Please provide the Cloud Foundry Endpoint")
|
||||
})
|
||||
|
||||
t.Run("CF Login check: failure case", func(t *testing.T) {
|
||||
cfconfig := LoginOptions{
|
||||
CfAPIEndpoint: "https://api.endpoint.com",
|
||||
}
|
||||
loggedIn, err := LoginCheck(cfconfig)
|
||||
assert.Equal(t, false, loggedIn)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCloudFoundryLogin(t *testing.T) {
|
||||
t.Run("CF Login: missing parameter", func(t *testing.T) {
|
||||
cfconfig := LoginOptions{}
|
||||
err := Login(cfconfig)
|
||||
assert.EqualError(t, err, "Failed to login to Cloud Foundry: Parameters missing. Please provide the Cloud Foundry Endpoint, Org, Space, Username and Password")
|
||||
})
|
||||
t.Run("CF Login: failure", func(t *testing.T) {
|
||||
cfconfig := LoginOptions{
|
||||
CfAPIEndpoint: "https://api.endpoint.com",
|
||||
CfSpace: "testSpace",
|
||||
CfOrg: "testOrg",
|
||||
Username: "testUser",
|
||||
Password: "testPassword",
|
||||
}
|
||||
err := Login(cfconfig)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCloudFoundryLogout(t *testing.T) {
|
||||
t.Run("CF Logout", func(t *testing.T) {
|
||||
err := Logout()
|
||||
if err == nil {
|
||||
assert.Equal(t, nil, err)
|
||||
} else {
|
||||
assert.Error(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCloudFoundryReadServiceKeyAbapEnvironment(t *testing.T) {
|
||||
t.Run("CF ReadServiceKeyAbapEnvironment", func(t *testing.T) {
|
||||
cfconfig := ServiceKeyOptions{
|
||||
CfAPIEndpoint: "https://api.endpoint.com",
|
||||
CfSpace: "testSpace",
|
||||
CfOrg: "testOrg",
|
||||
CfServiceInstance: "testInstance",
|
||||
CfServiceKey: "testKey",
|
||||
Username: "testUser",
|
||||
Password: "testPassword",
|
||||
}
|
||||
var abapKey ServiceKey
|
||||
abapKey, err := ReadServiceKeyAbapEnvironment(cfconfig, true)
|
||||
assert.Equal(t, "", abapKey.Abap.Password)
|
||||
assert.Equal(t, "", abapKey.Abap.Username)
|
||||
assert.Equal(t, "", abapKey.Abap.CommunicationArrangementID)
|
||||
assert.Equal(t, "", abapKey.Abap.CommunicationScenarioID)
|
||||
assert.Equal(t, "", abapKey.Abap.CommunicationSystemID)
|
||||
assert.Equal(t, "", abapKey.Binding.Env)
|
||||
assert.Equal(t, "", abapKey.Binding.Type)
|
||||
assert.Equal(t, "", abapKey.Binding.ID)
|
||||
assert.Equal(t, "", abapKey.Binding.Version)
|
||||
assert.Equal(t, "", abapKey.Systemid)
|
||||
assert.Equal(t, "", abapKey.URL)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
110
pkg/cloudfoundry/Services.go
Normal file
110
pkg/cloudfoundry/Services.go
Normal file
@ -0,0 +1,110 @@
|
||||
package cloudfoundry
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
)
|
||||
|
||||
//ReadServiceKeyAbapEnvironment from Cloud Foundry and returns it.
|
||||
//Depending on user/developer requirements if he wants to perform further Cloud Foundry actions the cfLogoutOption parameters gives the option to logout after reading ABAP communication arrangement or not.
|
||||
func ReadServiceKeyAbapEnvironment(options ServiceKeyOptions, cfLogoutOption bool) (ServiceKey, error) {
|
||||
var abapServiceKey ServiceKey
|
||||
var err error
|
||||
|
||||
//Logging into Cloud Foundry
|
||||
config := LoginOptions{
|
||||
CfAPIEndpoint: options.CfAPIEndpoint,
|
||||
CfOrg: options.CfOrg,
|
||||
CfSpace: options.CfSpace,
|
||||
Username: options.Username,
|
||||
Password: options.Password,
|
||||
}
|
||||
|
||||
err = Login(config)
|
||||
var serviceKeyBytes bytes.Buffer
|
||||
c.Stdout(&serviceKeyBytes)
|
||||
if err == nil {
|
||||
//Reading Service Key
|
||||
log.Entry().WithField("cfServiceInstance", options.CfServiceInstance).WithField("cfServiceKey", options.CfServiceKey).Info("Read service key for service instance")
|
||||
|
||||
cfReadServiceKeyScript := []string{"service-key", options.CfServiceInstance, options.CfServiceKey}
|
||||
|
||||
err = c.RunExecutable("cf", cfReadServiceKeyScript...)
|
||||
}
|
||||
if err == nil {
|
||||
var serviceKeyJSON string
|
||||
|
||||
if len(serviceKeyBytes.String()) > 0 {
|
||||
var lines []string = strings.Split(serviceKeyBytes.String(), "\n")
|
||||
serviceKeyJSON = strings.Join(lines[2:], "")
|
||||
}
|
||||
|
||||
json.Unmarshal([]byte(serviceKeyJSON), &abapServiceKey)
|
||||
if abapServiceKey == (ServiceKey{}) {
|
||||
return abapServiceKey, errors.New("Parsing the service key failed")
|
||||
}
|
||||
|
||||
log.Entry().Info("Service Key read successfully")
|
||||
}
|
||||
if err != nil {
|
||||
if cfLogoutOption == true {
|
||||
var logoutErr error
|
||||
logoutErr = Logout()
|
||||
if logoutErr != nil {
|
||||
return abapServiceKey, fmt.Errorf("Failed to Logout of Cloud Foundry: %w", err)
|
||||
}
|
||||
}
|
||||
return abapServiceKey, fmt.Errorf("Reading Service Key failed: %w", err)
|
||||
}
|
||||
|
||||
//Logging out of CF
|
||||
if cfLogoutOption == true {
|
||||
var logoutErr error
|
||||
logoutErr = Logout()
|
||||
if logoutErr != nil {
|
||||
return abapServiceKey, fmt.Errorf("Failed to Logout of Cloud Foundry: %w", err)
|
||||
}
|
||||
}
|
||||
return abapServiceKey, nil
|
||||
}
|
||||
|
||||
//ServiceKeyOptions for reading CF Service Key
|
||||
type ServiceKeyOptions struct {
|
||||
CfAPIEndpoint string
|
||||
CfOrg string
|
||||
CfSpace string
|
||||
CfServiceInstance string
|
||||
CfServiceKey string
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
//ServiceKey struct to parse CF Service Key
|
||||
type ServiceKey struct {
|
||||
Abap AbapConnection `json:"abap"`
|
||||
Binding AbapBinding `json:"binding"`
|
||||
Systemid string `json:"systemid"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
//AbapConnection contains information about the ABAP connection for the ABAP endpoint
|
||||
type AbapConnection struct {
|
||||
CommunicationArrangementID string `json:"communication_arrangement_id"`
|
||||
CommunicationScenarioID string `json:"communication_scenario_id"`
|
||||
CommunicationSystemID string `json:"communication_system_id"`
|
||||
Password string `json:"password"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
//AbapBinding contains information about service binding in Cloud Foundry
|
||||
type AbapBinding struct {
|
||||
Env string `json:"env"`
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Version string `json:"version"`
|
||||
}
|
106
resources/metadata/abapEnvironmentRunATCCheck.yaml
Normal file
106
resources/metadata/abapEnvironmentRunATCCheck.yaml
Normal file
@ -0,0 +1,106 @@
|
||||
metadata:
|
||||
name: abapEnvironmentRunATCCheck
|
||||
description: Runs an ATC Check
|
||||
longDescription: |
|
||||
Run ATC Check
|
||||
spec:
|
||||
inputs:
|
||||
secrets:
|
||||
- name: abapCredentialsId
|
||||
aliases:
|
||||
- name: cfCredentialsId
|
||||
description: Jenkins credentials ID containing user and password to authenticate to the Cloud Platform ABAP Environment system or the Cloud Foundry API
|
||||
type: jenkins
|
||||
params:
|
||||
- name: atcConfig
|
||||
type: string
|
||||
description: Path to a YAML configuration file for Packages and/or Software Components to be checked during ATC run
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: true
|
||||
- name: cfApiEndpoint
|
||||
type: string
|
||||
description: Cloud Foundry API endpoint
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- GENERAL
|
||||
mandatory: false
|
||||
aliases:
|
||||
- name: cloudFoundry/apiEndpoint
|
||||
- name: cfOrg
|
||||
type: string
|
||||
description: CF org
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- GENERAL
|
||||
mandatory: false
|
||||
aliases:
|
||||
- name: cloudFoundry/org
|
||||
- name: cfServiceInstance
|
||||
type: string
|
||||
description: Parameter of ServiceInstance Name to delete CloudFoundry Service
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- GENERAL
|
||||
mandatory: false
|
||||
aliases:
|
||||
- name: cloudFoundry/serviceInstance
|
||||
- name: cfServiceKeyName
|
||||
type: string
|
||||
description: Parameter of CloudFoundry Service Key to be created
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- GENERAL
|
||||
mandatory: false
|
||||
aliases:
|
||||
- name: cloudFoundry/serviceKeyName
|
||||
- name: cfSpace
|
||||
type: string
|
||||
description: CF Space
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- GENERAL
|
||||
mandatory: false
|
||||
aliases:
|
||||
- name: cloudFoundry/space
|
||||
- name: username
|
||||
type: string
|
||||
description: User or E-Mail for CF
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: true
|
||||
- name: password
|
||||
type: string
|
||||
description: User Password for CF User
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: true
|
||||
- name: host
|
||||
type: string
|
||||
description: Specifies the host address of the SAP Cloud Platform ABAP Environment system
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: false
|
||||
containers:
|
||||
- name: cf
|
||||
image: ppiper/cf-cli
|
||||
dockerWorkspace: '/home/piper'
|
||||
imagePullPolicy: Never
|
@ -132,6 +132,7 @@ public class CommonStepsTest extends BasePiperTest{
|
||||
'mavenExecuteStaticCodeChecks', //implementing new golang pattern without fields
|
||||
'nexusUpload', //implementing new golang pattern without fields
|
||||
'piperPipelineStageArtifactDeployment', //stage without step flags
|
||||
'abapEnvironmentRunATCCheck', //implementing new golang pattern without fields
|
||||
'sonarExecuteScan', //implementing new golang pattern without fields
|
||||
'gctsCreateRepository', //implementing new golang pattern without fields
|
||||
]
|
||||
|
11
vars/abapEnvironmentRunATCCheck.groovy
Normal file
11
vars/abapEnvironmentRunATCCheck.groovy
Normal file
@ -0,0 +1,11 @@
|
||||
import groovy.transform.Field
|
||||
|
||||
@Field String STEP_NAME = getClass().getName()
|
||||
@Field String METADATA_FILE = 'metadata/abapEnvironmentRunATCCheck.yaml'
|
||||
|
||||
void call(Map parameters = [:]) {
|
||||
List credentials = [
|
||||
[type: 'usernamePassword', id: 'abapCredentialsId', env: ['PIPER_username', 'PIPER_password']]
|
||||
]
|
||||
piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials, true, false, true)
|
||||
}
|
@ -7,5 +7,5 @@ void call(Map parameters = [:]) {
|
||||
List credentials = [
|
||||
[type: 'usernamePassword', id: 'cfCredentialsId', env: ['PIPER_username', 'PIPER_password']]
|
||||
]
|
||||
piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials)
|
||||
piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials, false, false, true)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user