diff --git a/cmd/shellExecute.go b/cmd/shellExecute.go index 44f7dab79..d6ae69d50 100644 --- a/cmd/shellExecute.go +++ b/cmd/shellExecute.go @@ -1,28 +1,23 @@ package cmd import ( - "net/url" + "fmt" "os/exec" - "strings" - "github.com/hashicorp/vault/api" "github.com/pkg/errors" "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/SAP/jenkins-library/pkg/vault" ) type shellExecuteUtils interface { command.ExecRunner - FileExists(filename string) (bool, error) + piperutils.FileUtils } type shellExecuteUtilsBundle struct { - *vault.Client *command.Command *piperutils.Files } @@ -39,83 +34,33 @@ func newShellExecuteUtils() shellExecuteUtils { func shellExecute(config shellExecuteOptions, telemetryData *telemetry.CustomData) { utils := newShellExecuteUtils() - fileUtils := &piperutils.Files{} - err := runShellExecute(&config, telemetryData, utils, fileUtils) + err := runShellExecute(&config, telemetryData, utils) if err != nil { log.Entry().WithError(err).Fatal("step execution failed") } } -func runShellExecute(config *shellExecuteOptions, telemetryData *telemetry.CustomData, utils shellExecuteUtils, fileUtils piperutils.FileUtils) error { - // create vault client - // try to retrieve existing credentials - // if it's impossible - will add it - vaultConfig := &vault.Config{ - Config: &api.Config{ - Address: config.VaultServerURL, - }, - Namespace: config.VaultNamespace, - } - _, err := vault.NewClientWithAppRole(vaultConfig, GeneralConfig.VaultRoleID, GeneralConfig.VaultRoleSecretID) - if err != nil { - log.Entry().Info("could not create vault client:", err) - } - - // piper http client for downloading scripts - httpClient := piperhttp.Client{} - - // scripts for running locally - var e []string - +func runShellExecute(config *shellExecuteOptions, telemetryData *telemetry.CustomData, utils shellExecuteUtils) error { // check input data // example for script: sources: ["./script.sh"] for _, source := range config.Sources { - // check it's a local script or remote - _, err := url.ParseRequestURI(source) + // check if the script is physically present + exists, err := utils.FileExists(source) if err != nil { - // err means that it's not a remote script - // check if the script is physically present (for local scripts) - exists, err := fileUtils.FileExists(source) - if err != nil { - log.Entry().WithError(err).Error("failed to check for defined script") - return errors.Wrap(err, "failed to check for defined script") - } - if !exists { - log.Entry().WithError(err).Error("the specified script could not be found") - return errors.New("the specified script could not be found") - } - e = append(e, source) - } else { - // this block means that it's a remote script - // so, need to download it before - // get script name at first - path := strings.Split(source, "/") - err = httpClient.DownloadFile(source, path[len(path)-1], nil, nil) - if err != nil { - log.Entry().WithError(err).Errorf("the specified script could not be downloaded") - } - // make script executable - exec.Command("/bin/sh", "chmod +x "+path[len(path)-1]) - - e = append(e, path[len(path)-1]) - + log.Entry().WithError(err).Error("failed to check for defined script") + return fmt.Errorf("failed to check for defined script: %w", err) } - } - - // if all ok - try to run them one by one - for _, script := range e { - log.Entry().Info("starting running script:", script) - err = utils.RunExecutable(script) + if !exists { + log.Entry().WithError(err).Errorf("the script '%v' could not be found: %v", source, err) + return fmt.Errorf("the script '%v' could not be found", source) + } + log.Entry().Info("starting running script:", source) + err = utils.RunExecutable(source) if err != nil { - log.Entry().Errorln("starting running script:", script) + log.Entry().Errorln("starting running script:", source) } - - // if it's an exit error, then check the exit code - // according to the requirements - // 0 - success - // 1 - fails the build (or > 2) - // 2 - build unstable - unsupported now + // handle exit code if ee, ok := err.(*exec.ExitError); ok { switch ee.ExitCode() { case 0: diff --git a/cmd/shellExecute_generated.go b/cmd/shellExecute_generated.go index dfa8742bf..72131b527 100644 --- a/cmd/shellExecute_generated.go +++ b/cmd/shellExecute_generated.go @@ -16,9 +16,7 @@ import ( ) type shellExecuteOptions struct { - VaultServerURL string `json:"vaultServerUrl,omitempty"` - VaultNamespace string `json:"vaultNamespace,omitempty"` - Sources []string `json:"sources,omitempty"` + Sources []string `json:"sources,omitempty"` } // ShellExecuteCommand Step executes defined script @@ -35,7 +33,7 @@ func ShellExecuteCommand() *cobra.Command { var createShellExecuteCmd = &cobra.Command{ Use: STEP_NAME, Short: "Step executes defined script", - Long: `Step executes defined script with Vault credentials, or created them on this step`, + Long: `Step executes defined script with using test vault credentials`, PreRunE: func(cmd *cobra.Command, _ []string) error { startTime = time.Now() log.SetStepName(STEP_NAME) @@ -110,8 +108,6 @@ func ShellExecuteCommand() *cobra.Command { } func addShellExecuteFlags(cmd *cobra.Command, stepConfig *shellExecuteOptions) { - cmd.Flags().StringVar(&stepConfig.VaultServerURL, "vaultServerUrl", os.Getenv("PIPER_vaultServerUrl"), "The URL for the Vault server to use") - cmd.Flags().StringVar(&stepConfig.VaultNamespace, "vaultNamespace", os.Getenv("PIPER_vaultNamespace"), "The vault namespace that should be used (optional)") cmd.Flags().StringSliceVar(&stepConfig.Sources, "sources", []string{}, "Scripts names for execution or links to scripts") } @@ -127,24 +123,6 @@ func shellExecuteMetadata() config.StepData { Spec: config.StepSpec{ Inputs: config.StepInputs{ Parameters: []config.StepParameters{ - { - Name: "vaultServerUrl", - ResourceRef: []config.ResourceReference{}, - Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"}, - Type: "string", - Mandatory: false, - Aliases: []config.Alias{}, - Default: os.Getenv("PIPER_vaultServerUrl"), - }, - { - Name: "vaultNamespace", - ResourceRef: []config.ResourceReference{}, - Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"}, - Type: "string", - Mandatory: false, - Aliases: []config.Alias{}, - Default: os.Getenv("PIPER_vaultNamespace"), - }, { Name: "sources", ResourceRef: []config.ResourceReference{}, @@ -156,6 +134,9 @@ func shellExecuteMetadata() config.StepData { }, }, }, + Containers: []config.Container{ + {Name: "shell", Image: "node:lts-stretch", WorkingDir: "/home/node"}, + }, }, } return theMetaData diff --git a/cmd/shellExecute_test.go b/cmd/shellExecute_test.go index 6c223a329..a5155f3c5 100644 --- a/cmd/shellExecute_test.go +++ b/cmd/shellExecute_test.go @@ -52,32 +52,24 @@ func TestRunShellExecute(t *testing.T) { Sources: []string{"path/to/script.sh"}, } u := newShellExecuteTestsUtils() - fm := &shellExecuteFileMock{} - err := runShellExecute(c, nil, u, fm) - assert.EqualError(t, err, "the specified script could not be found") + err := runShellExecute(c, nil, u) + assert.EqualError(t, err, "the script 'path/to/script.sh' could not be found") }) t.Run("success case - script is present", func(t *testing.T) { o := &shellExecuteOptions{} u := newShellExecuteTestsUtils() - m := &shellExecuteFileMock{ - fileReadContent: map[string]string{"path/to/script/script.sh": ``}, - } - err := runShellExecute(o, nil, u, m) + err := runShellExecute(o, nil, u) assert.NoError(t, err) }) t.Run("success case - script run successfully", func(t *testing.T) { o := &shellExecuteOptions{} u := newShellExecuteTestsUtils() - m := &shellExecuteFileMock{ - fileReadContent: map[string]string{"path/to/script/script.sh": `#!/usr/bin/env sh -print 'test'`}, - } - err := runShellExecute(o, nil, u, m) + err := runShellExecute(o, nil, u) assert.NoError(t, err) }) diff --git a/documentation/docs/steps/shellExecute.md b/documentation/docs/steps/shellExecute.md new file mode 100644 index 000000000..63991c134 --- /dev/null +++ b/documentation/docs/steps/shellExecute.md @@ -0,0 +1,7 @@ +# ${docGenStepName} + +## ${docGenDescription} + +## ${docGenParameters} + +## ${docGenConfiguration} diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml index a7259b0e9..beef07f65 100644 --- a/documentation/mkdocs.yml +++ b/documentation/mkdocs.yml @@ -145,6 +145,7 @@ nav: - protecodeExecuteScan: steps/protecodeExecuteScan.md - seleniumExecuteTests: steps/seleniumExecuteTests.md - setupCommonPipelineEnvironment: steps/setupCommonPipelineEnvironment.md + - shellExecute: steps/shellExecute.md - slackSendNotification: steps/slackSendNotification.md - snykExecute: steps/snykExecute.md - sonarExecuteScan: steps/sonarExecuteScan.md diff --git a/resources/metadata/shellExecute.yaml b/resources/metadata/shellExecute.yaml index 45e8f9969..9f2419c07 100644 --- a/resources/metadata/shellExecute.yaml +++ b/resources/metadata/shellExecute.yaml @@ -1,26 +1,10 @@ metadata: name: shellExecute description: Step executes defined script - longDescription: Step executes defined script with Vault credentials, or created them on this step + longDescription: Step executes defined script with using test vault credentials spec: inputs: params: - - name: vaultServerUrl - type: string - scope: - - GENERAL - - PARAMETERS - - STAGES - - STEPS - description: The URL for the Vault server to use - - name: vaultNamespace - type: string - scope: - - GENERAL - - PARAMETERS - - STAGES - - STEPS - description: The vault namespace that should be used (optional) - name: sources type: "[]string" scope: @@ -28,3 +12,7 @@ spec: - STAGES - STEPS description: Scripts names for execution or links to scripts + containers: + - name: shell + image: node:lts-stretch + workingDir: /home/node diff --git a/test/groovy/CommonStepsTest.groovy b/test/groovy/CommonStepsTest.groovy index 7c7d5fd4c..d45d0235b 100644 --- a/test/groovy/CommonStepsTest.groovy +++ b/test/groovy/CommonStepsTest.groovy @@ -205,6 +205,7 @@ public class CommonStepsTest extends BasePiperTest{ 'golangBuild', //implementing new golang pattern without fields 'apiProxyDownload', //implementing new golang pattern without fields 'apiKeyValueMapDownload', //implementing new golang pattern without fields + 'shellExecute', //implementing new golang pattern without fields ] @Test diff --git a/vars/shellExecute.groovy b/vars/shellExecute.groovy new file mode 100644 index 000000000..9c3656be8 --- /dev/null +++ b/vars/shellExecute.groovy @@ -0,0 +1,9 @@ +import groovy.transform.Field + +@Field String STEP_NAME = getClass().getName() +@Field String METADATA_FILE = 'metadata/shellExecute.yaml' + +void call(Map parameters = [:]) { + List credentials = [] + piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials) +}