1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-01-04 04:07:16 +02:00

Persisted pipeline environment for golang library (#1091)

* Use commonPipelineEnvironment in go binary

* Update groovy part incl. tests

* Rework structure and naming

* Support influx resources in steps

* Update tests and some cleanups

* Add correct defer handling

* Address PR feedback

* Fix test

* Update resources.go

Co-authored-by: Sven Merk <33895725+nevskrem@users.noreply.github.com>
This commit is contained in:
Oliver Nocon 2020-01-15 12:16:25 +01:00 committed by GitHub
parent e5db600cf4
commit a46b57e6b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1369 additions and 468 deletions

View File

@ -4,6 +4,7 @@
1. [Getting started](#getting-started)
1. [Build the project](#build-the-project_)
1. [Generating step framework](#generating-step-framework)
1. [Logging](#logging)
1. [Error handling](#error-handling)

View File

@ -5,6 +5,7 @@ import (
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/spf13/cobra"
)
@ -20,11 +21,11 @@ type detectExecuteScanOptions struct {
}
var myDetectExecuteScanOptions detectExecuteScanOptions
var detectExecuteScanStepConfigJSON string
// DetectExecuteScanCommand Executes Synopsis Detect scan
func DetectExecuteScanCommand() *cobra.Command {
metadata := detectExecuteScanMetadata()
var createDetectExecuteScanCmd = &cobra.Command{
Use: "detectExecuteScan",
Short: "Executes Synopsis Detect scan",
@ -35,6 +36,7 @@ func DetectExecuteScanCommand() *cobra.Command {
return PrepareConfig(cmd, &metadata, "detectExecuteScan", &myDetectExecuteScanOptions, config.OpenPiperFile)
},
RunE: func(cmd *cobra.Command, args []string) error {
return detectExecuteScan(myDetectExecuteScanOptions)
},
}
@ -65,60 +67,68 @@ func detectExecuteScanMetadata() config.StepData {
Inputs: config.StepInputs{
Parameters: []config.StepParameters{
{
Name: "apiToken",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "detect/apiToken"}},
Name: "apiToken",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "detect/apiToken"}},
},
{
Name: "codeLocation",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
Name: "codeLocation",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "projectName",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "detect/projectName"}},
Name: "projectName",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "detect/projectName"}},
},
{
Name: "projectVersion",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "detect/projectVersion"}},
Name: "projectVersion",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "detect/projectVersion"}},
},
{
Name: "scanners",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{{Name: "detect/scanners"}},
Name: "scanners",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{{Name: "detect/scanners"}},
},
{
Name: "scanPaths",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{{Name: "detect/scanPaths"}},
Name: "scanPaths",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{{Name: "detect/scanPaths"}},
},
{
Name: "scanProperties",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{{Name: "detect/scanProperties"}},
Name: "scanProperties",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{{Name: "detect/scanProperties"}},
},
{
Name: "serverUrl",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "detect/serverUrl"}},
Name: "serverUrl",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "detect/serverUrl"}},
},
},
},

View File

@ -54,6 +54,8 @@ func generateConfig() error {
return errors.Wrap(err, "metadata: read failed")
}
resourceParams := metadata.GetResourceParameters(GeneralConfig.EnvRootPath, "commonPipelineEnvironment")
var customConfig io.ReadCloser
{
exists, e := piperutils.FileExists(GeneralConfig.CustomConfig)
@ -91,7 +93,7 @@ func generateConfig() error {
params = metadata.Spec.Inputs.Parameters
}
stepConfig, err = myConfig.GetStepConfig(flags, GeneralConfig.ParametersJSON, customConfig, defaultConfig, paramFilter, params, GeneralConfig.StageName, metadata.Metadata.Name)
stepConfig, err = myConfig.GetStepConfig(flags, GeneralConfig.ParametersJSON, customConfig, defaultConfig, paramFilter, params, resourceParams, GeneralConfig.StageName, metadata.Metadata.Name)
if err != nil {
return errors.Wrap(err, "getting step config failed")
}

View File

@ -5,6 +5,7 @@ import (
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/spf13/cobra"
)
@ -23,11 +24,11 @@ type githubCreatePullRequestOptions struct {
}
var myGithubCreatePullRequestOptions githubCreatePullRequestOptions
var githubCreatePullRequestStepConfigJSON string
// GithubCreatePullRequestCommand Create a pull request on GitHub
func GithubCreatePullRequestCommand() *cobra.Command {
metadata := githubCreatePullRequestMetadata()
var createGithubCreatePullRequestCmd = &cobra.Command{
Use: "githubCreatePullRequest",
Short: "Create a pull request on GitHub",
@ -40,6 +41,7 @@ It can for example be used for GitOps scenarios or for scenarios where you want
return PrepareConfig(cmd, &metadata, "githubCreatePullRequest", &myGithubCreatePullRequestOptions, config.OpenPiperFile)
},
RunE: func(cmd *cobra.Command, args []string) error {
return githubCreatePullRequest(myGithubCreatePullRequestOptions)
},
}
@ -79,81 +81,92 @@ func githubCreatePullRequestMetadata() config.StepData {
Inputs: config.StepInputs{
Parameters: []config.StepParameters{
{
Name: "assignees",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{},
Name: "assignees",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "base",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Name: "base",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "body",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Name: "body",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "apiUrl",
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubApiUrl"}},
Name: "apiUrl",
ResourceRef: []config.ResourceReference{},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubApiUrl"}},
},
{
Name: "head",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Name: "head",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "owner",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubOrg"}},
Name: "owner",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubOrg"}},
},
{
Name: "repository",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubRepo"}},
Name: "repository",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubRepo"}},
},
{
Name: "serverUrl",
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubServerUrl"}},
Name: "serverUrl",
ResourceRef: []config.ResourceReference{},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubServerUrl"}},
},
{
Name: "title",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Name: "title",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "token",
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubToken"}},
Name: "token",
ResourceRef: []config.ResourceReference{},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubToken"}},
},
{
Name: "labels",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{},
Name: "labels",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{},
},
},
},

View File

@ -5,32 +5,33 @@ import (
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/spf13/cobra"
)
type githubPublishReleaseOptions struct {
AddClosedIssues bool `json:"addClosedIssues,omitempty"`
AddDeltaToLastRelease bool `json:"addDeltaToLastRelease,omitempty"`
APIURL string `json:"apiUrl,omitempty"`
AssetPath string `json:"assetPath,omitempty"`
Commitish string `json:"commitish,omitempty"`
ExcludeLabels []string `json:"excludeLabels,omitempty"`
APIURL string `json:"apiUrl,omitempty"`
Labels []string `json:"labels,omitempty"`
Owner string `json:"owner,omitempty"`
ReleaseBodyHeader string `json:"releaseBodyHeader,omitempty"`
Repository string `json:"repository,omitempty"`
ServerURL string `json:"serverUrl,omitempty"`
Token string `json:"token,omitempty"`
UploadURL string `json:"uploadUrl,omitempty"`
Labels []string `json:"labels,omitempty"`
ReleaseBodyHeader string `json:"releaseBodyHeader,omitempty"`
Version string `json:"version,omitempty"`
}
var myGithubPublishReleaseOptions githubPublishReleaseOptions
var githubPublishReleaseStepConfigJSON string
// GithubPublishReleaseCommand Publish a release in GitHub
func GithubPublishReleaseCommand() *cobra.Command {
metadata := githubPublishReleaseMetadata()
var createGithubPublishReleaseCmd = &cobra.Command{
Use: "githubPublishRelease",
Short: "Publish a release in GitHub",
@ -50,6 +51,7 @@ The result looks like
return PrepareConfig(cmd, &metadata, "githubPublishRelease", &myGithubPublishReleaseOptions, config.OpenPiperFile)
},
RunE: func(cmd *cobra.Command, args []string) error {
return githubPublishRelease(myGithubPublishReleaseOptions)
},
}
@ -61,17 +63,17 @@ The result looks like
func addGithubPublishReleaseFlags(cmd *cobra.Command) {
cmd.Flags().BoolVar(&myGithubPublishReleaseOptions.AddClosedIssues, "addClosedIssues", false, "If set to `true`, closed issues and merged pull-requests since the last release will added below the `releaseBodyHeader`")
cmd.Flags().BoolVar(&myGithubPublishReleaseOptions.AddDeltaToLastRelease, "addDeltaToLastRelease", false, "If set to `true`, a link will be added to the relese information that brings up all commits since the last release.")
cmd.Flags().StringVar(&myGithubPublishReleaseOptions.APIURL, "apiUrl", "https://api.github.com", "Set the GitHub API url.")
cmd.Flags().StringVar(&myGithubPublishReleaseOptions.AssetPath, "assetPath", os.Getenv("PIPER_assetPath"), "Path to a release asset which should be uploaded to the list of release assets.")
cmd.Flags().StringVar(&myGithubPublishReleaseOptions.Commitish, "commitish", "master", "Target git commitish for the release")
cmd.Flags().StringSliceVar(&myGithubPublishReleaseOptions.ExcludeLabels, "excludeLabels", []string{}, "Allows to exclude issues with dedicated list of labels.")
cmd.Flags().StringVar(&myGithubPublishReleaseOptions.APIURL, "apiUrl", "https://api.github.com", "Set the GitHub API url.")
cmd.Flags().StringSliceVar(&myGithubPublishReleaseOptions.Labels, "labels", []string{}, "Labels to include in issue search.")
cmd.Flags().StringVar(&myGithubPublishReleaseOptions.Owner, "owner", os.Getenv("PIPER_owner"), "Set the GitHub organization.")
cmd.Flags().StringVar(&myGithubPublishReleaseOptions.ReleaseBodyHeader, "releaseBodyHeader", os.Getenv("PIPER_releaseBodyHeader"), "Content which will appear for the release.")
cmd.Flags().StringVar(&myGithubPublishReleaseOptions.Repository, "repository", os.Getenv("PIPER_repository"), "Set the GitHub repository.")
cmd.Flags().StringVar(&myGithubPublishReleaseOptions.ServerURL, "serverUrl", "https://github.com", "GitHub server url for end-user access.")
cmd.Flags().StringVar(&myGithubPublishReleaseOptions.Token, "token", os.Getenv("PIPER_token"), "GitHub personal access token as per https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line")
cmd.Flags().StringVar(&myGithubPublishReleaseOptions.UploadURL, "uploadUrl", "https://uploads.github.com", "Set the GitHub API url.")
cmd.Flags().StringSliceVar(&myGithubPublishReleaseOptions.Labels, "labels", []string{}, "Labels to include in issue search.")
cmd.Flags().StringVar(&myGithubPublishReleaseOptions.ReleaseBodyHeader, "releaseBodyHeader", os.Getenv("PIPER_releaseBodyHeader"), "Content which will appear for the release.")
cmd.Flags().StringVar(&myGithubPublishReleaseOptions.Version, "version", os.Getenv("PIPER_version"), "Define the version number which will be written as tag as well as release name.")
cmd.MarkFlagRequired("apiUrl")
@ -90,102 +92,116 @@ func githubPublishReleaseMetadata() config.StepData {
Inputs: config.StepInputs{
Parameters: []config.StepParameters{
{
Name: "addClosedIssues",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "bool",
Mandatory: false,
Aliases: []config.Alias{},
Name: "addClosedIssues",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "bool",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "addDeltaToLastRelease",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "bool",
Mandatory: false,
Aliases: []config.Alias{},
Name: "addDeltaToLastRelease",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "bool",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "assetPath",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
Name: "apiUrl",
ResourceRef: []config.ResourceReference{},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubApiUrl"}},
},
{
Name: "commitish",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
Name: "assetPath",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "excludeLabels",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{},
Name: "commitish",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "apiUrl",
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubApiUrl"}},
Name: "excludeLabels",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "owner",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubOrg"}},
Name: "labels",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "repository",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubRepo"}},
Name: "owner",
ResourceRef: []config.ResourceReference{{Name: "commonPipelineEnvironment", Param: "github/owner"}},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubOrg"}},
},
{
Name: "serverUrl",
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubServerUrl"}},
Name: "releaseBodyHeader",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "token",
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubToken"}},
Name: "repository",
ResourceRef: []config.ResourceReference{{Name: "commonPipelineEnvironment", Param: "github/repository"}},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubRepo"}},
},
{
Name: "uploadUrl",
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubUploadUrl"}},
Name: "serverUrl",
ResourceRef: []config.ResourceReference{},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubServerUrl"}},
},
{
Name: "labels",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{},
Name: "token",
ResourceRef: []config.ResourceReference{},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubToken"}},
},
{
Name: "releaseBodyHeader",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
Name: "uploadUrl",
ResourceRef: []config.ResourceReference{},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubUploadUrl"}},
},
{
Name: "version",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Name: "version",
ResourceRef: []config.ResourceReference{{Name: "commonPipelineEnvironment", Param: "artifactVersion"}},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
},
},

View File

@ -3,6 +3,7 @@ package cmd
import (
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/spf13/cobra"
)
@ -13,11 +14,11 @@ type karmaExecuteTestsOptions struct {
}
var myKarmaExecuteTestsOptions karmaExecuteTestsOptions
var karmaExecuteTestsStepConfigJSON string
// KarmaExecuteTestsCommand Executes the Karma test runner
func KarmaExecuteTestsCommand() *cobra.Command {
metadata := karmaExecuteTestsMetadata()
var createKarmaExecuteTestsCmd = &cobra.Command{
Use: "karmaExecuteTests",
Short: "Executes the Karma test runner",
@ -38,6 +39,7 @@ In the Docker network, the containers can be referenced by the values provided i
return PrepareConfig(cmd, &metadata, "karmaExecuteTests", &myKarmaExecuteTestsOptions, config.OpenPiperFile)
},
RunE: func(cmd *cobra.Command, args []string) error {
return karmaExecuteTests(myKarmaExecuteTestsOptions)
},
}
@ -63,25 +65,28 @@ func karmaExecuteTestsMetadata() config.StepData {
Inputs: config.StepInputs{
Parameters: []config.StepParameters{
{
Name: "installCommand",
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Name: "installCommand",
ResourceRef: []config.ResourceReference{},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "modulePath",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Name: "modulePath",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "runCommand",
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Name: "runCommand",
ResourceRef: []config.ResourceReference{},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
},
},

View File

@ -18,6 +18,7 @@ type GeneralConfigOptions struct {
CustomConfig string
DefaultConfig []string //ordered list of Piper default configurations. Can be filePath or ENV containing JSON in format 'ENV:MY_ENV_VAR'
ParametersJSON string
EnvRootPath string
StageName string
StepConfigJSON string
StepMetadata string //metadata to be considered, can be filePath or ENV containing JSON in format 'ENV:MY_ENV_VAR'
@ -61,6 +62,7 @@ func addRootFlags(rootCmd *cobra.Command) {
rootCmd.PersistentFlags().StringVar(&GeneralConfig.CustomConfig, "customConfig", ".pipeline/config.yml", "Path to the pipeline configuration file")
rootCmd.PersistentFlags().StringSliceVar(&GeneralConfig.DefaultConfig, "defaultConfig", []string{".pipeline/defaults.yaml"}, "Default configurations, passed as path to yaml file")
rootCmd.PersistentFlags().StringVar(&GeneralConfig.ParametersJSON, "parametersJSON", os.Getenv("PIPER_parametersJSON"), "Parameters to be considered in JSON format")
rootCmd.PersistentFlags().StringVar(&GeneralConfig.EnvRootPath, "envRootPath", ".pipeline", "Root path to Piper pipeline shared environments")
rootCmd.PersistentFlags().StringVar(&GeneralConfig.StageName, "stageName", os.Getenv("STAGE_NAME"), "Name of the stage for which configuration should be included")
rootCmd.PersistentFlags().StringVar(&GeneralConfig.StepConfigJSON, "stepConfigJSON", os.Getenv("PIPER_stepConfigJSON"), "Step configuration in JSON format")
rootCmd.PersistentFlags().BoolVarP(&GeneralConfig.Verbose, "verbose", "v", false, "verbose output")
@ -71,6 +73,7 @@ func addRootFlags(rootCmd *cobra.Command) {
func PrepareConfig(cmd *cobra.Command, metadata *config.StepData, stepName string, options interface{}, openFile func(s string) (io.ReadCloser, error)) error {
filters := metadata.GetParameterFilters()
resourceParams := metadata.GetResourceParameters(GeneralConfig.EnvRootPath, "commonPipelineEnvironment")
flagValues := config.AvailableFlagValues(cmd, &filters)
@ -108,7 +111,7 @@ func PrepareConfig(cmd *cobra.Command, metadata *config.StepData, stepName strin
defaultConfig = append(defaultConfig, fc)
}
stepConfig, err = myConfig.GetStepConfig(flagValues, GeneralConfig.ParametersJSON, customConfig, defaultConfig, filters, metadata.Spec.Inputs.Parameters, GeneralConfig.StageName, stepName)
stepConfig, err = myConfig.GetStepConfig(flagValues, GeneralConfig.ParametersJSON, customConfig, defaultConfig, filters, metadata.Spec.Inputs.Parameters, resourceParams, GeneralConfig.StageName, stepName)
if err != nil {
return errors.Wrap(err, "retrieving step configuration failed")
}

View File

@ -3,6 +3,7 @@ package cmd
import (
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/spf13/cobra"
)
@ -10,11 +11,11 @@ type versionOptions struct {
}
var myVersionOptions versionOptions
var versionStepConfigJSON string
// VersionCommand Returns the version of the piper binary
func VersionCommand() *cobra.Command {
metadata := versionMetadata()
var createVersionCmd = &cobra.Command{
Use: "version",
Short: "Returns the version of the piper binary",
@ -25,6 +26,7 @@ func VersionCommand() *cobra.Command {
return PrepareConfig(cmd, &metadata, "version", &myVersionOptions, config.OpenPiperFile)
},
RunE: func(cmd *cobra.Command, args []string) error {
return version(myVersionOptions)
},
}

View File

@ -5,6 +5,7 @@ import (
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/spf13/cobra"
)
@ -25,11 +26,11 @@ type xsDeployOptions struct {
}
var myXsDeployOptions xsDeployOptions
var xsDeployStepConfigJSON string
// XsDeployCommand Performs xs deployment
func XsDeployCommand() *cobra.Command {
metadata := xsDeployMetadata()
var createXsDeployCmd = &cobra.Command{
Use: "xsDeploy",
Short: "Performs xs deployment",
@ -40,6 +41,7 @@ func XsDeployCommand() *cobra.Command {
return PrepareConfig(cmd, &metadata, "xsDeploy", &myXsDeployOptions, config.OpenPiperFile)
},
RunE: func(cmd *cobra.Command, args []string) error {
return xsDeploy(myXsDeployOptions)
},
}
@ -80,95 +82,108 @@ func xsDeployMetadata() config.StepData {
Inputs: config.StepInputs{
Parameters: []config.StepParameters{
{
Name: "deployOpts",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
Name: "deployOpts",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "operationIdLogPattern",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "deployIdLogPattern"}},
Name: "operationIdLogPattern",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "deployIdLogPattern"}},
},
{
Name: "mtaPath",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Name: "mtaPath",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "action",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
Name: "action",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "mode",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Name: "mode",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "operationId",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
Name: "operationId",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "apiUrl",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Name: "apiUrl",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "user",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Name: "user",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "password",
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: "org",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Name: "org",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "space",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Name: "space",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "loginOpts",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Name: "loginOpts",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "xsSessionFile",
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
Name: "xsSessionFile",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
},
},

View File

@ -82,7 +82,7 @@ func getDeepAliasValue(configMap map[string]interface{}, key string) interface{}
}
// GetStepConfig provides merged step configuration using defaults, config, if available
func (c *Config) GetStepConfig(flagValues map[string]interface{}, paramJSON string, configuration io.ReadCloser, defaults []io.ReadCloser, filters StepFilters, parameters []StepParameters, stageName, stepName string) (StepConfig, error) {
func (c *Config) GetStepConfig(flagValues map[string]interface{}, paramJSON string, configuration io.ReadCloser, defaults []io.ReadCloser, filters StepFilters, parameters []StepParameters, envParameters map[string]interface{}, stageName, stepName string) (StepConfig, error) {
var stepConfig StepConfig
var d PipelineDefaults
@ -126,6 +126,9 @@ func (c *Config) GetStepConfig(flagValues map[string]interface{}, paramJSON stri
stepConfig.mixIn(def.Steps[stepName], filters.Steps)
}
// merge parameters provided by Piper environment
stepConfig.mixIn(envParameters, filters.All)
// read config & merge - general -> steps -> stages
stepConfig.mixIn(c.General, filters.General)
stepConfig.mixIn(c.Steps[stepName], filters.Steps)

View File

@ -5,9 +5,12 @@ import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"github.com/SAP/jenkins-library/pkg/piperenv"
"github.com/stretchr/testify/assert"
)
@ -145,9 +148,26 @@ steps:
},
},
},
{
Name: "pe1",
Scope: []string{"STEPS"},
ResourceRef: []ResourceReference{{Name: "commonPipelineEnvironment", Param: "test_pe1"}},
},
}
stepConfig, err := c.GetStepConfig(flags, paramJSON, myConfig, defaults, filters, parameterMetadata, "stage1", "step1")
stepMeta := StepData{Spec: StepSpec{Inputs: StepInputs{Parameters: parameterMetadata}}}
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal("Failed to create temporary directory")
}
// clean up tmp dir
defer os.RemoveAll(dir)
piperenv.SetParameter(filepath.Join(dir, "commonPipelineEnvironment"), "test_pe1", "pe1_val")
stepConfig, err := c.GetStepConfig(flags, paramJSON, myConfig, defaults, filters, parameterMetadata, stepMeta.GetResourceParameters(dir, "commonPipelineEnvironment"), "stage1", "step1")
assert.Equal(t, nil, err, "error occured but none expected")
@ -163,7 +183,9 @@ steps:
"p7": "p7_flag",
"pd1": "pd1_dependent_default",
"pd2": "pd2_metadata_default",
"pe1": "pe1_val",
}
for k, v := range expected {
t.Run(k, func(t *testing.T) {
if stepConfig.Config[k] != v {
@ -191,7 +213,7 @@ steps:
c.openFile = customDefaultsOpenFileMock
stepConfig, err := c.GetStepConfig(nil, "", ioutil.NopCloser(strings.NewReader(testConfDefaults)), nil, StepFilters{General: []string{"p0"}}, nil, "stage1", "step1")
stepConfig, err := c.GetStepConfig(nil, "", ioutil.NopCloser(strings.NewReader(testConfDefaults)), nil, StepFilters{General: []string{"p0"}}, nil, nil, "stage1", "step1")
assert.NoError(t, err, "Error occured but no error expected")
assert.Equal(t, "p0_custom_default", stepConfig.Config["p0"])
@ -204,7 +226,7 @@ steps:
stepParams := []StepParameters{StepParameters{Name: "p0", Scope: []string{"GENERAL"}, Type: "string", Default: "p0_step_default", Aliases: []Alias{{Name: "p0_alias"}}}}
testConf := "general:\n p1: p1_conf"
stepConfig, err := c.GetStepConfig(nil, "", ioutil.NopCloser(strings.NewReader(testConf)), nil, StepFilters{General: []string{"p0", "p1"}}, stepParams, "stage1", "step1")
stepConfig, err := c.GetStepConfig(nil, "", ioutil.NopCloser(strings.NewReader(testConf)), nil, StepFilters{General: []string{"p0", "p1"}}, stepParams, nil, "stage1", "step1")
assert.NoError(t, err, "Error occured but no error expected")
assert.Equal(t, "p0_step_default", stepConfig.Config["p0"])
@ -214,7 +236,7 @@ steps:
t.Run("Failure case config", func(t *testing.T) {
var c Config
myConfig := ioutil.NopCloser(strings.NewReader("invalid config"))
_, err := c.GetStepConfig(nil, "", myConfig, nil, StepFilters{}, []StepParameters{}, "stage1", "step1")
_, err := c.GetStepConfig(nil, "", myConfig, nil, StepFilters{}, []StepParameters{}, nil, "stage1", "step1")
assert.EqualError(t, err, "failed to parse custom pipeline configuration: error unmarshalling \"invalid config\": error unmarshaling JSON: json: cannot unmarshal string into Go value of type config.Config", "default error expected")
})
@ -222,7 +244,7 @@ steps:
var c Config
myConfig := ioutil.NopCloser(strings.NewReader(""))
myDefaults := []io.ReadCloser{ioutil.NopCloser(strings.NewReader("invalid defaults"))}
_, err := c.GetStepConfig(nil, "", myConfig, myDefaults, StepFilters{}, []StepParameters{}, "stage1", "step1")
_, err := c.GetStepConfig(nil, "", myConfig, myDefaults, StepFilters{}, []StepParameters{}, nil, "stage1", "step1")
assert.EqualError(t, err, "failed to parse pipeline default configuration: error unmarshalling \"invalid defaults\": error unmarshaling JSON: json: cannot unmarshal string into Go value of type config.Config", "default error expected")
})

7
pkg/config/resources.go Normal file
View File

@ -0,0 +1,7 @@
package config
// InfluxField is the constant for an Influx field
const InfluxField = "field"
// InfluxTag is the constant for an Influx field
const InfluxTag = "tag"

View File

@ -5,6 +5,9 @@ import (
"fmt"
"io"
"io/ioutil"
"path/filepath"
"github.com/SAP/jenkins-library/pkg/piperenv"
"github.com/ghodss/yaml"
"github.com/pkg/errors"
@ -25,8 +28,8 @@ type StepMetadata struct {
// StepSpec defines the spec details for a step, like step inputs, containers, sidecars, ...
type StepSpec struct {
Inputs StepInputs `json:"inputs"`
// Outputs string `json:"description,omitempty"`
Inputs StepInputs `json:"inputs,omitempty"`
Outputs StepOutputs `json:"outputs,omitempty"`
Containers []Container `json:"containers,omitempty"`
Sidecars []Container `json:"sidecars,omitempty"`
}
@ -40,15 +43,22 @@ type StepInputs struct {
// StepParameters defines the parameters for a step
type StepParameters struct {
Name string `json:"name"`
Description string `json:"description"`
LongDescription string `json:"longDescription,omitempty"`
Scope []string `json:"scope"`
Type string `json:"type"`
Mandatory bool `json:"mandatory,omitempty"`
Default interface{} `json:"default,omitempty"`
Aliases []Alias `json:"aliases,omitempty"`
Conditions []Condition `json:"conditions,omitempty"`
Name string `json:"name"`
Description string `json:"description"`
LongDescription string `json:"longDescription,omitempty"`
ResourceRef []ResourceReference `json:"resourceRef,omitempty"`
Scope []string `json:"scope"`
Type string `json:"type"`
Mandatory bool `json:"mandatory,omitempty"`
Default interface{} `json:"default,omitempty"`
Aliases []Alias `json:"aliases,omitempty"`
Conditions []Condition `json:"conditions,omitempty"`
}
// ResourceReference defines the parameters of a resource reference
type ResourceReference struct {
Name string `json:"name"`
Param string `json:"param"`
}
// Alias defines a step input parameter alias
@ -59,10 +69,11 @@ type Alias struct {
// StepResources defines the resources to be provided by the step context, e.g. Jenkins pipeline
type StepResources struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Type string `json:"type,omitempty"`
Conditions []Condition `json:"conditions,omitempty"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Type string `json:"type,omitempty"`
Parameters []map[string]interface{} `json:"params,omitempty"`
Conditions []Condition `json:"conditions,omitempty"`
}
// StepSecrets defines the secrets to be provided by the step context, e.g. Jenkins pipeline
@ -72,10 +83,10 @@ type StepSecrets struct {
Type string `json:"type,omitempty"`
}
// StepOutputs defines the outputs of a step
//type StepOutputs struct {
// Name string `json:"name"`
//}
// StepOutputs defines the outputs of a step step, typically one or multiple resources
type StepOutputs struct {
Resources []StepResources `json:"resources,omitempty"`
}
// Container defines an execution container
type Container struct {
@ -318,6 +329,23 @@ func (m *StepData) GetContextDefaults(stepName string) (io.ReadCloser, error) {
return r, nil
}
// GetResourceParameters retrieves parameters from a named pipeline resource with a defined path
func (m *StepData) GetResourceParameters(path, name string) map[string]interface{} {
resourceParams := map[string]interface{}{}
for _, param := range m.Spec.Inputs.Parameters {
for _, res := range param.ResourceRef {
if res.Name == name {
if val := piperenv.GetParameter(filepath.Join(path, name), res.Param); len(val) > 0 {
resourceParams[param.Name] = val
}
}
}
}
return resourceParams
}
func envVarsAsStringSlice(envVars []EnvVar) []string {
e := []string{}
for _, v := range envVars {

View File

@ -4,6 +4,8 @@ import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
@ -442,3 +444,62 @@ func TestGetContextDefaults(t *testing.T) {
})
})
}
func TestGetResourceParameters(t *testing.T) {
tt := []struct {
in StepData
expected map[string]interface{}
}{
{
in: StepData{Spec: StepSpec{Inputs: StepInputs{}}},
expected: map[string]interface{}{},
},
{
in: StepData{
Spec: StepSpec{Inputs: StepInputs{Parameters: []StepParameters{
{Name: "param1"},
{Name: "param2"},
}}}},
expected: map[string]interface{}{},
},
{
in: StepData{
Spec: StepSpec{Inputs: StepInputs{Parameters: []StepParameters{
{Name: "param1", ResourceRef: []ResourceReference{}},
{Name: "param2", ResourceRef: []ResourceReference{}},
}}}},
expected: map[string]interface{}{},
},
{
in: StepData{
Spec: StepSpec{Inputs: StepInputs{Parameters: []StepParameters{
{Name: "param1", ResourceRef: []ResourceReference{{Name: "notAvailable", Param: "envparam1"}}},
{Name: "param2", ResourceRef: []ResourceReference{{Name: "commonPipelineEnvironment", Param: "envparam2"}}},
}}}},
expected: map[string]interface{}{"param2": "val2"},
},
}
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal("Failed to create temporary directory")
}
// clean up tmp dir
defer os.RemoveAll(dir)
cpeDir := filepath.Join(dir, "commonPipelineEnvironment")
err = os.MkdirAll(cpeDir, 0700)
if err != nil {
t.Fatal("Failed to create sub directory")
}
ioutil.WriteFile(filepath.Join(cpeDir, "envparam1"), []byte("val1"), 0700)
ioutil.WriteFile(filepath.Join(cpeDir, "envparam2"), []byte("val2"), 0700)
for run, test := range tt {
t.Run(fmt.Sprintf("Run %v", run), func(t *testing.T) {
got := test.in.GetResourceParameters(dir, "commonPipelineEnvironment")
assert.Equal(t, test.expected, got)
})
}
}

View File

@ -11,6 +11,7 @@ import (
"text/template"
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/piperutils"
)
type stepInfo struct {
@ -21,6 +22,7 @@ type stepInfo struct {
Long string
Metadata []config.StepParameters
OSImport bool
OutputResources []map[string]string
Short string
StepFunc string
StepName string
@ -30,11 +32,14 @@ type stepInfo struct {
const stepGoTemplate = `package cmd
import (
{{if .OSImport}}"os"{{end}}
{{ if .OSImport }}"os"{{ end }}
{{ if .OutputResources }}"fmt"{{ end }}
{{ if .OutputResources }}"path/filepath"{{ end }}
{{if .ExportPrefix}}{{ .ExportPrefix }} "github.com/SAP/jenkins-library/cmd"{{end}}
{{ if .ExportPrefix}}{{ .ExportPrefix }} "github.com/SAP/jenkins-library/cmd"{{ end -}}
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
{{ if .OutputResources }}"github.com/SAP/jenkins-library/pkg/piperenv"{{ end }}
"github.com/spf13/cobra"
)
@ -43,12 +48,18 @@ type {{ .StepName }}Options struct {
{{ $value.Name | golangName }} {{ $value.Type }} ` + "`json:\"{{$value.Name}},omitempty\"`" + `{{end}}
}
{{ range $notused, $oRes := .OutputResources }}
{{ index $oRes "def"}}
{{ end }}
var my{{ .StepName | title}}Options {{.StepName}}Options
var {{ .StepName }}StepConfigJSON string
// {{.CobraCmdFuncName}} {{.Short}}
func {{.CobraCmdFuncName}}() *cobra.Command {
metadata := {{ .StepName }}Metadata()
{{- range $notused, $oRes := .OutputResources }}
var {{ index $oRes "name" }} {{ index $oRes "objectname" }}{{ end }}
var {{.CreateCmdVar}} = &cobra.Command{
Use: "{{.StepName}}",
Short: "{{.Short}}",
@ -59,7 +70,15 @@ func {{.CobraCmdFuncName}}() *cobra.Command {
return {{if .ExportPrefix}}{{ .ExportPrefix }}.{{end}}PrepareConfig(cmd, &metadata, "{{ .StepName }}", &my{{ .StepName | title}}Options, config.OpenPiperFile)
},
RunE: func(cmd *cobra.Command, args []string) error {
return {{.StepName}}(my{{ .StepName | title }}Options)
{{ if .OutputResources -}}
handler := func() {
{{- range $notused, $oRes := .OutputResources }}
{{ index $oRes "name" }}.persist(GeneralConfig.EnvRootPath, "{{ index $oRes "name" }}"){{ end }}
}
log.DeferExitHandler(handler)
defer handler()
{{- end }}
return {{.StepName}}(my{{ .StepName | title }}Options{{ range $notused, $oRes := .OutputResources}}, &{{ index $oRes "name" }}{{ end }})
},
}
@ -84,6 +103,7 @@ func {{ .StepName }}Metadata() config.StepData {
{{- range $key, $value := .Metadata }}
{
Name: "{{ $value.Name }}",
ResourceRef: []config.ResourceReference{{ "{" }}{{ range $notused, $ref := $value.ResourceRef }}{{ "{" }}Name: "{{ $ref.Name }}", Param: "{{ $ref.Param }}"{{ "}" }},{{ end }}{{ "}" }},
Scope: []string{{ "{" }}{{ range $notused, $scope := $value.Scope }}"{{ $scope }}",{{ end }}{{ "}" }},
Type: "{{ $value.Type }}",
Mandatory: {{ $value.Mandatory }},
@ -116,6 +136,17 @@ func Test{{.CobraCmdFuncName}}(t *testing.T) {
}
`
const stepGoImplementationTemplate = `package cmd
import (
"github.com/SAP/jenkins-library/pkg/log"
)
func {{.StepName}}(config {{ .StepName }}Options{{ range $notused, $oRes := .OutputResources}}, {{ index $oRes "name" }} *{{ index $oRes "objectname" }} {{ end }}) error {
log.Entry().WithField("customKey", "customValue").Info("This is how you write a log message with a custom field ...")
return nil
}
`
// ProcessMetaFiles generates step coding based on step configuration provided in yaml files
func ProcessMetaFiles(metadataFiles []string, stepHelperData StepHelperData, docuHelperData DocuHelperData) error {
for key := range metadataFiles {
@ -141,7 +172,8 @@ func ProcessMetaFiles(metadataFiles []string, stepHelperData StepHelperData, doc
osImport, err = setDefaultParameters(&stepData)
checkError(err)
myStepInfo := getStepInfo(&stepData, osImport, stepHelperData.ExportPrefix)
myStepInfo, err := getStepInfo(&stepData, osImport, stepHelperData.ExportPrefix)
checkError(err)
step := stepTemplate(myStepInfo)
err = stepHelperData.WriteFile(fmt.Sprintf("cmd/%v_generated.go", stepData.Metadata.Name), step, 0644)
@ -150,6 +182,13 @@ func ProcessMetaFiles(metadataFiles []string, stepHelperData StepHelperData, doc
test := stepTestTemplate(myStepInfo)
err = stepHelperData.WriteFile(fmt.Sprintf("cmd/%v_generated_test.go", stepData.Metadata.Name), test, 0644)
checkError(err)
exists, _ := piperutils.FileExists(fmt.Sprintf("cmd/%v.go", stepData.Metadata.Name))
if !exists {
impl := stepImplementation(myStepInfo)
err = stepHelperData.WriteFile(fmt.Sprintf("cmd/%v.go", stepData.Metadata.Name), impl, 0644)
checkError(err)
}
} else {
err = generateStepDocumentation(stepData, docuHelperData)
if err != nil {
@ -209,18 +248,91 @@ func setDefaultParameters(stepData *config.StepData) (bool, error) {
return osImportRequired, nil
}
func getStepInfo(stepData *config.StepData, osImport bool, exportPrefix string) stepInfo {
func getStepInfo(stepData *config.StepData, osImport bool, exportPrefix string) (stepInfo, error) {
oRes, err := getOutputResourceDetails(stepData)
return stepInfo{
StepName: stepData.Metadata.Name,
CobraCmdFuncName: fmt.Sprintf("%vCommand", strings.Title(stepData.Metadata.Name)),
CreateCmdVar: fmt.Sprintf("create%vCmd", strings.Title(stepData.Metadata.Name)),
Short: stepData.Metadata.Description,
Long: stepData.Metadata.LongDescription,
Metadata: stepData.Spec.Inputs.Parameters,
FlagsFunc: fmt.Sprintf("add%vFlags", strings.Title(stepData.Metadata.Name)),
OSImport: osImport,
ExportPrefix: exportPrefix,
StepName: stepData.Metadata.Name,
CobraCmdFuncName: fmt.Sprintf("%vCommand", strings.Title(stepData.Metadata.Name)),
CreateCmdVar: fmt.Sprintf("create%vCmd", strings.Title(stepData.Metadata.Name)),
Short: stepData.Metadata.Description,
Long: stepData.Metadata.LongDescription,
Metadata: stepData.Spec.Inputs.Parameters,
FlagsFunc: fmt.Sprintf("add%vFlags", strings.Title(stepData.Metadata.Name)),
OSImport: osImport,
OutputResources: oRes,
ExportPrefix: exportPrefix,
},
err
}
func getOutputResourceDetails(stepData *config.StepData) ([]map[string]string, error) {
outputResources := []map[string]string{}
for _, res := range stepData.Spec.Outputs.Resources {
currentResource := map[string]string{}
currentResource["name"] = res.Name
switch res.Type {
case "piperEnvironment":
var envResource PiperEnvironmentResource
envResource.Name = res.Name
envResource.StepName = stepData.Metadata.Name
for _, param := range res.Parameters {
paramSections := strings.Split(fmt.Sprintf("%v", param["name"]), "/")
category := ""
name := paramSections[0]
if len(paramSections) > 1 {
name = strings.Join(paramSections[1:], "_")
category = paramSections[0]
if !contains(envResource.Categories, category) {
envResource.Categories = append(envResource.Categories, category)
}
}
envParam := PiperEnvironmentParameter{Category: category, Name: name}
envResource.Parameters = append(envResource.Parameters, envParam)
}
def, err := envResource.StructString()
if err != nil {
return outputResources, err
}
currentResource["def"] = def
currentResource["objectname"] = envResource.StructName()
outputResources = append(outputResources, currentResource)
case "influx":
var influxResource InfluxResource
influxResource.Name = res.Name
influxResource.StepName = stepData.Metadata.Name
for _, measurement := range res.Parameters {
influxMeasurement := InfluxMeasurement{Name: fmt.Sprintf("%v", measurement["name"])}
if fields, ok := measurement["fields"].([]interface{}); ok {
for _, field := range fields {
if fieldParams, ok := field.(map[string]interface{}); ok {
influxMeasurement.Fields = append(influxMeasurement.Fields, InfluxMetric{Name: fmt.Sprintf("%v", fieldParams["name"])})
}
}
}
if tags, ok := measurement["tags"].([]interface{}); ok {
for _, tag := range tags {
if tagParams, ok := tag.(map[string]interface{}); ok {
influxMeasurement.Tags = append(influxMeasurement.Tags, InfluxMetric{Name: fmt.Sprintf("%v", tagParams["name"])})
}
}
}
influxResource.Measurements = append(influxResource.Measurements, influxMeasurement)
}
def, err := influxResource.StructString()
if err != nil {
return outputResources, err
}
currentResource["def"] = def
currentResource["objectname"] = influxResource.StructName()
outputResources = append(outputResources, currentResource)
}
}
return outputResources, nil
}
// MetadataFiles provides a list of all step metadata files
@ -244,7 +356,7 @@ func stepTemplate(myStepInfo stepInfo) []byte {
funcMap := template.FuncMap{
"flagType": flagType,
"golangName": golangName,
"golangName": golangNameTitle,
"title": strings.Title,
"longName": longName,
}
@ -263,7 +375,7 @@ func stepTestTemplate(myStepInfo stepInfo) []byte {
funcMap := template.FuncMap{
"flagType": flagType,
"golangName": golangName,
"golangName": golangNameTitle,
"title": strings.Title,
}
@ -277,6 +389,22 @@ func stepTestTemplate(myStepInfo stepInfo) []byte {
return generatedCode.Bytes()
}
func stepImplementation(myStepInfo stepInfo) []byte {
funcMap := template.FuncMap{
"title": strings.Title,
}
tmpl, err := template.New("impl").Funcs(funcMap).Parse(stepGoImplementationTemplate)
checkError(err)
var generatedCode bytes.Buffer
err = tmpl.Execute(&generatedCode, myStepInfo)
checkError(err)
return generatedCode.Bytes()
}
func longName(long string) string {
l := strings.ReplaceAll(long, "`", "` + \"`\" + `")
l = strings.TrimSpace(l)
@ -290,7 +418,11 @@ func golangName(name string) string {
properName = strings.Replace(properName, "Id", "ID", -1)
properName = strings.Replace(properName, "Json", "JSON", -1)
properName = strings.Replace(properName, "json", "JSON", -1)
return strings.Title(properName)
return properName
}
func golangNameTitle(name string) string {
return strings.Title(golangName(name))
}
func flagType(paramType string) string {

View File

@ -20,6 +20,22 @@ func configOpenFileMock(name string) (io.ReadCloser, error) {
longDescription: |
Long Test description
spec:
outputs:
resources:
- name: commonPipelineEnvironment
type: piperEnvironment
params:
- name: artifactVersion
- name: git/commitId
- name: git/branch
- name: influxTest
type: influx
params:
- name: m1
fields:
- name: f1
tags:
- name: t1
inputs:
params:
- name: param0
@ -75,6 +91,7 @@ func TestProcessMetaFiles(t *testing.T) {
t.Fatalf("failed reading %v", goldenFilePath)
}
assert.Equal(t, expected, files["cmd/testStep_generated.go"])
t.Log(string(files["cmd/testStep_generated.go"]))
})
t.Run("test code", func(t *testing.T) {
@ -176,7 +193,9 @@ func TestGetStepInfo(t *testing.T) {
},
}
myStepInfo := getStepInfo(&stepData, true, "")
myStepInfo, err := getStepInfo(&stepData, true, "")
assert.NoError(t, err)
assert.Equal(t, "testStep", myStepInfo.StepName, "StepName incorrect")
assert.Equal(t, "TestStepCommand", myStepInfo.CobraCmdFuncName, "CobraCmdFuncName incorrect")
@ -203,7 +222,7 @@ func TestLongName(t *testing.T) {
}
}
func TestGolangName(t *testing.T) {
func TestGolangNameTitle(t *testing.T) {
tt := []struct {
input string
expected string
@ -217,7 +236,7 @@ func TestGolangName(t *testing.T) {
}
for k, v := range tt {
assert.Equal(t, v.expected, golangName(v.input), fmt.Sprintf("wrong golang name for run %v", k))
assert.Equal(t, v.expected, golangNameTitle(v.input), fmt.Sprintf("wrong golang name for run %v", k))
}
}

View File

@ -0,0 +1,191 @@
package helper
import (
"bytes"
"fmt"
"strings"
"text/template"
)
// PiperEnvironmentResource defines a piper environement resource which stores data across multiple pipeline steps
type PiperEnvironmentResource struct {
Name string
StepName string
Parameters []PiperEnvironmentParameter
Categories []string
}
// PiperEnvironmentParameter defines a parameter within the Piper environment
type PiperEnvironmentParameter struct {
Category string
Name string
}
const piperEnvStructTemplate = `type {{ .StepName }}{{ .Name | title}} struct {
{{ range $notused, $param := .Parameters }}
{{- if not $param.Category}}{{ $param.Name | golangName }} string{{ end }}
{{- end }}
{{- range $notused, $category := .Categories }}
{{ $category }} struct {
{{- range $notused, $param := $.Parameters }}
{{- if eq $category $param.Category }}
{{ $param.Name | golangName }} string
{{- end }}
{{- end }}
}
{{- end }}
}
func (p *{{ .StepName }}{{ .Name | title}}) persist(path, resourceName string) {
content := []struct{
category string
name string
value string
}{
{{- range $notused, $param := .Parameters }}
{{- if not $param.Category}}
{category: "", name: "{{ $param.Name }}", value: p.{{ $param.Name | golangName}}},
{{- else }}
{category: "{{ $param.Category }}", name: "{{ $param.Name }}", value: p.{{ $param.Category }}.{{ $param.Name | golangName}}},
{{- end }}
{{- end }}
}
errCount := 0
for _, param := range content {
err := piperenv.SetResourceParameter(path, resourceName, filepath.Join(param.category, param.name), param.value)
if err != nil {
log.Entry().WithError(err).Error("Error persisting piper environment.")
errCount++
}
}
if errCount > 0 {
os.Exit(1)
}
}`
// StructName returns the name of the influx resource struct
func (p *PiperEnvironmentResource) StructName() string {
return fmt.Sprintf("%v%v", p.StepName, strings.Title(p.Name))
}
// StructString returns the golang coding for the struct definition of the InfluxResource
func (p *PiperEnvironmentResource) StructString() (string, error) {
funcMap := template.FuncMap{
"title": strings.Title,
"golangName": golangName,
}
tmpl, err := template.New("resources").Funcs(funcMap).Parse(piperEnvStructTemplate)
if err != nil {
return "", err
}
var generatedCode bytes.Buffer
err = tmpl.Execute(&generatedCode, &p)
if err != nil {
return "", err
}
return string(generatedCode.Bytes()), nil
}
// InfluxResource defines an Influx resouece that holds measurement information for a pipeline run
type InfluxResource struct {
Name string
StepName string
Measurements []InfluxMeasurement
}
// InfluxMeasurement defines a measurement for Influx reporting which is defined via a step resource
type InfluxMeasurement struct {
Name string
Fields []InfluxMetric
Tags []InfluxMetric
}
// InfluxMetric defines a metric (column) in an influx measurement
type InfluxMetric struct {
Name string
}
// InfluxMetricContent defines the content of an Inflx metric
type InfluxMetricContent struct {
Measurement string
ValType string
Name string
Value *string
}
const influxStructTemplate = `type {{ .StepName }}{{ .Name | title}} struct {
{{- range $notused, $measurement := .Measurements }}
{{ $measurement.Name }} struct {
fields struct {
{{- range $notused, $field := $measurement.Fields }}
{{ $field.Name | golangName }} string
{{- end }}
}
tags struct {
{{- range $notused, $tag := $measurement.Tags }}
{{ $tag.Name | golangName }} string
{{- end }}
}
}
{{- end }}
}
func (i *{{ .StepName }}{{ .Name | title}}) persist(path, resourceName string) {
measurementContent := []struct{
measurement string
valType string
name string
value string
}{
{{- range $notused, $measurement := .Measurements }}
{{- range $notused, $field := $measurement.Fields }}
{valType: config.InfluxField, measurement: "{{ $measurement.Name }}" , name: "{{ $field.Name }}", value: i.{{ $measurement.Name }}.fields.{{ $field.Name | golangName }}},
{{- end }}
{{- range $notused, $tag := $measurement.Tags }}
{valType: config.InfluxTag, measurement: "{{ $measurement.Name }}" , name: "{{ $tag.Name }}", value: i.{{ $measurement.Name }}.tags.{{ $tag.Name | golangName }}},
{{- end }}
{{- end }}
}
errCount := 0
for _, metric := range measurementContent {
err := piperenv.SetResourceParameter(path, resourceName, filepath.Join(metric.measurement, fmt.Sprintf("%vs", metric.valType), metric.name), metric.value)
if err != nil {
log.Entry().WithError(err).Error("Error persisting influx environment.")
errCount++
}
}
if errCount > 0 {
os.Exit(1)
}
}`
// StructString returns the golang coding for the struct definition of the InfluxResource
func (i *InfluxResource) StructString() (string, error) {
funcMap := template.FuncMap{
"title": strings.Title,
"golangName": golangName,
}
tmpl, err := template.New("resources").Funcs(funcMap).Parse(influxStructTemplate)
if err != nil {
return "", err
}
var generatedCode bytes.Buffer
err = tmpl.Execute(&generatedCode, &i)
if err != nil {
return "", err
}
return string(generatedCode.Bytes()), nil
}
// StructName returns the name of the influx resource struct
func (i *InfluxResource) StructName() string {
return fmt.Sprintf("%v%v", i.StepName, strings.Title(i.Name))
}

View File

@ -0,0 +1,94 @@
package helper
import (
"fmt"
"github.com/stretchr/testify/assert"
"testing"
)
func TestStructString(t *testing.T) {
tt := []struct {
in InfluxResource
expected string
}{
{
in: InfluxResource{
Name: "TestInflux",
StepName: "TestStep",
Measurements: []InfluxMeasurement{
{
Name: "m1",
Fields: []InfluxMetric{{Name: "field1_1"}, {Name: "field1_2"}},
Tags: []InfluxMetric{{Name: "tag1_1"}, {Name: "tag1_2"}},
},
{
Name: "m2",
Fields: []InfluxMetric{{Name: "field2_1"}, {Name: "field2_2"}},
Tags: []InfluxMetric{{Name: "tag2_1"}, {Name: "tag2_2"}},
},
},
},
expected: `type TestStepTestInflux struct {
m1 struct {
fields struct {
field1_1 string
field1_2 string
}
tags struct {
tag1_1 string
tag1_2 string
}
}
m2 struct {
fields struct {
field2_1 string
field2_2 string
}
tags struct {
tag2_1 string
tag2_2 string
}
}
}
func (i *TestStepTestInflux) persist(path, resourceName string) {
measurementContent := []struct{
measurement string
valType string
name string
value string
}{
{valType: config.InfluxField, measurement: "m1" , name: "field1_1", value: i.m1.fields.field1_1},
{valType: config.InfluxField, measurement: "m1" , name: "field1_2", value: i.m1.fields.field1_2},
{valType: config.InfluxTag, measurement: "m1" , name: "tag1_1", value: i.m1.tags.tag1_1},
{valType: config.InfluxTag, measurement: "m1" , name: "tag1_2", value: i.m1.tags.tag1_2},
{valType: config.InfluxField, measurement: "m2" , name: "field2_1", value: i.m2.fields.field2_1},
{valType: config.InfluxField, measurement: "m2" , name: "field2_2", value: i.m2.fields.field2_2},
{valType: config.InfluxTag, measurement: "m2" , name: "tag2_1", value: i.m2.tags.tag2_1},
{valType: config.InfluxTag, measurement: "m2" , name: "tag2_2", value: i.m2.tags.tag2_2},
}
errCount := 0
for _, metric := range measurementContent {
err := piperenv.SetResourceParameter(path, resourceName, filepath.Join(metric.measurement, fmt.Sprintf("%vs", metric.valType), metric.name), metric.value)
if err != nil {
log.Entry().WithError(err).Error("Error persisting influx environment.")
errCount++
}
}
if errCount > 0 {
os.Exit(1)
}
}`,
},
}
for run, test := range tt {
t.Run(fmt.Sprintf("Run %v", run), func(t *testing.T) {
got, err := test.in.StructString()
assert.NoError(t, err)
assert.Equal(t, test.expected, got)
})
}
}

View File

@ -2,10 +2,12 @@ package cmd
import (
"os"
"fmt"
"path/filepath"
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperenv"
"github.com/spf13/cobra"
)
@ -15,12 +17,83 @@ type testStepOptions struct {
Param2 string `json:"param2,omitempty"`
}
type testStepCommonPipelineEnvironment struct {
artifactVersion string
git struct {
commitID string
branch string
}
}
func (p *testStepCommonPipelineEnvironment) persist(path, resourceName string) {
content := []struct{
category string
name string
value string
}{
{category: "", name: "artifactVersion", value: p.artifactVersion},
{category: "git", name: "commitId", value: p.git.commitID},
{category: "git", name: "branch", value: p.git.branch},
}
errCount := 0
for _, param := range content {
err := piperenv.SetResourceParameter(path, resourceName, filepath.Join(param.category, param.name), param.value)
if err != nil {
log.Entry().WithError(err).Error("Error persisting piper environment.")
errCount++
}
}
if errCount > 0 {
os.Exit(1)
}
}
type testStepInfluxTest struct {
m1 struct {
fields struct {
f1 string
}
tags struct {
t1 string
}
}
}
func (i *testStepInfluxTest) persist(path, resourceName string) {
measurementContent := []struct{
measurement string
valType string
name string
value string
}{
{valType: config.InfluxField, measurement: "m1" , name: "f1", value: i.m1.fields.f1},
{valType: config.InfluxTag, measurement: "m1" , name: "t1", value: i.m1.tags.t1},
}
errCount := 0
for _, metric := range measurementContent {
err := piperenv.SetResourceParameter(path, resourceName, filepath.Join(metric.measurement, fmt.Sprintf("%vs", metric.valType), metric.name), metric.value)
if err != nil {
log.Entry().WithError(err).Error("Error persisting influx environment.")
errCount++
}
}
if errCount > 0 {
os.Exit(1)
}
}
var myTestStepOptions testStepOptions
var testStepStepConfigJSON string
// TestStepCommand Test description
func TestStepCommand() *cobra.Command {
metadata := testStepMetadata()
var commonPipelineEnvironment testStepCommonPipelineEnvironment
var influxTest testStepInfluxTest
var createTestStepCmd = &cobra.Command{
Use: "testStep",
Short: "Test description",
@ -31,7 +104,13 @@ func TestStepCommand() *cobra.Command {
return PrepareConfig(cmd, &metadata, "testStep", &myTestStepOptions, config.OpenPiperFile)
},
RunE: func(cmd *cobra.Command, args []string) error {
return testStep(myTestStepOptions)
handler := func() {
commonPipelineEnvironment.persist(GeneralConfig.EnvRootPath, "commonPipelineEnvironment")
influxTest.persist(GeneralConfig.EnvRootPath, "influxTest")
}
log.DeferExitHandler(handler)
defer handler()
return testStep(myTestStepOptions, &commonPipelineEnvironment, &influxTest)
},
}
@ -56,6 +135,7 @@ func testStepMetadata() config.StepData {
Parameters: []config.StepParameters{
{
Name: "param0",
ResourceRef: []config.ResourceReference{},
Scope: []string{"GENERAL","PARAMETERS",},
Type: "string",
Mandatory: true,
@ -63,6 +143,7 @@ func testStepMetadata() config.StepData {
},
{
Name: "param1",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS",},
Type: "string",
Mandatory: false,
@ -70,6 +151,7 @@ func testStepMetadata() config.StepData {
},
{
Name: "param2",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS",},
Type: "string",
Mandatory: true,

View File

@ -28,3 +28,8 @@ func SetVerbose(verbose bool) {
func SetStepName(stepName string) {
logger = Entry().WithField("stepName", stepName)
}
// DeferExitHandler registers a logrus exit handler to allow cleanup activities.
func DeferExitHandler(handler func()) {
logrus.DeferExitHandler(handler)
}

View File

@ -0,0 +1,62 @@
package piperenv
import (
"io/ioutil"
"os"
"path/filepath"
"github.com/SAP/jenkins-library/pkg/log"
)
// This file contains functions used to read/write pipeline environment data from/to disk.
// The content of a written file is the value. For the custom parameters this could for example also be a JSON representation of a more complex value.
// SetResourceParameter sets a resource parameter in the environment stored in the file system
func SetResourceParameter(path, resourceName, paramName, value string) error {
paramPath := filepath.Join(path, resourceName, paramName)
return writeToDisk(paramPath, []byte(value))
}
// GetResourceParameter reads a resource parameter from the environment stored in the file system
func GetResourceParameter(path, resourceName, paramName string) string {
paramPath := filepath.Join(path, resourceName, paramName)
return readFromDisk(paramPath)
}
// SetParameter sets any parameter in the pipeline environment or another environment stored in the file system
func SetParameter(path, name, value string) error {
paramPath := filepath.Join(path, name)
return writeToDisk(paramPath, []byte(value))
}
// GetParameter reads any parameter from the pipeline environment or another environment stored in the file system
func GetParameter(path, name string) string {
paramPath := filepath.Join(path, name)
return readFromDisk(paramPath)
}
func writeToDisk(filename string, data []byte) error {
if _, err := os.Stat(filepath.Dir(filename)); os.IsNotExist(err) {
log.Entry().Debugf("Creating directory: %v", filepath.Dir(filename))
os.MkdirAll(filepath.Dir(filename), 0700)
}
//ToDo: make sure to not overwrite file but rather add another file? Create error if already existing?
if len(data) > 0 {
log.Entry().Debugf("Writing file to disk: %v", filename)
return ioutil.WriteFile(filename, data, 0700)
}
return nil
}
func readFromDisk(filename string) string {
//ToDo: if multiple files exist, read from latest file
log.Entry().Debugf("Reading file from disk: %v", filename)
v, err := ioutil.ReadFile(filename)
val := string(v)
if err != nil {
val = ""
}
return val
}

View File

@ -0,0 +1,51 @@
package piperenv
import (
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSetResourceParameter(t *testing.T) {
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal("Failed to create temporary directory")
}
// clean up tmp dir
defer os.RemoveAll(dir)
err = SetResourceParameter(dir, "testRes", "testParam", "testVal")
assert.NoError(t, err, "Error occured but none expected")
assert.Equal(t, "testVal", GetResourceParameter(dir, "testRes", "testParam"))
}
func TestSetParameter(t *testing.T) {
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal("Failed to create temporary directory")
}
// clean up tmp dir
defer os.RemoveAll(dir)
err = SetParameter(dir, "testParam", "testVal")
assert.NoError(t, err, "Error occured but none expected")
assert.Equal(t, "testVal", GetParameter(dir, "testParam"))
}
func TestReadFromDisk(t *testing.T) {
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal("Failed to create temporary directory")
}
// clean up tmp dir
defer os.RemoveAll(dir)
assert.Equal(t, "", GetParameter(dir, "testParamNotExistingYet"))
}

View File

@ -15,134 +15,147 @@ metadata:
spec:
inputs:
secrets:
- name: githubTokenCredentialsId
description: Jenkins 'Secret text' credentials ID containing token to authenticate to GitHub.
type: jenkins
- name: githubTokenCredentialsId
description: Jenkins 'Secret text' credentials ID containing token to authenticate to GitHub.
type: jenkins
resources:
- name: commonPipelineEnvironment
resourceSpec:
type: piperEnvironment
params:
- name: addClosedIssues
description: 'If set to `true`, closed issues and merged pull-requests since the last release will added below the `releaseBodyHeader`'
scope:
- PARAMETERS
- STAGES
- STEPS
type: bool
default: false
- name: addDeltaToLastRelease
description: 'If set to `true`, a link will be added to the relese information that brings up all commits since the last release.'
scope:
- PARAMETERS
- STAGES
- STEPS
type: bool
default: false
- name: assetPath
description: Path to a release asset which should be uploaded to the list of release assets.
scope:
- PARAMETERS
- STAGES
- STEPS
type: string
- name: commitish
description: 'Target git commitish for the release'
scope:
- PARAMETERS
- STAGES
- STEPS
type: string
default: "master"
- name: excludeLabels
description: 'Allows to exclude issues with dedicated list of labels.'
scope:
- PARAMETERS
- STAGES
- STEPS
type: '[]string'
- name: apiUrl
aliases:
- name: githubApiUrl
description: Set the GitHub API url.
scope:
- GENERAL
- PARAMETERS
- STAGES
- STEPS
type: string
default: https://api.github.com
mandatory: true
- name: owner
aliases:
- name: githubOrg
description: 'Set the GitHub organization.'
scope:
- PARAMETERS
- STAGES
- STEPS
type: string
mandatory: true
- name: repository
aliases:
- name: githubRepo
description: 'Set the GitHub repository.'
scope:
- PARAMETERS
- STAGES
- STEPS
type: string
mandatory: true
- name: serverUrl
aliases:
- name: githubServerUrl
description: 'GitHub server url for end-user access.'
scope:
- GENERAL
- PARAMETERS
- STAGES
- STEPS
type: string
default: https://github.com
mandatory: true
- name: token
aliases:
- name: githubToken
description: 'GitHub personal access token as per https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line'
scope:
- GENERAL
- PARAMETERS
- STAGES
- STEPS
type: string
mandatory: true
- name: uploadUrl
aliases:
- name: githubUploadUrl
description: Set the GitHub API url.
scope:
- GENERAL
- PARAMETERS
- STAGES
- STEPS
type: string
default: https://uploads.github.com
mandatory: true
- name: labels
description: 'Labels to include in issue search.'
scope:
- PARAMETERS
- STAGES
- STEPS
type: '[]string'
- name: releaseBodyHeader
description: Content which will appear for the release.
scope:
- PARAMETERS
- STAGES
- STEPS
type: string
- name: version
description: 'Define the version number which will be written as tag as well as release name.'
scope:
- PARAMETERS
- STAGES
- STEPS
type: string
mandatory: true
- name: addClosedIssues
description: 'If set to `true`, closed issues and merged pull-requests since the last release will added below the `releaseBodyHeader`'
scope:
- PARAMETERS
- STAGES
- STEPS
type: bool
default: false
- name: addDeltaToLastRelease
description: 'If set to `true`, a link will be added to the relese information that brings up all commits since the last release.'
scope:
- PARAMETERS
- STAGES
- STEPS
type: bool
default: false
- name: apiUrl
aliases:
- name: githubApiUrl
description: Set the GitHub API url.
scope:
- GENERAL
- PARAMETERS
- STAGES
- STEPS
type: string
default: https://api.github.com
mandatory: true
- name: assetPath
description: Path to a release asset which should be uploaded to the list of release assets.
scope:
- PARAMETERS
- STAGES
- STEPS
type: string
- name: commitish
description: 'Target git commitish for the release'
scope:
- PARAMETERS
- STAGES
- STEPS
type: string
default: "master"
- name: excludeLabels
description: 'Allows to exclude issues with dedicated list of labels.'
scope:
- PARAMETERS
- STAGES
- STEPS
type: '[]string'
- name: labels
description: 'Labels to include in issue search.'
scope:
- PARAMETERS
- STAGES
- STEPS
type: '[]string'
- name: owner
aliases:
- name: githubOrg
description: 'Set the GitHub organization.'
resourceRef:
- name: commonPipelineEnvironment
param: github/owner
scope:
- PARAMETERS
- STAGES
- STEPS
type: string
mandatory: true
- name: releaseBodyHeader
description: Content which will appear for the release.
scope:
- PARAMETERS
- STAGES
- STEPS
type: string
- name: repository
aliases:
- name: githubRepo
description: 'Set the GitHub repository.'
resourceRef:
- name: commonPipelineEnvironment
param: github/repository
scope:
- PARAMETERS
- STAGES
- STEPS
type: string
mandatory: true
- name: serverUrl
aliases:
- name: githubServerUrl
description: 'GitHub server url for end-user access.'
scope:
- GENERAL
- PARAMETERS
- STAGES
- STEPS
type: string
default: https://github.com
mandatory: true
- name: token
aliases:
- name: githubToken
description: 'GitHub personal access token as per https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line'
scope:
- GENERAL
- PARAMETERS
- STAGES
- STEPS
type: string
mandatory: true
- name: uploadUrl
aliases:
- name: githubUploadUrl
description: Set the GitHub API url.
scope:
- GENERAL
- PARAMETERS
- STAGES
- STEPS
type: string
default: https://uploads.github.com
mandatory: true
- name: version
description: 'Define the version number which will be written as tag as well as release name.'
resourceRef:
- name: commonPipelineEnvironment
param: artifactVersion
scope:
- PARAMETERS
- STAGES
- STEPS
type: string
mandatory: true

View File

@ -2,20 +2,27 @@ import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import util.BasePiperTest
import util.JenkinsFileExistsRule
import util.JenkinsWriteFileRule
import util.JenkinsReadYamlRule
import util.Rules
import static org.hamcrest.CoreMatchers.is
import static org.hamcrest.Matchers.contains
import static org.hamcrest.Matchers.hasItem
import static org.junit.Assert.assertThat
class CommonPipelineEnvironmentTest extends BasePiperTest {
private JenkinsWriteFileRule writeFileRule = new JenkinsWriteFileRule(this)
private JenkinsFileExistsRule fileExistsRule = new JenkinsFileExistsRule(this, [])
@Rule
public RuleChain rules = Rules
.getCommonRules(this)
.around(new JenkinsReadYamlRule(this)
)
.around(new JenkinsReadYamlRule(this))
.around(writeFileRule)
.around(fileExistsRule)
@Test
void testCustomValueList() {
@ -34,4 +41,14 @@ class CommonPipelineEnvironmentTest extends BasePiperTest {
assertThat(nullScript.commonPipelineEnvironment.getValue('myList').key1, is('val1'))
assertThat(nullScript.commonPipelineEnvironment.getValue('myList').key2, is('val2'))
}
@Test
void testWritetoDisk() {
nullScript.commonPipelineEnvironment.artifactVersion = '1.0.0'
nullScript.commonPipelineEnvironment.setValue('custom1', 'customVal1')
nullScript.commonPipelineEnvironment.writeToDisk(nullScript)
assertThat(writeFileRule.files['.pipeline/commonPipelineEnvironment/artifactVersion'], is('1.0.0'))
assertThat(writeFileRule.files['.pipeline/commonPipelineEnvironment/custom/custom1'], is('customVal1'))
}
}

View File

@ -5,6 +5,7 @@ import org.junit.rules.ExpectedException
import org.junit.rules.RuleChain
import util.BasePiperTest
import util.JenkinsCredentialsRule
import util.JenkinsFileExistsRule
import util.JenkinsLoggingRule
import util.JenkinsReadJsonRule
import util.JenkinsReadYamlRule
@ -23,6 +24,7 @@ class GithubPublishReleaseTest extends BasePiperTest {
private JenkinsShellCallRule shellCallRule = new JenkinsShellCallRule(this)
private JenkinsStepRule stepRule = new JenkinsStepRule(this)
private JenkinsWriteFileRule writeFileRule = new JenkinsWriteFileRule(this)
private JenkinsFileExistsRule fileExistsRule = new JenkinsFileExistsRule(this, [])
private List withEnvArgs = []
@ -35,6 +37,7 @@ class GithubPublishReleaseTest extends BasePiperTest {
.around(shellCallRule)
.around(stepRule)
.around(writeFileRule)
.around(fileExistsRule)
@Before
void init() {
@ -58,26 +61,6 @@ class GithubPublishReleaseTest extends BasePiperTest {
// asserts
assertThat(writeFileRule.files['metadata/githubrelease.yaml'], containsString('name: githubPublishRelease'))
assertThat(withEnvArgs[0], allOf(startsWith('PIPER_parametersJSON'), containsString('"testParam":"This is test content"')))
assertThat(withEnvArgs[1], is('PIPER_owner='))
assertThat(withEnvArgs[2], is('PIPER_repository='))
assertThat(withEnvArgs[3], is('PIPER_version='))
assertThat(shellCallRule.shell[1], is('./piper githubPublishRelease --token thisIsATestToken'))
}
@Test
void testGithubPublishReleaseWithEnv() {
nullScript.commonPipelineEnvironment.setArtifactVersion('1.0.0')
nullScript.commonPipelineEnvironment.setGithubOrg('TestOrg')
nullScript.commonPipelineEnvironment.setGithubRepo('TestRepo')
stepRule.step.githubPublishRelease(
juStabUtils: utils,
script: nullScript
)
// asserts
assertThat(withEnvArgs[1], is('PIPER_owner=TestOrg'))
assertThat(withEnvArgs[2], is('PIPER_repository=TestRepo'))
assertThat(withEnvArgs[3], is('PIPER_version=1.0.0'))
}
}

View File

@ -40,13 +40,13 @@ class JenkinsFileExistsRule implements TestRule {
void evaluate() throws Throwable {
testInstance.helper.registerAllowedMethod('fileExists', [String.class], {s ->
queriedFiles.add(s)
return s in existingFiles
queriedFiles.add(s.toString())
return s.toString() in existingFiles
})
testInstance.helper.registerAllowedMethod('fileExists', [Map.class], {m ->
queriedFiles.add(m.file)
return m.file in existingFiles}
queriedFiles.add(m.file.toString())
return m.file.toString() in existingFiles}
)
base.evaluate()

View File

@ -144,8 +144,74 @@ class commonPipelineEnvironment implements Serializable {
config = ConfigurationMerger.merge(configuration.get('stages')?.get(stageName) ?: [:], null, config)
return config
}
void writeToDisk(script) {
def files = [
[filename: '.pipeline/commonPipelineEnvironment/artifactVersion', content: artifactVersion],
[filename: '.pipeline/commonPipelineEnvironment/github/owner', content: githubOrg],
[filename: '.pipeline/commonPipelineEnvironment/github/repository', content: githubRepo],
[filename: '.pipeline/commonPipelineEnvironment/git/branch', content: gitBranch],
[filename: '.pipeline/commonPipelineEnvironment/git/commitId', content: gitCommitId],
[filename: '.pipeline/commonPipelineEnvironment/git/commitMessage', content: gitCommitMessage],
]
files.each({f ->
if (f.content && !script.fileExists(f.filename)) {
script.writeFile file: f.filename, text: f.content
}
})
valueMap.each({key, value ->
def fileName = ".pipeline/commonPipelineEnvironment/custom/${key}"
if (value && !script.fileExists(fileName)) {
//ToDo: check for value type and act accordingly?
script.writeFile file: fileName, text: value
}
})
}
void readFromDisk() {
def file = '.pipeline/commonPipelineEnvironment/artifactVersion'
if (fileExists(file)) {
artifactVersion = readFile(file)
}
file = '.pipeline/commonPipelineEnvironment/github/owner'
if (fileExists(file)) {
githubOrg = readFile(file)
}
file = '.pipeline/commonPipelineEnvironment/github/repository'
if (fileExists(file)) {
githubRepo = readFile(file)
}
file = '.pipeline/commonPipelineEnvironment/git/branch'
if (fileExists(file)) {
gitBranch = readFile(file)
}
file = '.pipeline/commonPipelineEnvironment/git/commitId'
if (fileExists(file)) {
gitCommitId = readFile(file)
}
file = '.pipeline/commonPipelineEnvironment/git/commitMessage'
if (fileExists(file)) {
gitCommitMessage = readFile(file)
}
def customValues = findFiles(glob: '.pipeline/commonPipelineEnvironment/custom/*')
customValues.each({f ->
def fileName = f.getName()
def param = fileName.split('/')[fileName.split('\\/').size()-1]
valueMap[param] = readFile(f.getPath())
})
}
List getCustomDefaults() {
DefaultValueCache.getInstance().getCustomDefaults()
}
}

View File

@ -23,14 +23,12 @@ void call(Map parameters = [:]) {
new PiperGoUtils(this, utils).unstashPiperBin()
utils.unstash('pipelineConfigAndTests')
script.commonPipelineEnvironment.writeToDisk(script)
writeFile(file: METADATA_FILE, text: libraryResource(METADATA_FILE))
withEnv([
"PIPER_parametersJSON=${groovy.json.JsonOutput.toJson(parameters)}",
"PIPER_owner=${script.commonPipelineEnvironment.getGithubOrg()?:''}",
"PIPER_repository=${script.commonPipelineEnvironment.getGithubRepo()?:''}",
"PIPER_version=${script.commonPipelineEnvironment.getArtifactVersion()?:''}"
]) {
// get context configuration
config = readJSON (text: sh(returnStdout: true, script: "./piper getConfig --contextConfig --stepMetadata '${METADATA_FILE}'"))