diff --git a/cmd/abapAddonAssemblyKitCheck.go b/cmd/abapAddonAssemblyKitCheck.go
new file mode 100644
index 0000000000..923c27631c
--- /dev/null
+++ b/cmd/abapAddonAssemblyKitCheck.go
@@ -0,0 +1,94 @@
+package cmd
+
+import (
+ "github.com/SAP/jenkins-library/pkg/abap/aakaas"
+ abapbuild "github.com/SAP/jenkins-library/pkg/abap/build"
+ "github.com/SAP/jenkins-library/pkg/abaputils"
+ "github.com/SAP/jenkins-library/pkg/log"
+ "github.com/SAP/jenkins-library/pkg/piperutils"
+ "github.com/SAP/jenkins-library/pkg/telemetry"
+)
+
+func abapAddonAssemblyKitCheck(config abapAddonAssemblyKitCheckOptions, telemetryData *telemetry.CustomData, commonPipelineEnvironment *abapAddonAssemblyKitCheckCommonPipelineEnvironment) {
+ utils := aakaas.NewAakBundle()
+
+ err := runAbapAddonAssemblyKitCheck(&config, telemetryData, utils, commonPipelineEnvironment)
+ if err != nil {
+ log.Entry().WithError(err).Fatal("step execution failed")
+ }
+}
+
+func runAbapAddonAssemblyKitCheck(config *abapAddonAssemblyKitCheckOptions, telemetryData *telemetry.CustomData, utils aakaas.AakUtils, commonPipelineEnvironment *abapAddonAssemblyKitCheckCommonPipelineEnvironment) error {
+
+ log.Entry().Info("╔═══════════════════════════╗")
+ log.Entry().Info("║ abapAddonAssemblyKitCheck ║")
+ log.Entry().Info("╚═══════════════════════════╝")
+
+ conn := new(abapbuild.Connector)
+ if err := conn.InitAAKaaS(config.AbapAddonAssemblyKitEndpoint, config.Username, config.Password, utils, "", config.AbapAddonAssemblyKitCertificateFile, config.AbapAddonAssemblyKitCertificatePass); err != nil {
+ return err
+ }
+
+ log.Entry().Infof("reading addonDescriptor (aka addon.yml) file: %s", config.AddonDescriptorFileName)
+ addonDescriptor, err := utils.ReadAddonDescriptor(config.AddonDescriptorFileName)
+ if err != nil {
+ return err
+ }
+ log.Entry().Info("building product modelling (and resolving potential wildcards)")
+ pvh, err := aakaas.NewProductVersionHeader(&addonDescriptor, conn)
+ if err != nil {
+ return err
+ }
+ printProductVersionHeader(*pvh)
+
+ log.Entry().Info("calling AAKaaS to check product modelling...")
+ if err := pvh.CheckAndResolveVersion(conn); err != nil {
+ return err
+ }
+ log.Entry().Info("... success!")
+ pvh.SyncAddonDescriptorVersionFields(&addonDescriptor)
+ log.Entry().Info("resolved version fields:")
+ printAddonDescriptorVersionFields(addonDescriptor)
+ log.Entry().Info("transferring addonDescriptor to commonPipelineEnvironment for usage by subsequent steps of the pipeline")
+ commonPipelineEnvironment.abap.addonDescriptor = string(addonDescriptor.AsJSON())
+
+ publishAddonYaml(config, utils)
+ return nil
+}
+
+func printProductVersionHeader(pvh aakaas.ProductVersionHeader) {
+ logLine30 := "──────────────────────────────"
+ log.Entry().Infof("┌─%-30v─┬─%-30v─┐", logLine30, logLine30)
+ log.Entry().Infof("│ %-30v │ %-30v │", "Product Name", pvh.ProductName)
+ log.Entry().Infof("│ %-30v │ %-30v │", "Product Version", pvh.SemanticProductVersion)
+ log.Entry().Infof("├─%-30v─┼─%-30v─┤", logLine30, logLine30)
+ log.Entry().Infof("│ %-30v │ %-30v │", "Software Component Name", "Software Component Version")
+ log.Entry().Infof("├─%-30v─┼─%-30v─┤", logLine30, logLine30)
+ for _, pvc := range pvh.Content {
+ log.Entry().Infof("│ %-30v │ %-30v │", pvc.SoftwareComponentName, pvc.SemanticSoftwareComponentVersion)
+ }
+ log.Entry().Infof("└─%-30v─┴─%-30v─┘", logLine30, logLine30)
+}
+
+func printAddonDescriptorVersionFields(addonDescriptor abaputils.AddonDescriptor) {
+ logLine30 := "──────────────────────────────"
+ logLine4 := "────"
+ log.Entry().Infof("┌─%-30v─┬─%-4v─┬─%-4v─┬─%-4v─┐", logLine30, logLine4, logLine4, logLine4)
+ log.Entry().Infof("│ %-30v │ %-4v │ %-4v │ %-4v │", "Name", "Vers", "SP", "Pat.")
+ log.Entry().Infof("├─%-30v─┼─%-4v─┼─%-4v─┼─%-4v─┤", logLine30, logLine4, logLine4, logLine4)
+ log.Entry().Infof("│ %-30v │ %-4v │ %-4v │ %-4v │", addonDescriptor.AddonProduct, addonDescriptor.AddonVersion, addonDescriptor.AddonSpsLevel, addonDescriptor.AddonPatchLevel)
+ for _, repo := range addonDescriptor.Repositories {
+ log.Entry().Infof("│ %-30v │ %-4v │ %-4v │ %-4v │", repo.Name, repo.Version, repo.SpLevel, repo.PatchLevel)
+ }
+ log.Entry().Infof("└─%-30v─┴─%-4v─┴─%-4v─┴─%-4v─┘", logLine30, logLine4, logLine4, logLine4)
+}
+
+func publishAddonYaml(config *abapAddonAssemblyKitCheckOptions, utils aakaas.AakUtils) {
+ var filesToPublish []piperutils.Path
+ log.Entry().Infof("adding %s to be published", config.AddonDescriptorFileName)
+ filesToPublish = append(filesToPublish, piperutils.Path{Target: config.AddonDescriptorFileName, Name: "AddonDescriptor", Mandatory: true})
+ log.Entry().Infof("publishing %v files", len(filesToPublish))
+ if err := piperutils.PersistReportsAndLinks("abapAddonAssemblyKitCheckPV", "", utils, filesToPublish, nil); err != nil {
+ log.Entry().WithError(err).Error("failed to persist report information")
+ }
+}
diff --git a/cmd/abapAddonAssemblyKitCheck_generated.go b/cmd/abapAddonAssemblyKitCheck_generated.go
new file mode 100644
index 0000000000..e153d4e165
--- /dev/null
+++ b/cmd/abapAddonAssemblyKitCheck_generated.go
@@ -0,0 +1,295 @@
+// Code generated by piper's step-generator. DO NOT EDIT.
+
+package cmd
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "time"
+
+ "github.com/SAP/jenkins-library/pkg/config"
+ "github.com/SAP/jenkins-library/pkg/log"
+ "github.com/SAP/jenkins-library/pkg/piperenv"
+ "github.com/SAP/jenkins-library/pkg/splunk"
+ "github.com/SAP/jenkins-library/pkg/telemetry"
+ "github.com/SAP/jenkins-library/pkg/validation"
+ "github.com/spf13/cobra"
+)
+
+type abapAddonAssemblyKitCheckOptions struct {
+ AbapAddonAssemblyKitCertificateFile string `json:"abapAddonAssemblyKitCertificateFile,omitempty"`
+ AbapAddonAssemblyKitCertificatePass string `json:"abapAddonAssemblyKitCertificatePass,omitempty"`
+ AbapAddonAssemblyKitEndpoint string `json:"abapAddonAssemblyKitEndpoint,omitempty"`
+ Username string `json:"username,omitempty"`
+ Password string `json:"password,omitempty"`
+ AddonDescriptorFileName string `json:"addonDescriptorFileName,omitempty"`
+ AddonDescriptor string `json:"addonDescriptor,omitempty"`
+}
+
+type abapAddonAssemblyKitCheckCommonPipelineEnvironment struct {
+ abap struct {
+ addonDescriptor string
+ }
+}
+
+func (p *abapAddonAssemblyKitCheckCommonPipelineEnvironment) persist(path, resourceName string) {
+ content := []struct {
+ category string
+ name string
+ value interface{}
+ }{
+ {category: "abap", name: "addonDescriptor", value: p.abap.addonDescriptor},
+ }
+
+ 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 {
+ log.Entry().Error("failed to persist Piper environment")
+ }
+}
+
+// AbapAddonAssemblyKitCheckCommand This step calls AAKaaS to check the validity of the Addon Product Modelling.
+func AbapAddonAssemblyKitCheckCommand() *cobra.Command {
+ const STEP_NAME = "abapAddonAssemblyKitCheck"
+
+ metadata := abapAddonAssemblyKitCheckMetadata()
+ var stepConfig abapAddonAssemblyKitCheckOptions
+ var startTime time.Time
+ var commonPipelineEnvironment abapAddonAssemblyKitCheckCommonPipelineEnvironment
+ var logCollector *log.CollectorHook
+ var splunkClient *splunk.Splunk
+ telemetryClient := &telemetry.Telemetry{}
+
+ var createAbapAddonAssemblyKitCheckCmd = &cobra.Command{
+ Use: STEP_NAME,
+ Short: "This step calls AAKaaS to check the validity of the Addon Product Modelling.",
+ Long: `This step does the following:
+ - [The Addon Product Modelling](https://www.project-piper.io/scenarios/abapEnvironmentAddons/#add-on-descriptor-file) is read from the addonDescriptorFileName (e.g. addon.yml)
+ - A connection to AAKaaS (Addon Assembly Kit as a Service) is established and the Addon Product Modelling is transfered for detailed [checks](https://www.project-piper.io/scenarios/abapEnvironmentAddons/#versioning-rules)
+ - The semantic versions are resolved and stored into the piper commonPipelineEnviroment for usage of subsequent pipeline steps
+
+
+For logon to AAKaaS you can either provide a credential with basic authorization (username and password) or two secret text credentials containing the technical s-users certificate (see note [2805811](https://me.sap.com/notes/2805811) for download) as base64 encoded string and the password to decrypt the file
+
+For Terminology refer to the [Scenario Description](https://www.project-piper.io/scenarios/abapEnvironmentAddons/).`,
+ PreRunE: func(cmd *cobra.Command, _ []string) error {
+ startTime = time.Now()
+ log.SetStepName(STEP_NAME)
+ log.SetVerbose(GeneralConfig.Verbose)
+
+ GeneralConfig.GitHubAccessTokens = ResolveAccessTokens(GeneralConfig.GitHubTokens)
+
+ 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.AbapAddonAssemblyKitCertificateFile)
+ log.RegisterSecret(stepConfig.AbapAddonAssemblyKitCertificatePass)
+ 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)
+ }
+
+ if len(GeneralConfig.HookConfig.SplunkConfig.Dsn) > 0 || len(GeneralConfig.HookConfig.SplunkConfig.ProdCriblEndpoint) > 0 {
+ splunkClient = &splunk.Splunk{}
+ logCollector = &log.CollectorHook{CorrelationID: GeneralConfig.CorrelationID}
+ log.RegisterHook(logCollector)
+ }
+
+ if err = log.RegisterANSHookIfConfigured(GeneralConfig.CorrelationID); err != nil {
+ log.Entry().WithError(err).Warn("failed to set up SAP Alert Notification Service log hook")
+ }
+
+ validation, err := validation.New(validation.WithJSONNamesForStructFields(), validation.WithPredefinedErrorMessages())
+ if err != nil {
+ return err
+ }
+ if err = validation.ValidateStruct(stepConfig); err != nil {
+ log.SetErrorCategory(log.ErrorConfiguration)
+ return err
+ }
+
+ return nil
+ },
+ Run: func(_ *cobra.Command, _ []string) {
+ stepTelemetryData := telemetry.CustomData{}
+ stepTelemetryData.ErrorCode = "1"
+ handler := func() {
+ commonPipelineEnvironment.persist(GeneralConfig.EnvRootPath, "commonPipelineEnvironment")
+ config.RemoveVaultSecretFiles()
+ stepTelemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds())
+ stepTelemetryData.ErrorCategory = log.GetErrorCategory().String()
+ stepTelemetryData.PiperCommitHash = GitCommit
+ telemetryClient.SetData(&stepTelemetryData)
+ telemetryClient.Send()
+ if len(GeneralConfig.HookConfig.SplunkConfig.Dsn) > 0 {
+ splunkClient.Initialize(GeneralConfig.CorrelationID,
+ GeneralConfig.HookConfig.SplunkConfig.Dsn,
+ GeneralConfig.HookConfig.SplunkConfig.Token,
+ GeneralConfig.HookConfig.SplunkConfig.Index,
+ GeneralConfig.HookConfig.SplunkConfig.SendLogs)
+ splunkClient.Send(telemetryClient.GetData(), logCollector)
+ }
+ if len(GeneralConfig.HookConfig.SplunkConfig.ProdCriblEndpoint) > 0 {
+ splunkClient.Initialize(GeneralConfig.CorrelationID,
+ GeneralConfig.HookConfig.SplunkConfig.ProdCriblEndpoint,
+ GeneralConfig.HookConfig.SplunkConfig.ProdCriblToken,
+ GeneralConfig.HookConfig.SplunkConfig.ProdCriblIndex,
+ GeneralConfig.HookConfig.SplunkConfig.SendLogs)
+ splunkClient.Send(telemetryClient.GetData(), logCollector)
+ }
+ }
+ log.DeferExitHandler(handler)
+ defer handler()
+ telemetryClient.Initialize(GeneralConfig.NoTelemetry, STEP_NAME, GeneralConfig.HookConfig.PendoConfig.Token)
+ abapAddonAssemblyKitCheck(stepConfig, &stepTelemetryData, &commonPipelineEnvironment)
+ stepTelemetryData.ErrorCode = "0"
+ log.Entry().Info("SUCCESS")
+ },
+ }
+
+ addAbapAddonAssemblyKitCheckFlags(createAbapAddonAssemblyKitCheckCmd, &stepConfig)
+ return createAbapAddonAssemblyKitCheckCmd
+}
+
+func addAbapAddonAssemblyKitCheckFlags(cmd *cobra.Command, stepConfig *abapAddonAssemblyKitCheckOptions) {
+ cmd.Flags().StringVar(&stepConfig.AbapAddonAssemblyKitCertificateFile, "abapAddonAssemblyKitCertificateFile", os.Getenv("PIPER_abapAddonAssemblyKitCertificateFile"), "base64 encoded certificate pfx file (PKCS12 format) see note [2805811](https://me.sap.com/notes/2805811)")
+ cmd.Flags().StringVar(&stepConfig.AbapAddonAssemblyKitCertificatePass, "abapAddonAssemblyKitCertificatePass", os.Getenv("PIPER_abapAddonAssemblyKitCertificatePass"), "password to decrypt the certificate file")
+ cmd.Flags().StringVar(&stepConfig.AbapAddonAssemblyKitEndpoint, "abapAddonAssemblyKitEndpoint", `https://apps.support.sap.com`, "Base URL to the Addon Assembly Kit as a Service (AAKaaS) system")
+ cmd.Flags().StringVar(&stepConfig.Username, "username", os.Getenv("PIPER_username"), "User for the Addon Assembly Kit as a Service (AAKaaS) system")
+ cmd.Flags().StringVar(&stepConfig.Password, "password", os.Getenv("PIPER_password"), "Password for the Addon Assembly Kit as a Service (AAKaaS) system")
+ cmd.Flags().StringVar(&stepConfig.AddonDescriptorFileName, "addonDescriptorFileName", `addon.yml`, "File name of the YAML file which describes the Product Version and corresponding Software Component Versions")
+ cmd.Flags().StringVar(&stepConfig.AddonDescriptor, "addonDescriptor", os.Getenv("PIPER_addonDescriptor"), "Structure in the commonPipelineEnvironment containing information about the Product Version and corresponding Software Component Versions")
+
+ cmd.MarkFlagRequired("abapAddonAssemblyKitEndpoint")
+ cmd.MarkFlagRequired("addonDescriptorFileName")
+}
+
+// retrieve step metadata
+func abapAddonAssemblyKitCheckMetadata() config.StepData {
+ var theMetaData = config.StepData{
+ Metadata: config.StepMetadata{
+ Name: "abapAddonAssemblyKitCheck",
+ Aliases: []config.Alias{},
+ Description: "This step calls AAKaaS to check the validity of the Addon Product Modelling.",
+ },
+ Spec: config.StepSpec{
+ Inputs: config.StepInputs{
+ Secrets: []config.StepSecrets{
+ {Name: "abapAddonAssemblyKitCredentialsId", Description: "CredentialsId stored in Jenkins for the Addon Assembly Kit as a Service (AAKaaS) system", Type: "jenkins"},
+ {Name: "abapAddonAssemblyKitCertificateFileCredentialsId", Description: "Jenkins secret text credential ID containing the base64 encoded certificate pfx file (PKCS12 format) see note [2805811](https://me.sap.com/notes/2805811)", Type: "jenkins"},
+ {Name: "abapAddonAssemblyKitCertificatePassCredentialsId", Description: "Jenkins secret text credential ID containing the password to decrypt the certificate file stored in abapAddonAssemblyKitCertificateFileCredentialsId", Type: "jenkins"},
+ },
+ Parameters: []config.StepParameters{
+ {
+ Name: "abapAddonAssemblyKitCertificateFile",
+ ResourceRef: []config.ResourceReference{
+ {
+ Name: "abapAddonAssemblyKitCertificateFileCredentialsId",
+ Param: "abapAddonAssemblyKitCertificateFile",
+ Type: "secret",
+ },
+ },
+ Scope: []string{"PARAMETERS"},
+ Type: "string",
+ Mandatory: false,
+ Aliases: []config.Alias{},
+ Default: os.Getenv("PIPER_abapAddonAssemblyKitCertificateFile"),
+ },
+ {
+ Name: "abapAddonAssemblyKitCertificatePass",
+ ResourceRef: []config.ResourceReference{
+ {
+ Name: "abapAddonAssemblyKitCertificatePassCredentialsId",
+ Param: "abapAddonAssemblyKitCertificatePass",
+ Type: "secret",
+ },
+ },
+ Scope: []string{"PARAMETERS"},
+ Type: "string",
+ Mandatory: false,
+ Aliases: []config.Alias{},
+ Default: os.Getenv("PIPER_abapAddonAssemblyKitCertificatePass"),
+ },
+ {
+ Name: "abapAddonAssemblyKitEndpoint",
+ ResourceRef: []config.ResourceReference{},
+ Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
+ Type: "string",
+ Mandatory: true,
+ Aliases: []config.Alias{},
+ Default: `https://apps.support.sap.com`,
+ },
+ {
+ Name: "username",
+ ResourceRef: []config.ResourceReference{},
+ Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
+ Type: "string",
+ Mandatory: false,
+ Aliases: []config.Alias{},
+ Default: os.Getenv("PIPER_username"),
+ },
+ {
+ Name: "password",
+ ResourceRef: []config.ResourceReference{},
+ Scope: []string{"PARAMETERS"},
+ Type: "string",
+ Mandatory: false,
+ Aliases: []config.Alias{},
+ Default: os.Getenv("PIPER_password"),
+ },
+ {
+ Name: "addonDescriptorFileName",
+ ResourceRef: []config.ResourceReference{},
+ Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
+ Type: "string",
+ Mandatory: true,
+ Aliases: []config.Alias{},
+ Default: `addon.yml`,
+ },
+ {
+ Name: "addonDescriptor",
+ ResourceRef: []config.ResourceReference{
+ {
+ Name: "commonPipelineEnvironment",
+ Param: "abap/addonDescriptor",
+ },
+ },
+ Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
+ Type: "string",
+ Mandatory: false,
+ Aliases: []config.Alias{},
+ Default: os.Getenv("PIPER_addonDescriptor"),
+ },
+ },
+ },
+ Outputs: config.StepOutputs{
+ Resources: []config.StepResources{
+ {
+ Name: "commonPipelineEnvironment",
+ Type: "piperEnvironment",
+ Parameters: []map[string]interface{}{
+ {"name": "abap/addonDescriptor"},
+ },
+ },
+ },
+ },
+ },
+ }
+ return theMetaData
+}
diff --git a/cmd/abapAddonAssemblyKitCheck_generated_test.go b/cmd/abapAddonAssemblyKitCheck_generated_test.go
new file mode 100644
index 0000000000..0cbf7de791
--- /dev/null
+++ b/cmd/abapAddonAssemblyKitCheck_generated_test.go
@@ -0,0 +1,20 @@
+//go:build unit
+// +build unit
+
+package cmd
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestAbapAddonAssemblyKitCheckCommand(t *testing.T) {
+ t.Parallel()
+
+ testCmd := AbapAddonAssemblyKitCheckCommand()
+
+ // only high level testing performed - details are tested in step generation procedure
+ assert.Equal(t, "abapAddonAssemblyKitCheck", testCmd.Use, "command name incorrect")
+
+}
diff --git a/cmd/abapAddonAssemblyKitCheck_test.go b/cmd/abapAddonAssemblyKitCheck_test.go
new file mode 100644
index 0000000000..a9b2f41fbe
--- /dev/null
+++ b/cmd/abapAddonAssemblyKitCheck_test.go
@@ -0,0 +1,60 @@
+//go:build unit
+// +build unit
+
+package cmd
+
+import (
+ "testing"
+
+ "github.com/SAP/jenkins-library/pkg/abap/aakaas"
+ "github.com/SAP/jenkins-library/pkg/abaputils"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestRunAbapAddonAssemblyKitCheck(t *testing.T) {
+ var config abapAddonAssemblyKitCheckOptions
+ var cpe abapAddonAssemblyKitCheckCommonPipelineEnvironment
+ bundle := aakaas.NewAakBundleMock()
+ utils := bundle.GetUtils()
+ config.Username = "dummyUser"
+ config.Password = "dummyPassword"
+
+ t.Run("happy path", func(t *testing.T) {
+ config.AddonDescriptorFileName = "addon.yml.mock"
+ bundle.SetBody(aakaas.ResponseCheck)
+ bundle.MockAddonDescriptor = abaputils.AddonDescriptor{
+ AddonProduct: "/DRNMSPC/PRD01",
+ AddonVersionYAML: "2.0.0",
+ Repositories: []abaputils.Repository{
+ {
+ Name: "/DRNMSPC/COMP01",
+ VersionYAML: "2.0.0",
+ },
+ {
+ Name: "/DRNMSPC/COMP02",
+ VersionYAML: "1.0.0",
+ },
+ },
+ }
+
+ err := runAbapAddonAssemblyKitCheck(&config, nil, utils, &cpe)
+
+ assert.NoError(t, err)
+ })
+
+ t.Run("error path", func(t *testing.T) {
+ config.AddonDescriptorFileName = "addon.yml.mock"
+ bundle.SetBody(aakaas.ResponseCheck)
+ bundle.MockAddonDescriptor = abaputils.AddonDescriptor{
+ AddonProduct: "/DRNMSPC/PRD01",
+ AddonVersionYAML: "2.0.0",
+ Repositories: []abaputils.Repository{
+ //no repos should fail during pvh creation...
+ },
+ }
+
+ err := runAbapAddonAssemblyKitCheck(&config, nil, utils, &cpe)
+
+ assert.EqualError(t, err, "addonDescriptor must contain at least one software component repository")
+ })
+}
diff --git a/cmd/metadata_generated.go b/cmd/metadata_generated.go
index 21360f54dd..d39526eb13 100644
--- a/cmd/metadata_generated.go
+++ b/cmd/metadata_generated.go
@@ -7,6 +7,7 @@ import "github.com/SAP/jenkins-library/pkg/config"
// GetStepMetadata return a map with all the step metadata mapped to their stepName
func GetAllStepMetadata() map[string]config.StepData {
return map[string]config.StepData{
+ "abapAddonAssemblyKitCheck": abapAddonAssemblyKitCheckMetadata(),
"abapAddonAssemblyKitCheckCVs": abapAddonAssemblyKitCheckCVsMetadata(),
"abapAddonAssemblyKitCheckPV": abapAddonAssemblyKitCheckPVMetadata(),
"abapAddonAssemblyKitCreateTargetVector": abapAddonAssemblyKitCreateTargetVectorMetadata(),
diff --git a/cmd/piper.go b/cmd/piper.go
index bcd5217a05..8377f61a41 100644
--- a/cmd/piper.go
+++ b/cmd/piper.go
@@ -154,6 +154,7 @@ func Execute() {
rootCmd.AddCommand(AbapEnvironmentAssemblePackagesCommand())
rootCmd.AddCommand(AbapAddonAssemblyKitCheckCVsCommand())
rootCmd.AddCommand(AbapAddonAssemblyKitCheckPVCommand())
+ rootCmd.AddCommand(AbapAddonAssemblyKitCheckCommand())
rootCmd.AddCommand(AbapAddonAssemblyKitCreateTargetVectorCommand())
rootCmd.AddCommand(AbapAddonAssemblyKitPublishTargetVectorCommand())
rootCmd.AddCommand(AbapAddonAssemblyKitRegisterPackagesCommand())
diff --git a/documentation/docs/steps/abapAddonAssemblyKitCheck.md b/documentation/docs/steps/abapAddonAssemblyKitCheck.md
new file mode 100644
index 0000000000..feb11316c8
--- /dev/null
+++ b/documentation/docs/steps/abapAddonAssemblyKitCheck.md
@@ -0,0 +1,42 @@
+# ${docGenStepName}
+
+## ${docGenDescription}
+
+### Artifacts
+
+- addonDescriptorFile (addon.yml)
+ The addonDescriptorFile as specified in parameter addonDescriptorFileName is archived as artifact. This is done as this file is the main configuration and usually changed with every run. Thus it simplifies support if the corresponding configuration file is directly accessible in the pipeline.
+
+## Prerequisites
+
+* The credentials to access the AAKaaS (Technical Communication User) must be stored in the Jenkins Credential Store
+* The step needs an addon.yml containing information about the Product Version and corresponding Software Component Versions/Repositories
+
+A detailed description of all prerequisites of the scenario and how to configure them can be found in the [Scenario Description](https://www.project-piper.io/scenarios/abapEnvironmentAddons/).
+
+## ${docGenParameters}
+
+## ${docGenConfiguration}
+
+## ${docJenkinsPluginDependencies}
+
+## Examples
+
+### Configuration in the config.yml
+
+The recommended way to configure your pipeline is via the config.yml file. In this case, calling the step in the Jenkinsfile is reduced to one line:
+
+```groovy
+abapAddonAssemblyKitCheck script: this
+```
+
+If the step is to be configured individually the config.yml should look like this:
+
+```yaml
+steps:
+ abapAddonAssemblyKitCheck:
+ abapAddonAssemblyKitCredentialsId: 'abapAddonAssemblyKitCredentialsId',
+ addonDescriptorFileName: 'addon.yml'
+```
+
+More convenient ways of configuration (e.g. on stage level) are described in the respective scenario/pipeline documentation.
diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml
index b56abdf4ce..c7e3674b75 100644
--- a/documentation/mkdocs.yml
+++ b/documentation/mkdocs.yml
@@ -51,13 +51,15 @@ nav:
- 'Set up a Pipeline-Based ABAP Development and Testing Process Using Git-Enabled Change and Transport System': scenarios/gCTS_Scenario.md
- Extensibility: extensibility.md
- 'Library steps':
- - abapAddonAssemblyKitCheckCVs: steps/abapAddonAssemblyKitCheckCVs.md
- - abapAddonAssemblyKitCheckPV: steps/abapAddonAssemblyKitCheckPV.md
- - abapAddonAssemblyKitCreateTargetVector: steps/abapAddonAssemblyKitCreateTargetVector.md
- - abapAddonAssemblyKitPublishTargetVector: steps/abapAddonAssemblyKitPublishTargetVector.md
- - abapAddonAssemblyKitRegisterPackages: steps/abapAddonAssemblyKitRegisterPackages.md
- - abapAddonAssemblyKitReleasePackages: steps/abapAddonAssemblyKitReleasePackages.md
- - abapAddonAssemblyKitReserveNextPackages: steps/abapAddonAssemblyKitReserveNextPackages.md
+ - 'abapAddonAssemblyKit':
+ - Check: steps/abapAddonAssemblyKitCheck.md
+ - CheckCVs: steps/abapAddonAssemblyKitCheckCVs.md
+ - CheckPV: steps/abapAddonAssemblyKitCheckPV.md
+ - CreateTargetVector: steps/abapAddonAssemblyKitCreateTargetVector.md
+ - PublishTargetVector: steps/abapAddonAssemblyKitPublishTargetVector.md
+ - RegisterPackages: steps/abapAddonAssemblyKitRegisterPackages.md
+ - ReleasePackages: steps/abapAddonAssemblyKitReleasePackages.md
+ - ReserveNextPackages: steps/abapAddonAssemblyKitReserveNextPackages.md
- abapEnvironmentBuild: steps/abapEnvironmentBuild.md
- abapEnvironmentAssemblePackages: steps/abapEnvironmentAssemblePackages.md
- abapEnvironmentAssembleConfirm: steps/abapEnvironmentAssembleConfirm.md
diff --git a/pkg/abap/aakaas/aakUtils_mock.go b/pkg/abap/aakaas/aakUtils_mock.go
index 307771ef4a..5e4de0a8f0 100644
--- a/pkg/abap/aakaas/aakUtils_mock.go
+++ b/pkg/abap/aakaas/aakUtils_mock.go
@@ -17,7 +17,8 @@ type AakBundleMock struct {
*mock.ExecMockRunner
*abaputils.ClientMock
*mock.FilesMock
- maxRuntime time.Duration
+ maxRuntime time.Duration
+ MockAddonDescriptor abaputils.AddonDescriptor
}
func NewAakBundleMock() *AakBundleMock {
@@ -99,6 +100,10 @@ func (bundle *AakBundleMock) ReadAddonDescriptor(FileName string) (abaputils.Add
{
err = errors.New("error in ReadAddonDescriptor")
}
+ case "addon.yml.mock":
+ {
+ return bundle.MockAddonDescriptor, nil
+ }
}
return addonDescriptor, err
}
diff --git a/pkg/abap/aakaas/productVersionHeader.go b/pkg/abap/aakaas/productVersionHeader.go
new file mode 100644
index 0000000000..b7a83e251a
--- /dev/null
+++ b/pkg/abap/aakaas/productVersionHeader.go
@@ -0,0 +1,147 @@
+package aakaas
+
+import (
+ "encoding/json"
+
+ abapbuild "github.com/SAP/jenkins-library/pkg/abap/build"
+ "github.com/SAP/jenkins-library/pkg/abaputils"
+ "github.com/pkg/errors"
+)
+
+type jsonProductVersionHeader struct {
+ Pvh *ProductVersionHeader `json:"d"`
+}
+
+type ProductVersionHeader struct {
+ ProductName string
+ SemanticProductVersion string `json:"SemProductVersion"`
+ ProductVersion string
+ SpsLevel string
+ PatchLevel string
+ Vendor string
+ VendorType string
+ Content []ProductVersionContent `json:"-"` //for developer access
+ JsonContent ProductVersionContents `json:"Content"` //for json (Un)Marshaling
+}
+
+type ProductVersionContents struct {
+ Content []ProductVersionContent `json:"results"`
+}
+
+type ProductVersionContent struct {
+ ProductName string
+ SemanticProductVersion string `json:"SemProductVersion"`
+ SoftwareComponentName string `json:"ScName"`
+ SemanticSoftwareComponentVersion string `json:"SemScVersion"`
+ SoftwareComponentVersion string `json:"ScVersion"`
+ SpLevel string
+ PatchLevel string
+ Vendor string
+ VendorType string
+}
+
+func NewProductVersionHeader(addonDescriptor *abaputils.AddonDescriptor, conn *abapbuild.Connector) (*ProductVersionHeader, error) {
+ productVersion := new(ProductVersion)
+ if err := productVersion.ConstructProductversion(*addonDescriptor, *conn); err != nil {
+ return nil, err
+ }
+ pvh := ProductVersionHeader{
+ ProductName: productVersion.Name,
+ SemanticProductVersion: productVersion.Version,
+ Content: []ProductVersionContent{},
+ }
+
+ for _, repo := range addonDescriptor.Repositories {
+ componentVersion := new(ComponentVersion)
+ if err := componentVersion.ConstructComponentVersion(repo, *conn); err != nil {
+ return nil, err
+ }
+ pvc := ProductVersionContent{
+ ProductName: pvh.ProductName,
+ SemanticProductVersion: pvh.SemanticProductVersion,
+ SoftwareComponentName: componentVersion.Name,
+ SemanticSoftwareComponentVersion: componentVersion.Version,
+ }
+ pvh.Content = append(pvh.Content, pvc)
+ }
+
+ if len(pvh.Content) == 0 {
+ return nil, errors.New("addonDescriptor must contain at least one software component repository")
+ }
+
+ return &pvh, nil
+}
+
+func (pv *ProductVersionHeader) CheckAndResolveVersion(conn *abapbuild.Connector) error {
+ conn.GetToken("/odata/aas_ocs_package")
+ pv.JsonContent = ProductVersionContents{
+ Content: pv.Content,
+ }
+ requestJson, err := json.Marshal(pv)
+ if err != nil {
+ return err
+ }
+
+ appendum := "/odata/aas_ocs_package/ProductVersionHeaderSet"
+ responseBody, err := conn.Post(appendum, string(requestJson))
+ if err != nil {
+ return errors.Wrap(err, "Checking Product Modeling in AAkaaS failed")
+ }
+
+ var resultPv jsonProductVersionHeader
+ if err := json.Unmarshal(responseBody, &resultPv); err != nil {
+ return errors.Wrap(err, "Unexpected AAKaaS response for checking Product Modeling "+string(responseBody))
+ }
+
+ pv.ProductVersion = resultPv.Pvh.ProductVersion
+ pv.SpsLevel = resultPv.Pvh.SpsLevel
+ pv.PatchLevel = resultPv.Pvh.PatchLevel
+
+ for pvc_index, pvc := range pv.Content {
+ foundPvc := ProductVersionContent{}
+ for _, resultPvc := range resultPv.Pvh.JsonContent.Content {
+ if pvc.SoftwareComponentName == resultPvc.SoftwareComponentName && foundPvc.SoftwareComponentName == "" {
+ foundPvc = resultPvc
+ } else if pvc.SoftwareComponentName == resultPvc.SoftwareComponentName {
+ return errors.New("Software Component Name must be unique in the ProductVersionContent")
+ }
+ }
+ if foundPvc.SoftwareComponentName == "" {
+ return errors.New("Software Component Name not found in the ProductVersionContent")
+ }
+ pv.Content[pvc_index].PatchLevel = foundPvc.PatchLevel
+ pv.Content[pvc_index].SpLevel = foundPvc.SpLevel
+ pv.Content[pvc_index].SoftwareComponentVersion = foundPvc.SoftwareComponentVersion
+ }
+
+ pv.JsonContent = ProductVersionContents{}
+ return nil
+}
+
+func (pv *ProductVersionHeader) SyncAddonDescriptorVersionFields(addonDescriptor *abaputils.AddonDescriptor) error {
+ addonDescriptor.AddonVersion = pv.ProductVersion
+ addonDescriptor.AddonSpsLevel = pv.SpsLevel
+ addonDescriptor.AddonPatchLevel = pv.PatchLevel
+
+ //in NewPvh function pvh was build up 1:1 based on addonDescriptor
+ //in checkAndResolve pvh was synced from AAKaaS reply assuming it does not contain more content than before(if it does it is ignored)
+ for repo_index, repo := range addonDescriptor.Repositories {
+ foundPvc := ProductVersionContent{}
+ for _, pvc := range pv.Content {
+ if pvc.SoftwareComponentName == repo.Name && foundPvc.SoftwareComponentName == "" {
+ foundPvc = pvc
+ } else if pvc.SoftwareComponentName == repo.Name {
+ return errors.New("Software Component Name must be unique in addon descriptor(aka addon.yml)")
+ }
+ }
+ if foundPvc.SoftwareComponentName == "" {
+ return errors.New("ProductVersionContent & addon descriptor (aka addon.yml) out of sync")
+ }
+
+ addonDescriptor.Repositories[repo_index].PatchLevel = foundPvc.PatchLevel
+ addonDescriptor.Repositories[repo_index].SpLevel = foundPvc.SpLevel
+ addonDescriptor.Repositories[repo_index].Version = foundPvc.SoftwareComponentVersion
+ }
+
+ return nil
+}
diff --git a/pkg/abap/aakaas/testData.go b/pkg/abap/aakaas/testData.go
index 3a0b10dbe4..adc64a167a 100644
--- a/pkg/abap/aakaas/testData.go
+++ b/pkg/abap/aakaas/testData.go
@@ -34,6 +34,44 @@ var ResponseCheckCVs = `{
}
}`
+var ResponseCheck = `{
+ "d": {
+ "ProductName": "/DRNMSPC/PRD01",
+ "SemProductVersion": "2.0.0",
+ "ProductVersion": "0002",
+ "SpsLevel": "0000",
+ "PatchLevel": "0000",
+ "Vendor": "",
+ "VendorType": "",
+ "Content": {
+ "results": [
+ {
+ "ProductName": "/DRNMSPC/PRD01",
+ "SemProductVersion": "2.0.0",
+ "ScName": "/DRNMSPC/COMP01",
+ "SemScVersion": "2.0.0",
+ "ScVersion": "0002",
+ "SpLevel": "0000",
+ "PatchLevel": "0000",
+ "Vendor": "",
+ "VendorType": ""
+ },
+ {
+ "ProductName": "/DRNMSPC/PRD01",
+ "SemProductVersion": "2.0.0",
+ "ScName": "/DRNMSPC/COMP02",
+ "SemScVersion": "1.0.0",
+ "ScVersion": "0001",
+ "SpLevel": "0000",
+ "PatchLevel": "0000",
+ "Vendor": "",
+ "VendorType": ""
+ }
+ ]
+ }
+ }
+}`
+
var emptyResultBody = `{
"d": {
"results": []
diff --git a/resources/metadata/abapAddonAssemblyKitCheck.yaml b/resources/metadata/abapAddonAssemblyKitCheck.yaml
new file mode 100644
index 0000000000..19842737b1
--- /dev/null
+++ b/resources/metadata/abapAddonAssemblyKitCheck.yaml
@@ -0,0 +1,101 @@
+metadata:
+ name: abapAddonAssemblyKitCheck
+ description: This step calls AAKaaS to check the validity of the Addon Product Modelling.
+ longDescription: |
+ This step does the following:
+ - [The Addon Product Modelling](https://www.project-piper.io/scenarios/abapEnvironmentAddons/#add-on-descriptor-file) is read from the addonDescriptorFileName (e.g. addon.yml)
+ - A connection to AAKaaS (Addon Assembly Kit as a Service) is established and the Addon Product Modelling is transfered for detailed [checks](https://www.project-piper.io/scenarios/abapEnvironmentAddons/#versioning-rules)
+ - The semantic versions are resolved and stored into the piper commonPipelineEnviroment for usage of subsequent pipeline steps
+
+
+ For logon to AAKaaS you can either provide a credential with basic authorization (username and password) or two secret text credentials containing the technical s-users certificate (see note [2805811](https://me.sap.com/notes/2805811) for download) as base64 encoded string and the password to decrypt the file
+
+ For Terminology refer to the [Scenario Description](https://www.project-piper.io/scenarios/abapEnvironmentAddons/).
+spec:
+ inputs:
+ secrets:
+ - name: abapAddonAssemblyKitCredentialsId
+ description: CredentialsId stored in Jenkins for the Addon Assembly Kit as a Service (AAKaaS) system
+ type: jenkins
+ - name: abapAddonAssemblyKitCertificateFileCredentialsId
+ description: Jenkins secret text credential ID containing the base64 encoded certificate pfx file (PKCS12 format) see note [2805811](https://me.sap.com/notes/2805811)
+ type: jenkins
+ - name: abapAddonAssemblyKitCertificatePassCredentialsId
+ description: Jenkins secret text credential ID containing the password to decrypt the certificate file stored in abapAddonAssemblyKitCertificateFileCredentialsId
+ type: jenkins
+ params:
+ - name: abapAddonAssemblyKitCertificateFile
+ type: string
+ description: base64 encoded certificate pfx file (PKCS12 format) see note [2805811](https://me.sap.com/notes/2805811)
+ scope:
+ - PARAMETERS
+ mandatory: false
+ secret: true
+ resourceRef:
+ - name: abapAddonAssemblyKitCertificateFileCredentialsId
+ type: secret
+ param: abapAddonAssemblyKitCertificateFile
+ - name: abapAddonAssemblyKitCertificatePass
+ type: string
+ description: password to decrypt the certificate file
+ scope:
+ - PARAMETERS
+ mandatory: false
+ secret: true
+ resourceRef:
+ - name: abapAddonAssemblyKitCertificatePassCredentialsId
+ type: secret
+ param: abapAddonAssemblyKitCertificatePass
+ - name: abapAddonAssemblyKitEndpoint
+ type: string
+ description: Base URL to the Addon Assembly Kit as a Service (AAKaaS) system
+ scope:
+ - PARAMETERS
+ - STAGES
+ - STEPS
+ - GENERAL
+ mandatory: true
+ default: https://apps.support.sap.com
+ - name: username
+ type: string
+ description: User for the Addon Assembly Kit as a Service (AAKaaS) system
+ scope:
+ - PARAMETERS
+ - STAGES
+ - STEPS
+ mandatory: false
+ secret: true
+ - name: password
+ type: string
+ description: Password for the Addon Assembly Kit as a Service (AAKaaS) system
+ scope:
+ - PARAMETERS
+ mandatory: false
+ secret: true
+ - name: addonDescriptorFileName
+ type: string
+ description: File name of the YAML file which describes the Product Version and corresponding Software Component Versions
+ mandatory: true
+ default: addon.yml
+ scope:
+ - PARAMETERS
+ - STAGES
+ - STEPS
+ - GENERAL
+ - name: addonDescriptor
+ type: string
+ description: Structure in the commonPipelineEnvironment containing information about the Product Version and corresponding Software Component Versions
+ mandatory: false
+ scope:
+ - PARAMETERS
+ - STAGES
+ - STEPS
+ resourceRef:
+ - name: commonPipelineEnvironment
+ param: abap/addonDescriptor
+ outputs:
+ resources:
+ - name: commonPipelineEnvironment
+ type: piperEnvironment
+ params:
+ - name: abap/addonDescriptor
diff --git a/test/groovy/CommonStepsTest.groovy b/test/groovy/CommonStepsTest.groovy
index a5ff1bbedf..30c5715828 100644
--- a/test/groovy/CommonStepsTest.groovy
+++ b/test/groovy/CommonStepsTest.groovy
@@ -106,6 +106,7 @@ public class CommonStepsTest extends BasePiperTest{
}
private static fieldRelatedWhitelist = [
+ 'abapAddonAssemblyKitCheck', //implementing new golang pattern without fields
'abapAddonAssemblyKitCheckCVs', //implementing new golang pattern without fields
'abapAddonAssemblyKitCheckPV', //implementing new golang pattern without fields
'abapAddonAssemblyKitCreateTargetVector', //implementing new golang pattern without fields
diff --git a/vars/abapAddonAssemblyKitCheck.groovy b/vars/abapAddonAssemblyKitCheck.groovy
new file mode 100644
index 0000000000..30c5f1b0e5
--- /dev/null
+++ b/vars/abapAddonAssemblyKitCheck.groovy
@@ -0,0 +1,13 @@
+import groovy.transform.Field
+
+@Field String STEP_NAME = getClass().getName()
+@Field String METADATA_FILE = 'metadata/abapAddonAssemblyKitCheck.yaml'
+
+void call(Map parameters = [:]) {
+ List credentials = [
+ [type: 'usernamePassword', id: 'abapAddonAssemblyKitCredentialsId', env: ['PIPER_username', 'PIPER_password']],
+ [type: 'token', id: 'abapAddonAssemblyKitCertificateFileCredentialsId', env: ['PIPER_abapAddonAssemblyKitCertificateFile']],
+ [type: 'token', id: 'abapAddonAssemblyKitCertificatePassCredentialsId', env: ['PIPER_abapAddonAssemblyKitCertificatePass']]
+ ]
+ piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials, false, false, true)
+}
diff --git a/vars/abapEnvironmentPipelineStageInitialChecks.groovy b/vars/abapEnvironmentPipelineStageInitialChecks.groovy
index 0d07359909..9ee6aaea9d 100644
--- a/vars/abapEnvironmentPipelineStageInitialChecks.groovy
+++ b/vars/abapEnvironmentPipelineStageInitialChecks.groovy
@@ -6,8 +6,8 @@ import static com.sap.piper.Prerequisites.checkScript
@Field String STEP_NAME = getClass().getName()
@Field Set GENERAL_CONFIG_KEYS = []
@Field STAGE_STEP_KEYS = [
- 'abapAddonAssemblyKitCheckPV',
- 'abapAddonAssemblyKitCheckCVs'
+ 'abapAddonAssemblyKitCheck',
+ 'abapAddonAssemblyKitReserveNextPackages'
]
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus(STAGE_STEP_KEYS)
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
@@ -19,8 +19,7 @@ void call(Map parameters = [:]) {
def stageName = parameters.stageName?:env.STAGE_NAME
piperStageWrapper (script: script, stageName: stageName, stashContent: [], stageLocking: false) {
- abapAddonAssemblyKitCheckPV script: parameters.script
- abapAddonAssemblyKitCheckCVs script: parameters.script
+ abapAddonAssemblyKitCheck script: parameters.script
abapAddonAssemblyKitReserveNextPackages script: parameters.script
}