mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-12 10:55:20 +02:00
Introduce checkChangeInDevelopment (#2504)
* Introduce checkChangeInDevelopment
This commit is contained in:
parent
2df2771734
commit
e90548d41d
109
cmd/checkChangeInDevelopment.go
Normal file
109
cmd/checkChangeInDevelopment.go
Normal file
@ -0,0 +1,109 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/SAP/jenkins-library/pkg/command"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/SAP/jenkins-library/pkg/telemetry"
|
||||
"github.com/pkg/errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type checkChangeInDevelopmentUtils interface {
|
||||
command.ExecRunner
|
||||
GetExitCode() int
|
||||
|
||||
// Add more methods here, or embed additional interfaces, or remove/replace as required.
|
||||
// The checkChangeInDevelopmentUtils interface should be descriptive of your runtime dependencies,
|
||||
// i.e. include everything you need to be able to mock in tests.
|
||||
// Unit tests shall be executable in parallel (not depend on global state), and don't (re-)test dependencies.
|
||||
}
|
||||
|
||||
type checkChangeInDevelopmentUtilsBundle struct {
|
||||
*command.Command
|
||||
|
||||
// Embed more structs as necessary to implement methods or interfaces you add to checkChangeInDevelopmentUtils.
|
||||
// Structs embedded in this way must each have a unique set of methods attached.
|
||||
// If there is no struct which implements the method you need, attach the method to
|
||||
// checkChangeInDevelopmentUtilsBundle and forward to the implementation of the dependency.
|
||||
}
|
||||
|
||||
func newCheckChangeInDevelopmentUtils() checkChangeInDevelopmentUtils {
|
||||
utils := checkChangeInDevelopmentUtilsBundle{
|
||||
Command: &command.Command{},
|
||||
}
|
||||
// Reroute command output to logging framework
|
||||
utils.Stdout(log.Writer())
|
||||
utils.Stderr(log.Writer())
|
||||
return &utils
|
||||
}
|
||||
|
||||
func checkChangeInDevelopment(config checkChangeInDevelopmentOptions, telemetryData *telemetry.CustomData) {
|
||||
// Utils can be used wherever the command.ExecRunner interface is expected.
|
||||
// It can also be used for example as a mavenExecRunner.
|
||||
utils := newCheckChangeInDevelopmentUtils()
|
||||
|
||||
// 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
|
||||
|
||||
// Error situations should be bubbled up until they reach the line below which will then stop execution
|
||||
// through the log.Entry().Fatal() call leading to an os.Exit(1) in the end.
|
||||
err := runCheckChangeInDevelopment(&config, telemetryData, utils)
|
||||
if err != nil {
|
||||
log.Entry().WithError(err).Fatal("step execution failed")
|
||||
}
|
||||
}
|
||||
|
||||
func runCheckChangeInDevelopment(config *checkChangeInDevelopmentOptions, telemetryData *telemetry.CustomData, utils checkChangeInDevelopmentUtils) error {
|
||||
|
||||
log.Entry().Infof("Checking change status for change '%s'", config.ChangeDocumentID)
|
||||
|
||||
isInDevelopment, err := isChangeInDevelopment(config, utils)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isInDevelopment {
|
||||
log.Entry().Infof("Change '%s' is in status 'in development'.", config.ChangeDocumentID)
|
||||
return nil
|
||||
}
|
||||
if config.FailIfStatusIsNotInDevelopment {
|
||||
return fmt.Errorf("Change '%s' is not in status 'in development'", config.ChangeDocumentID)
|
||||
}
|
||||
log.Entry().Warningf("Change '%s' is not in status 'in development'. Failing the step has been explicitly disabled.", config.ChangeDocumentID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func isChangeInDevelopment(config *checkChangeInDevelopmentOptions, utils checkChangeInDevelopmentUtils) (bool, error) {
|
||||
|
||||
if len(config.ClientOpts) > 0 {
|
||||
utils.AppendEnv([]string{fmt.Sprintf("CMCLIENT_OPTS=%s", strings.Join(config.ClientOpts, " "))})
|
||||
}
|
||||
|
||||
err := utils.RunExecutable("cmclient",
|
||||
"--endpoint", config.Endpoint,
|
||||
"--user", config.Username,
|
||||
"--password", config.Password,
|
||||
"--backend-type", "SOLMAN",
|
||||
"is-change-in-development",
|
||||
"--change-id", config.ChangeDocumentID,
|
||||
"--return-code")
|
||||
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "Cannot retrieve change status")
|
||||
}
|
||||
|
||||
exitCode := utils.GetExitCode()
|
||||
|
||||
hint := "Check log for details"
|
||||
if exitCode == 0 {
|
||||
return true, nil
|
||||
} else if exitCode == 3 {
|
||||
return false, nil
|
||||
} else if exitCode == 2 {
|
||||
hint = "Invalid credentials"
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("Cannot retrieve change status: %s", hint)
|
||||
}
|
161
cmd/checkChangeInDevelopment_generated.go
Normal file
161
cmd/checkChangeInDevelopment_generated.go
Normal file
@ -0,0 +1,161 @@
|
||||
// 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 checkChangeInDevelopmentOptions struct {
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
ChangeDocumentID string `json:"changeDocumentId,omitempty"`
|
||||
FailIfStatusIsNotInDevelopment bool `json:"failIfStatusIsNotInDevelopment,omitempty"`
|
||||
ClientOpts []string `json:"clientOpts,omitempty"`
|
||||
}
|
||||
|
||||
// CheckChangeInDevelopmentCommand Checks if a certain change is in status 'in development'
|
||||
func CheckChangeInDevelopmentCommand() *cobra.Command {
|
||||
const STEP_NAME = "checkChangeInDevelopment"
|
||||
|
||||
metadata := checkChangeInDevelopmentMetadata()
|
||||
var stepConfig checkChangeInDevelopmentOptions
|
||||
var startTime time.Time
|
||||
|
||||
var createCheckChangeInDevelopmentCmd = &cobra.Command{
|
||||
Use: STEP_NAME,
|
||||
Short: "Checks if a certain change is in status 'in development'",
|
||||
Long: `"Checks if a certain change is in status 'in development'"`,
|
||||
PreRunE: func(cmd *cobra.Command, _ []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 {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
return err
|
||||
}
|
||||
log.RegisterSecret(stepConfig.Username)
|
||||
log.RegisterSecret(stepConfig.Password)
|
||||
|
||||
if len(GeneralConfig.HookConfig.SentryConfig.Dsn) > 0 {
|
||||
sentryHook := log.NewSentryHook(GeneralConfig.HookConfig.SentryConfig.Dsn, GeneralConfig.CorrelationID)
|
||||
log.RegisterHook(&sentryHook)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
telemetryData := telemetry.CustomData{}
|
||||
telemetryData.ErrorCode = "1"
|
||||
handler := func() {
|
||||
config.RemoveVaultSecretFiles()
|
||||
telemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds())
|
||||
telemetryData.ErrorCategory = log.GetErrorCategory().String()
|
||||
telemetry.Send(&telemetryData)
|
||||
}
|
||||
log.DeferExitHandler(handler)
|
||||
defer handler()
|
||||
telemetry.Initialize(GeneralConfig.NoTelemetry, STEP_NAME)
|
||||
checkChangeInDevelopment(stepConfig, &telemetryData)
|
||||
telemetryData.ErrorCode = "0"
|
||||
log.Entry().Info("SUCCESS")
|
||||
},
|
||||
}
|
||||
|
||||
addCheckChangeInDevelopmentFlags(createCheckChangeInDevelopmentCmd, &stepConfig)
|
||||
return createCheckChangeInDevelopmentCmd
|
||||
}
|
||||
|
||||
func addCheckChangeInDevelopmentFlags(cmd *cobra.Command, stepConfig *checkChangeInDevelopmentOptions) {
|
||||
cmd.Flags().StringVar(&stepConfig.Endpoint, "endpoint", os.Getenv("PIPER_endpoint"), "The service endpoint")
|
||||
cmd.Flags().StringVar(&stepConfig.Username, "username", os.Getenv("PIPER_username"), "The user")
|
||||
cmd.Flags().StringVar(&stepConfig.Password, "password", os.Getenv("PIPER_password"), "The password")
|
||||
cmd.Flags().StringVar(&stepConfig.ChangeDocumentID, "changeDocumentId", os.Getenv("PIPER_changeDocumentId"), "The change document which should be checked for the status")
|
||||
cmd.Flags().BoolVar(&stepConfig.FailIfStatusIsNotInDevelopment, "failIfStatusIsNotInDevelopment", true, "lets the build fail in case the change is not in status 'in developent'. Otherwise a warning is emitted to the log")
|
||||
cmd.Flags().StringSliceVar(&stepConfig.ClientOpts, "clientOpts", []string{}, "additional options passed to cm client, e.g. for troubleshooting")
|
||||
|
||||
cmd.MarkFlagRequired("endpoint")
|
||||
cmd.MarkFlagRequired("username")
|
||||
cmd.MarkFlagRequired("password")
|
||||
cmd.MarkFlagRequired("changeDocumentId")
|
||||
}
|
||||
|
||||
// retrieve step metadata
|
||||
func checkChangeInDevelopmentMetadata() config.StepData {
|
||||
var theMetaData = config.StepData{
|
||||
Metadata: config.StepMetadata{
|
||||
Name: "checkChangeInDevelopment",
|
||||
Aliases: []config.Alias{},
|
||||
Description: "Checks if a certain change is in status 'in development'",
|
||||
},
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{
|
||||
Name: "endpoint",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
Aliases: []config.Alias{{Name: "changeManagement/endpoint"}},
|
||||
},
|
||||
{
|
||||
Name: "username",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "password",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "GENERAL"},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "changeDocumentId",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "failIfStatusIsNotInDevelopment",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "bool",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "clientOpts",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "[]string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return theMetaData
|
||||
}
|
17
cmd/checkChangeInDevelopment_generated_test.go
Normal file
17
cmd/checkChangeInDevelopment_generated_test.go
Normal file
@ -0,0 +1,17 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCheckChangeInDevelopmentCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCmd := CheckChangeInDevelopmentCommand()
|
||||
|
||||
// only high level testing performed - details are tested in step generation procedure
|
||||
assert.Equal(t, "checkChangeInDevelopment", testCmd.Use, "command name incorrect")
|
||||
|
||||
}
|
122
cmd/checkChangeInDevelopment_test.go
Normal file
122
cmd/checkChangeInDevelopment_test.go
Normal file
@ -0,0 +1,122 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/SAP/jenkins-library/pkg/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type checkChangeInDevelopmentMockUtils struct {
|
||||
*mock.ExecMockRunner
|
||||
}
|
||||
|
||||
func newCheckChangeInDevelopmentTestsUtils() checkChangeInDevelopmentMockUtils {
|
||||
utils := checkChangeInDevelopmentMockUtils{
|
||||
ExecMockRunner: &mock.ExecMockRunner{},
|
||||
}
|
||||
return utils
|
||||
}
|
||||
|
||||
func TestRunCheckChangeInDevelopment(t *testing.T) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
config := checkChangeInDevelopmentOptions{
|
||||
Endpoint: "https://example.org/cm",
|
||||
Username: "me",
|
||||
Password: "****",
|
||||
ChangeDocumentID: "12345678",
|
||||
ClientOpts: []string{"-Dabc=123", "-Ddef=456"},
|
||||
FailIfStatusIsNotInDevelopment: true, // this is the default
|
||||
}
|
||||
|
||||
expectedShellCall := mock.ExecCall{
|
||||
Exec: "cmclient",
|
||||
Params: []string{
|
||||
"--endpoint", "https://example.org/cm",
|
||||
"--user", "me",
|
||||
"--password", "****",
|
||||
"--backend-type", "SOLMAN",
|
||||
"is-change-in-development",
|
||||
"--change-id", "12345678",
|
||||
"--return-code",
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("change found and in status IN_DEVELOPMENT", func(t *testing.T) {
|
||||
|
||||
cmd := newCheckChangeInDevelopmentTestsUtils()
|
||||
cmd.ExitCode = 0 // this exit code represents a change in status IN_DEVELOPMENT
|
||||
|
||||
err := runCheckChangeInDevelopment(&config, nil, cmd)
|
||||
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, []string{"CMCLIENT_OPTS=-Dabc=123 -Ddef=456"}, cmd.Env)
|
||||
assert.Equal(t, []mock.ExecCall{expectedShellCall}, cmd.Calls)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("change found and not in status IN_DEVELOPMENT", func(t *testing.T) {
|
||||
|
||||
cmd := newCheckChangeInDevelopmentTestsUtils()
|
||||
cmd.ExitCode = 3 // this exit code represents a change which is not in status IN_DEVELOPMENT
|
||||
|
||||
err := runCheckChangeInDevelopment(&config, nil, cmd)
|
||||
|
||||
if assert.EqualError(t, err, "Change '12345678' is not in status 'in development'") {
|
||||
assert.Equal(t, []mock.ExecCall{expectedShellCall}, cmd.Calls)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("change found and not in status IN_DEVELOPMENT, but we don't fail", func(t *testing.T) {
|
||||
|
||||
cmd := newCheckChangeInDevelopmentTestsUtils()
|
||||
cmd.ExitCode = 3 // this exit code represents a change which is not in status IN_DEVELOPMENT
|
||||
|
||||
myConfig := config
|
||||
myConfig.FailIfStatusIsNotInDevelopment = false // needs to be explicitly configured
|
||||
|
||||
err := runCheckChangeInDevelopment(&myConfig, nil, cmd)
|
||||
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, []mock.ExecCall{expectedShellCall}, cmd.Calls)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid credentials", func(t *testing.T) {
|
||||
|
||||
cmd := newCheckChangeInDevelopmentTestsUtils()
|
||||
cmd.ExitCode = 2 // this exit code represents invalid credentials
|
||||
|
||||
err := runCheckChangeInDevelopment(&config, nil, cmd)
|
||||
|
||||
if assert.EqualError(t, err, "Cannot retrieve change status: Invalid credentials") {
|
||||
assert.Equal(t, []mock.ExecCall{expectedShellCall}, cmd.Calls)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("generic failure reported via exit code", func(t *testing.T) {
|
||||
|
||||
cmd := newCheckChangeInDevelopmentTestsUtils()
|
||||
cmd.ExitCode = 1 // this exit code indicates something went wrong
|
||||
|
||||
err := runCheckChangeInDevelopment(&config, nil, cmd)
|
||||
|
||||
if assert.EqualError(t, err, "Cannot retrieve change status: Check log for details") {
|
||||
assert.Equal(t, []mock.ExecCall{expectedShellCall}, cmd.Calls)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("generic failure reported via error", func(t *testing.T) {
|
||||
|
||||
cmd := newCheckChangeInDevelopmentTestsUtils()
|
||||
cmd.ShouldFailOnCommand = map[string]error{"cm.*": fmt.Errorf("%v", "Something went wrong")}
|
||||
|
||||
err := runCheckChangeInDevelopment(&config, nil, cmd)
|
||||
|
||||
if assert.EqualError(t, err, "Cannot retrieve change status: Something went wrong") {
|
||||
assert.Equal(t, []mock.ExecCall{expectedShellCall}, cmd.Calls)
|
||||
}
|
||||
})
|
||||
}
|
@ -21,6 +21,7 @@ func GetAllStepMetadata() map[string]config.StepData {
|
||||
"abapEnvironmentCreateSystem": abapEnvironmentCreateSystemMetadata(),
|
||||
"abapEnvironmentPullGitRepo": abapEnvironmentPullGitRepoMetadata(),
|
||||
"abapEnvironmentRunATCCheck": abapEnvironmentRunATCCheckMetadata(),
|
||||
"checkChangeInDevelopment": checkChangeInDevelopmentMetadata(),
|
||||
"checkmarxExecuteScan": checkmarxExecuteScanMetadata(),
|
||||
"cloudFoundryCreateService": cloudFoundryCreateServiceMetadata(),
|
||||
"cloudFoundryCreateServiceKey": cloudFoundryCreateServiceKeyMetadata(),
|
||||
|
@ -121,6 +121,7 @@ func Execute() {
|
||||
rootCmd.AddCommand(CloudFoundryCreateSpaceCommand())
|
||||
rootCmd.AddCommand(CloudFoundryDeleteSpaceCommand())
|
||||
rootCmd.AddCommand(VaultRotateSecretIdCommand())
|
||||
rootCmd.AddCommand(CheckChangeInDevelopmentCommand())
|
||||
rootCmd.AddCommand(TransportRequestUploadCTSCommand())
|
||||
rootCmd.AddCommand(IntegrationArtifactDeployCommand())
|
||||
rootCmd.AddCommand(IntegrationArtifactUpdateConfigurationCommand())
|
||||
|
61
resources/metadata/checkChangeInDevelopmemt.yaml
Normal file
61
resources/metadata/checkChangeInDevelopmemt.yaml
Normal file
@ -0,0 +1,61 @@
|
||||
metadata:
|
||||
name: checkChangeInDevelopment
|
||||
description: "Checks if a certain change is in status 'in development'"
|
||||
longDescription: |
|
||||
"Checks if a certain change is in status 'in development'"
|
||||
|
||||
spec:
|
||||
inputs:
|
||||
params:
|
||||
- name: endpoint
|
||||
type: string
|
||||
mandatory: true
|
||||
description: "The service endpoint"
|
||||
aliases:
|
||||
- name: changeManagement/endpoint
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- GENERAL
|
||||
- name: username
|
||||
type: string
|
||||
mandatory: true
|
||||
description: "The user"
|
||||
secret: true
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- GENERAL
|
||||
- name: password
|
||||
type: string
|
||||
mandatory: true
|
||||
description: "The password"
|
||||
secret: true
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- GENERAL
|
||||
- name: changeDocumentId
|
||||
type: string
|
||||
mandatory: true
|
||||
description: "The change document which should be checked for the status"
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- name: failIfStatusIsNotInDevelopment
|
||||
type: bool
|
||||
default: true
|
||||
description: "lets the build fail in case the change is not in status 'in developent'. Otherwise a warning is emitted to the log"
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- name: clientOpts
|
||||
type: "[]string"
|
||||
description: "additional options passed to cm client, e.g. for troubleshooting"
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
Loading…
Reference in New Issue
Block a user