From 39118454e15abb592cc6aaa85215f4738bce06d3 Mon Sep 17 00:00:00 2001 From: tiloKo <70266685+tiloKo@users.noreply.github.com> Date: Fri, 18 Sep 2020 14:07:42 +0200 Subject: [PATCH] AAKaaS: 5 register package (#2042) * adding my steps * messy step * Update abapEnvironmentAssembly.go * clean up * change yaml * corrections * Update cloudFoundryDeploy.go * update * delete simulation step * remove simulate * Update PiperGoUtils.groovy * Update PiperGoUtils.groovy * Update CommonStepsTest.groovy * add docu * Update abapEnvironmentAssembly.md * changes due to PR * Update .gitignore * b * CV list * Update abapEnvironmentAssembly.go * testing with simulation * Update abapEnvironmentAssembly.go * remove simulation * renaming * Update mkdocs.yml * moving service key to yaml and fixing code climate * Update abapEnvironmentAssemblePackages.go * Update abapEnvironmentAssemblePackages.go * Update abapEnvironmentAssemblePackages.go * Update abapEnvironmentAssemblePackages.go * change input * Update abapEnvironmentAssemblePackages.go * change json tag * fixed error handling * documentation * Update abapEnvironmentAssemblePackages.md * Update abapEnvironmentAssemblePackages.md * fixing code climate issues * fixing code climate issues * Update abapEnvironmentAssemblePackages.yaml * fixing code climate issues * Update abapEnvironmentAssemblePackages.yaml * adding unittests * adding unittests and improved logging * yaml -> json * change scope of cfServiceKeyName * correct indentation * Update CommonStepsTest.groovy * maintain correct step order * AAKaaS register package * AAKaaS register package #2 * Update documentation/docs/steps/abapAddonAssemblyKitPublishTargetVector.md Co-authored-by: Daniel Mieg <56156797+DanielMieg@users.noreply.github.com> * AAKaaS register package #3 Co-authored-by: rosemarieB <45030247+rosemarieB@users.noreply.github.com> Co-authored-by: Daniel Mieg <56156797+DanielMieg@users.noreply.github.com> Co-authored-by: Christopher Fenner <26137398+CCFenner@users.noreply.github.com> --- cmd/abapAddonAssemblyKitRegisterPackages.go | 106 ++++++++++ ...onAssemblyKitRegisterPackages_generated.go | 178 +++++++++++++++++ ...emblyKitRegisterPackages_generated_test.go | 16 ++ ...apAddonAssemblyKitRegisterPackages_test.go | 186 ++++++++++++++++++ cmd/piper.go | 2 +- ...abapAddonAssemblyKitPublishTargetVector.md | 2 +- .../abapAddonAssemblyKitRegisterPackages.md | 83 ++++++++ documentation/mkdocs.yml | 2 +- pkg/abap/aakaas/package.go | 132 +++++++++++++ pkg/abap/build/connector.go | 13 ++ .../abapAddonAssemblyKitRegisterPackages.yaml | 58 ++++++ test/groovy/CommonStepsTest.groovy | 2 +- ...bapAddonAssemblyKitRegisterPackages.groovy | 11 ++ 13 files changed, 787 insertions(+), 4 deletions(-) create mode 100644 cmd/abapAddonAssemblyKitRegisterPackages.go create mode 100644 cmd/abapAddonAssemblyKitRegisterPackages_generated.go create mode 100644 cmd/abapAddonAssemblyKitRegisterPackages_generated_test.go create mode 100644 cmd/abapAddonAssemblyKitRegisterPackages_test.go create mode 100644 documentation/docs/steps/abapAddonAssemblyKitRegisterPackages.md create mode 100644 pkg/abap/aakaas/package.go create mode 100644 resources/metadata/abapAddonAssemblyKitRegisterPackages.yaml create mode 100644 vars/abapAddonAssemblyKitRegisterPackages.groovy diff --git a/cmd/abapAddonAssemblyKitRegisterPackages.go b/cmd/abapAddonAssemblyKitRegisterPackages.go new file mode 100644 index 0000000000..0365acd903 --- /dev/null +++ b/cmd/abapAddonAssemblyKitRegisterPackages.go @@ -0,0 +1,106 @@ +package cmd + +import ( + "encoding/json" + "io/ioutil" + "path/filepath" + + "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/command" + piperhttp "github.com/SAP/jenkins-library/pkg/http" + "github.com/SAP/jenkins-library/pkg/log" + "github.com/SAP/jenkins-library/pkg/telemetry" + "github.com/pkg/errors" +) + +func abapAddonAssemblyKitRegisterPackages(config abapAddonAssemblyKitRegisterPackagesOptions, telemetryData *telemetry.CustomData, cpe *abapAddonAssemblyKitRegisterPackagesCommonPipelineEnvironment) { + // for command execution use Command + c := command.Command{} + // reroute command output to logging framework + c.Stdout(log.Writer()) + c.Stderr(log.Writer()) + + client := piperhttp.Client{} + + // error situations should stop execution through log.Entry().Fatal() call which leads to an os.Exit(1) in the end + err := runAbapAddonAssemblyKitRegisterPackages(&config, telemetryData, &client, cpe, reader) + if err != nil { + log.Entry().WithError(err).Fatal("step execution failed") + } +} + +func runAbapAddonAssemblyKitRegisterPackages(config *abapAddonAssemblyKitRegisterPackagesOptions, telemetryData *telemetry.CustomData, client piperhttp.Sender, + cpe *abapAddonAssemblyKitRegisterPackagesCommonPipelineEnvironment, fileReader readFile) error { + var addonDescriptor abaputils.AddonDescriptor + json.Unmarshal([]byte(config.AddonDescriptor), &addonDescriptor) + + conn := new(abapbuild.Connector) + conn.InitAAKaaS(config.AbapAddonAssemblyKitEndpoint, config.Username, config.Password, client) + err := uploadSarFiles(addonDescriptor.Repositories, *conn, fileReader) + if err != nil { + return err + } + + // we need a second connector without the added Header + conn2 := new(abapbuild.Connector) + conn2.InitAAKaaS(config.AbapAddonAssemblyKitEndpoint, config.Username, config.Password, client) + addonDescriptor.Repositories, err = registerPackages(addonDescriptor.Repositories, *conn2) + if err != nil { + return err + } + + log.Entry().Info("Writing package status to CommonPipelineEnvironment") + backToCPE, _ := json.Marshal(addonDescriptor) + cpe.abap.addonDescriptor = string(backToCPE) + return nil +} + +func uploadSarFiles(repos []abaputils.Repository, conn abapbuild.Connector, readFileFunc readFile) error { + for i := range repos { + if repos[i].Status == string(aakaas.PackageStatusPlanned) { + if repos[i].SarXMLFilePath == "" { + return errors.New("Parameter missing. Please provide the path to the SAR file") + } + filename := filepath.Base(repos[i].SarXMLFilePath) + conn.Header["Content-Filename"] = []string{filename} + sarFile, err := readFileFunc(repos[i].SarXMLFilePath) + if err != nil { + return err + } + log.Entry().Infof("Upload SAR file %s", filename) + err = conn.UploadSarFile("/odata/aas_file_upload", sarFile) + if err != nil { + return err + } + } else { + log.Entry().Infof("Package %s has status %s, cannot upload the SAR file of this package", repos[i].PackageName, repos[i].Status) + } + } + return nil +} + +// for moocking +type readFile func(path string) ([]byte, error) + +func reader(path string) ([]byte, error) { + return ioutil.ReadFile(path) +} + +func registerPackages(repos []abaputils.Repository, conn abapbuild.Connector) ([]abaputils.Repository, error) { + for i := range repos { + var pack aakaas.Package + pack.InitPackage(repos[i], conn) + if repos[i].Status == string(aakaas.PackageStatusPlanned) { + err := pack.Register() + if err != nil { + return repos, err + } + pack.ChangeStatus(&repos[i]) + } else { + log.Entry().Infof("Package %s has status %s, cannot register this package", pack.PackageName, pack.Status) + } + } + return repos, nil +} diff --git a/cmd/abapAddonAssemblyKitRegisterPackages_generated.go b/cmd/abapAddonAssemblyKitRegisterPackages_generated.go new file mode 100644 index 0000000000..c4e3ec2551 --- /dev/null +++ b/cmd/abapAddonAssemblyKitRegisterPackages_generated.go @@ -0,0 +1,178 @@ +// 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/telemetry" + "github.com/spf13/cobra" +) + +type abapAddonAssemblyKitRegisterPackagesOptions struct { + AbapAddonAssemblyKitEndpoint string `json:"abapAddonAssemblyKitEndpoint,omitempty"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + AddonDescriptor string `json:"addonDescriptor,omitempty"` +} + +type abapAddonAssemblyKitRegisterPackagesCommonPipelineEnvironment struct { + abap struct { + addonDescriptor string + } +} + +func (p *abapAddonAssemblyKitRegisterPackagesCommonPipelineEnvironment) persist(path, resourceName string) { + content := []struct { + category string + name string + value string + }{ + {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().Fatal("failed to persist Piper environment") + } +} + +// AbapAddonAssemblyKitRegisterPackagesCommand This step uploads the SAR archives and creates physical Delivery Packages in in the File Content Management System of SAP. +func AbapAddonAssemblyKitRegisterPackagesCommand() *cobra.Command { + const STEP_NAME = "abapAddonAssemblyKitRegisterPackages" + + metadata := abapAddonAssemblyKitRegisterPackagesMetadata() + var stepConfig abapAddonAssemblyKitRegisterPackagesOptions + var startTime time.Time + var commonPipelineEnvironment abapAddonAssemblyKitRegisterPackagesCommonPipelineEnvironment + + var createAbapAddonAssemblyKitRegisterPackagesCmd = &cobra.Command{ + Use: STEP_NAME, + Short: "This step uploads the SAR archives and creates physical Delivery Packages in in the File Content Management System of SAP.", + Long: `This step takes the list of Software Component Versions from the addonDescriptor in the commonPipelineEnvironment. +For Packages in status "P"lanned it uploads the SAR archive with the data file and metadata XML of the Delivery Packages composed and exported in the Final Assembly system. +and creates physical Delivery Packages. The Packages ares saved in the File Content Management System of SAP. +The new status "L"ocked is written back to the addonDescriptor in the commonPipelineEnvironment.`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + startTime = time.Now() + log.SetStepName(STEP_NAME) + log.SetVerbose(GeneralConfig.Verbose) + + path, _ := os.Getwd() + fatalHook := &log.FatalHook{CorrelationID: GeneralConfig.CorrelationID, Path: path} + log.RegisterHook(fatalHook) + + err := PrepareConfig(cmd, &metadata, STEP_NAME, &stepConfig, config.OpenPiperFile) + if err != nil { + log.SetErrorCategory(log.ErrorConfiguration) + return err + } + log.RegisterSecret(stepConfig.Username) + log.RegisterSecret(stepConfig.Password) + + if len(GeneralConfig.HookConfig.SentryConfig.Dsn) > 0 { + sentryHook := log.NewSentryHook(GeneralConfig.HookConfig.SentryConfig.Dsn, GeneralConfig.CorrelationID) + log.RegisterHook(&sentryHook) + } + + return nil + }, + Run: func(_ *cobra.Command, _ []string) { + telemetryData := telemetry.CustomData{} + telemetryData.ErrorCode = "1" + handler := func() { + commonPipelineEnvironment.persist(GeneralConfig.EnvRootPath, "commonPipelineEnvironment") + telemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds()) + telemetry.Send(&telemetryData) + } + log.DeferExitHandler(handler) + defer handler() + telemetry.Initialize(GeneralConfig.NoTelemetry, STEP_NAME) + abapAddonAssemblyKitRegisterPackages(stepConfig, &telemetryData, &commonPipelineEnvironment) + telemetryData.ErrorCode = "0" + log.Entry().Info("SUCCESS") + }, + } + + addAbapAddonAssemblyKitRegisterPackagesFlags(createAbapAddonAssemblyKitRegisterPackagesCmd, &stepConfig) + return createAbapAddonAssemblyKitRegisterPackagesCmd +} + +func addAbapAddonAssemblyKitRegisterPackagesFlags(cmd *cobra.Command, stepConfig *abapAddonAssemblyKitRegisterPackagesOptions) { + cmd.Flags().StringVar(&stepConfig.AbapAddonAssemblyKitEndpoint, "abapAddonAssemblyKitEndpoint", os.Getenv("PIPER_abapAddonAssemblyKitEndpoint"), "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.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("username") + cmd.MarkFlagRequired("password") + cmd.MarkFlagRequired("addonDescriptor") +} + +// retrieve step metadata +func abapAddonAssemblyKitRegisterPackagesMetadata() config.StepData { + var theMetaData = config.StepData{ + Metadata: config.StepMetadata{ + Name: "abapAddonAssemblyKitRegisterPackages", + Aliases: []config.Alias{}, + }, + Spec: config.StepSpec{ + Inputs: config.StepInputs{ + Parameters: []config.StepParameters{ + { + Name: "abapAddonAssemblyKitEndpoint", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + }, + { + Name: "username", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + }, + { + Name: "password", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + }, + { + Name: "addonDescriptor", + ResourceRef: []config.ResourceReference{ + { + Name: "commonPipelineEnvironment", + Param: "abap/addonDescriptor", + }, + }, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + }, + }, + }, + }, + } + return theMetaData +} diff --git a/cmd/abapAddonAssemblyKitRegisterPackages_generated_test.go b/cmd/abapAddonAssemblyKitRegisterPackages_generated_test.go new file mode 100644 index 0000000000..74d1cce8a2 --- /dev/null +++ b/cmd/abapAddonAssemblyKitRegisterPackages_generated_test.go @@ -0,0 +1,16 @@ +package cmd + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAbapAddonAssemblyKitRegisterPackagesCommand(t *testing.T) { + + testCmd := AbapAddonAssemblyKitRegisterPackagesCommand() + + // only high level testing performed - details are tested in step generation procedure + assert.Equal(t, "abapAddonAssemblyKitRegisterPackages", testCmd.Use, "command name incorrect") + +} diff --git a/cmd/abapAddonAssemblyKitRegisterPackages_test.go b/cmd/abapAddonAssemblyKitRegisterPackages_test.go new file mode 100644 index 0000000000..06c7f022d1 --- /dev/null +++ b/cmd/abapAddonAssemblyKitRegisterPackages_test.go @@ -0,0 +1,186 @@ +package cmd + +import ( + "encoding/json" + "testing" + + "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/pkg/errors" + "github.com/stretchr/testify/assert" +) + +func mockReader(path string) ([]byte, error) { + var file []byte + if path == "exists" { + return file, nil + } + return file, errors.New("error reading the file") +} + +func TestRegisterPackagesStep(t *testing.T) { + var config abapAddonAssemblyKitRegisterPackagesOptions + var cpe abapAddonAssemblyKitRegisterPackagesCommonPipelineEnvironment + t.Run("step success", func(t *testing.T) { + client := &abaputils.ClientMock{ + BodyList: []string{responseRegisterPackagesPost, "myToken", "dummyResponseUpload"}, + } + addonDescriptor := abaputils.AddonDescriptor{ + Repositories: []abaputils.Repository{ + { + PackageName: "SAPK-002AAINDRNMSPC", + Status: "P", + SarXMLFilePath: "exists", + }, + { + PackageName: "SAPK-001AAINDRNMSPC", + Status: "R", + }, + }, + } + adoDesc, _ := json.Marshal(addonDescriptor) + config.AddonDescriptor = string(adoDesc) + err := runAbapAddonAssemblyKitRegisterPackages(&config, nil, client, &cpe, mockReader) + + assert.NoError(t, err, "Did not expect error") + var addonDescriptorFinal abaputils.AddonDescriptor + json.Unmarshal([]byte(cpe.abap.addonDescriptor), &addonDescriptorFinal) + assert.Equal(t, "L", addonDescriptorFinal.Repositories[0].Status) + }) + + t.Run("step error - uploadSarFiles", func(t *testing.T) { + client := &abaputils.ClientMock{ + Body: "ErrorBody", + Error: errors.New("Failure during upload of SAR file"), + } + addonDescriptor := abaputils.AddonDescriptor{ + Repositories: []abaputils.Repository{ + { + PackageName: "SAPK-002AAINDRNMSPC", + Status: "P", + SarXMLFilePath: "exists", + }, + }, + } + adoDesc, _ := json.Marshal(addonDescriptor) + config.AddonDescriptor = string(adoDesc) + err := runAbapAddonAssemblyKitRegisterPackages(&config, nil, client, &cpe, mockReader) + assert.Error(t, err, "Did expect error") + }) + t.Run("step error - registerPackages - invalid input", func(t *testing.T) { + client := &abaputils.ClientMock{ + BodyList: []string{"myToken", "dummyResponseUpload"}, + } + addonDescriptor := abaputils.AddonDescriptor{ + Repositories: []abaputils.Repository{ + { + Status: "P", + SarXMLFilePath: "exists", + }, + }, + } + adoDesc, _ := json.Marshal(addonDescriptor) + config.AddonDescriptor = string(adoDesc) + err := runAbapAddonAssemblyKitRegisterPackages(&config, nil, client, &cpe, mockReader) + assert.Error(t, err, "Did expect error") + }) +} + +// ********************* Test uploadSarFiles ******************* +func TestUploadSarFiles(t *testing.T) { + t.Run("test uploadSarFiles - success", func(t *testing.T) { + client := abaputils.ClientMock{ + Body: "dummy", + } + repositories, conn := setupRepos("exists", aakaas.PackageStatusPlanned, client) + err := uploadSarFiles(repositories, conn, mockReader) + assert.NoError(t, err) + }) + t.Run("test uploadSarFiles - error due to missing file path", func(t *testing.T) { + repositories, conn := setupRepos("", aakaas.PackageStatusPlanned, abaputils.ClientMock{}) + err := uploadSarFiles(repositories, conn, mockReader) + assert.Error(t, err) + }) + t.Run("test uploadSarFiles - error due to missing file", func(t *testing.T) { + repositories, conn := setupRepos("does_not_exist", aakaas.PackageStatusPlanned, abaputils.ClientMock{}) + err := uploadSarFiles(repositories, conn, mockReader) + assert.Error(t, err) + }) + t.Run("test uploadSarFiles - error during upload", func(t *testing.T) { + client := abaputils.ClientMock{ + Body: "ErrorBody", + Error: errors.New("Failure during upload of SAR file"), + } + repositories, conn := setupRepos("exists", aakaas.PackageStatusPlanned, client) + err := uploadSarFiles(repositories, conn, mockReader) + assert.Error(t, err) + }) +} + +// ********************* Test registerPackages ******************* +func TestRegisterPackages(t *testing.T) { + t.Run("test registerPackages - planned", func(t *testing.T) { + client := abaputils.ClientMock{ + Body: responseRegisterPackagesPost, + } + repositories, conn := setupRepos("Filepath", aakaas.PackageStatusPlanned, client) + repos, err := registerPackages(repositories, conn) + assert.NoError(t, err) + assert.Equal(t, string(aakaas.PackageStatusLocked), repos[0].Status) + }) + t.Run("test registerPackages - released", func(t *testing.T) { + repositories, conn := setupRepos("Filepath", aakaas.PackageStatusReleased, abaputils.ClientMock{}) + repos, err := registerPackages(repositories, conn) + assert.NoError(t, err) + assert.Equal(t, string(aakaas.PackageStatusReleased), repos[0].Status) + }) + t.Run("test registerPackages - with error", func(t *testing.T) { + client := abaputils.ClientMock{ + Body: "ErrorBody", + Error: errors.New("Failure during registration"), + } + repositories, conn := setupRepos("Filepath", aakaas.PackageStatusPlanned, client) + repos, err := registerPackages(repositories, conn) + assert.Error(t, err) + assert.Equal(t, string(aakaas.PackageStatusPlanned), repos[0].Status) + }) +} + +// ********************* Test Setup ******************* +func setupRepos(filePath string, status aakaas.PackageStatus, cl abaputils.ClientMock) ([]abaputils.Repository, abapbuild.Connector) { + repositories := []abaputils.Repository{ + { + Name: "/DRNMSPC/COMP01", + VersionYAML: "1.0.0", + PackageName: "SAPK-001AAINDRNMSPC", + Status: string(status), + SarXMLFilePath: filePath, + }, + } + conn := new(abapbuild.Connector) + conn.Client = &cl + conn.Header = make(map[string][]string) + return repositories, *conn +} + +// ********************* Testdata ******************* + +var responseRegisterPackagesPost = `{ + "d": { + "__metadata": { + "id": "https://W7Q.DMZWDF.SAP.CORP:443/odata/aas_ocs_package/OcsPackageSet('SAPK-001AAINDRNMSPC')", + "uri": "https://W7Q.DMZWDF.SAP.CORP:443/odata/aas_ocs_package/OcsPackageSet('SAPK-001AAINDRNMSPC')", + "type": "SSDA.AAS_ODATA_PACKAGE_SRV.OcsPackage" + }, + "Name": "SAPK-001AAINDRNMSPC", + "Type": "AOI", + "Component": "/DRNMSPC/COMP01", + "Release": "0001", + "Level": "0000", + "Status": "L", + "Operation": "", + "Namespace": "/DRNMSPC/", + "Vendorid": "0000203069" + } +}` diff --git a/cmd/piper.go b/cmd/piper.go index 167f3bbc7d..121aa2e0dc 100644 --- a/cmd/piper.go +++ b/cmd/piper.go @@ -109,7 +109,7 @@ func Execute() { rootCmd.AddCommand(AbapAddonAssemblyKitCheckPVCommand()) rootCmd.AddCommand(AbapAddonAssemblyKitCreateTargetVectorCommand()) rootCmd.AddCommand(AbapAddonAssemblyKitPublishTargetVectorCommand()) - // rootCmd.AddCommand(AbapAddonAssemblyKitRegisterPackagesCommand()) + rootCmd.AddCommand(AbapAddonAssemblyKitRegisterPackagesCommand()) // rootCmd.AddCommand(AbapAddonAssemblyKitReleasePackagesCommand()) // rootCmd.AddCommand(AbapAddonAssemblyKitReserveNextPackagesCommand()) diff --git a/documentation/docs/steps/abapAddonAssemblyKitPublishTargetVector.md b/documentation/docs/steps/abapAddonAssemblyKitPublishTargetVector.md index 098499c31c..26e0fb1bd3 100644 --- a/documentation/docs/steps/abapAddonAssemblyKitPublishTargetVector.md +++ b/documentation/docs/steps/abapAddonAssemblyKitPublishTargetVector.md @@ -23,7 +23,7 @@ The recommended way to configure your pipeline is via the config.yml file. In th ```groovy abapAddonAssemblyKitPublishTargetVector( - scopeTV: 'scopeTV', + targetVectorScope: 'T', script: this, ) ``` diff --git a/documentation/docs/steps/abapAddonAssemblyKitRegisterPackages.md b/documentation/docs/steps/abapAddonAssemblyKitRegisterPackages.md new file mode 100644 index 0000000000..40dcb54b4f --- /dev/null +++ b/documentation/docs/steps/abapAddonAssemblyKitRegisterPackages.md @@ -0,0 +1,83 @@ +# ${docGenStepName} + +## ${docGenDescription} + +## Prerequisites + +* The credentials to access the AAKaaS (e.g. S-User) must be stored in the Jenkins Credential Store +* This step needs the names of the packages which should be registered. For each package a SAR archive with the data file and metadata XML must be provided. +* The package names and their status are taken from the addonDescriptor in the commonPipelineEnvironment, as well as the SarXMLFilePath with the path to the SAR file. +* The information will be written to the commonPipelineEnvironment if you run prior to this step the step [abapAddonAssemblyKitReserveNextPackages](https://sap.github.io/jenkins-library/steps/abapAddonAssemblyKitReserveNextPackages) +* The SAR archive is produced if you run the step [abapEnvironmentAssemblePackages](https://sap.github.io/jenkins-library/steps/abapEnvironmentAssemblePackages) + +## ${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 +abapAddonAssemblyKitRegisterPackages script: this +``` + +The config.yml should look like this: + +```yaml +steps: + abapAddonAssemblyKitRegisterPackages: + abapAddonAssemblyKitCredentialsId: 'abapAddonAssemblyKitCredentialsId', + abapAddonAssemblyKitEndpoint: 'https://myabapAddonAssemblyKitEndpoint.com', +``` + +### Input via the CommonPipelineEnvironment + +Mandatory fields: + +```json +{"addonProduct":"", +"addonVersion":"", +"addonVersionAAK":"", +"addonUniqueID":"", +"customerID":"", +"AddonSpsLevel":"", +"AddonPatchLevel":"", +"TargetVectorID":"", +"repositories":[ + { + "name":, + "tag":"", + "branch":"", + "version":"", + "versionAAK":"", + "PackageName":"SAPK001001REPOA", + "PackageType":"", + "SpLevel":"", + "PatchLevel":"", + "PredecessorCommitID":"", + "Status":"P", + "Namespace":"", + "SarXMLFilePath":".pipeline/commonPipelineEnvironment/SAPK001001REPOA.SAR" + }, + { + "name":"", + "tag":"", + "branch":"", + "version":"", + "versionAAK":"", + "PackageName":"SAPK002001REPOB", + "PackageType":"", + "SpLevel":"", + "PatchLevel":"", + "PredecessorCommitID":"", + "Status":"R", + "Namespace":"", + "SarXMLFilePath":".pipeline/commonPipelineEnvironment/SAPK002001REPOB.SAR" + } +]} +``` diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml index cea250a41f..78b69d2355 100644 --- a/documentation/mkdocs.yml +++ b/documentation/mkdocs.yml @@ -53,7 +53,7 @@ nav: - abapAddonAssemblyKitCheckCVs: steps/abapAddonAssemblyKitCheckPV.md - abapAddonAssemblyKitCreateTargetVector: steps/abapAddonAssemblyKitCreateTargetVector.md - abapAddonAssemblyKitPublishTargetVector: steps/abapAddonAssemblyKitPublishTargetVector.md -# - abapAddonAssemblyKitRegisterPackages: steps/abapAddonAssemblyKitRegisterPackages.md + - abapAddonAssemblyKitRegisterPackages: steps/abapAddonAssemblyKitRegisterPackages.md # - abapAddonAssemblyKitReleasePackages: steps/abapAddonAssemblyKitReleasePackages.md # - abapAddonAssemblyKitReserveNextPackages: steps/abapAddonAssemblyKitReserveNextPackages.md - abapEnvironmentAssemblePackages: steps/abapEnvironmentAssemblePackages.md diff --git a/pkg/abap/aakaas/package.go b/pkg/abap/aakaas/package.go new file mode 100644 index 0000000000..4a189c47bc --- /dev/null +++ b/pkg/abap/aakaas/package.go @@ -0,0 +1,132 @@ +package aakaas + +import ( + "encoding/json" + "errors" + + abapbuild "github.com/SAP/jenkins-library/pkg/abap/build" + "github.com/SAP/jenkins-library/pkg/abaputils" + "github.com/SAP/jenkins-library/pkg/log" +) + +// PackageStatus : Status of an ABAP delivery package +type PackageStatus string + +const ( + // PackageStatusPlanned : Package is Planned + PackageStatusPlanned PackageStatus = "P" + // PackageStatusLocked : Package is Locked + PackageStatusLocked PackageStatus = "L" + // PackageStatusReleased : Package is Released + PackageStatusReleased PackageStatus = "R" + // PackageStatusCreationTriggered : Package was Released but Release procedure is not yet finished + PackageStatusCreationTriggered PackageStatus = "C" +) + +type jsonPackageDeterminePackageForScv struct { + DeterminePackage struct { + Package *Package `json:"DeterminePackageForScv"` + } `json:"d"` +} + +type jsonPackage struct { + Package *Package `json:"d"` +} + +// Package : ABAP delivery package +type Package struct { + abapbuild.Connector + ComponentName string + PackageName string `json:"Name"` + VersionYAML string + Type string `json:"Type"` + PredecessorCommitID string `json:"PredecessorCommitId"` + Status PackageStatus `json:"Status"` + Namespace string `json:"Namespace"` +} + +type packageWithRepository struct { + p Package + repo abaputils.Repository +} + +// InitPackage : initialize package attributes from the repository +func (p *Package) InitPackage(repo abaputils.Repository, conn abapbuild.Connector) { + p.Connector = conn + p.ComponentName = repo.Name + p.VersionYAML = repo.VersionYAML + p.PackageName = repo.PackageName + p.Status = PackageStatus(repo.Status) +} + +// CopyFieldsToRepo : copy package attributes to the repository +func (p *Package) CopyFieldsToRepo(initialRepo *abaputils.Repository) { + initialRepo.PackageName = p.PackageName + initialRepo.PackageType = p.Type + initialRepo.PredecessorCommitID = p.PredecessorCommitID + initialRepo.Status = string(p.Status) + initialRepo.Namespace = p.Namespace + log.Entry().Infof("Package name %s, type %s, status %s, namespace %s, predecessorCommitID %s", p.PackageName, p.Type, p.Status, p.Namespace, p.PredecessorCommitID) +} + +// ReserveNext : reserve next delivery package for this software component version +func (p *Package) ReserveNext() error { + if p.ComponentName == "" || p.VersionYAML == "" { + return errors.New("Parameters missing. Please provide the name and version of the component") + } + log.Entry().Infof("Reserve package for %s version %s", p.ComponentName, p.VersionYAML) + p.Connector.GetToken("/odata/aas_ocs_package") + appendum := "/odata/aas_ocs_package/DeterminePackageForScv?Name='" + p.ComponentName + "'&Version='" + p.VersionYAML + "'" + body, err := p.Connector.Post(appendum, "") + if err != nil { + return err + } + var jPck jsonPackageDeterminePackageForScv + json.Unmarshal(body, &jPck) + p.PackageName = jPck.DeterminePackage.Package.PackageName + p.Type = jPck.DeterminePackage.Package.Type + p.PredecessorCommitID = jPck.DeterminePackage.Package.PredecessorCommitID + p.Status = jPck.DeterminePackage.Package.Status + p.Namespace = jPck.DeterminePackage.Package.Namespace + log.Entry().Infof("Reservation of package %s started", p.PackageName) + return nil +} + +// GetPackageAndNamespace : retrieve attributes of the package from AAKaaS +func (p *Package) GetPackageAndNamespace() error { + appendum := "/odata/aas_ocs_package/OcsPackageSet('" + p.PackageName + "')" + body, err := p.Connector.Get(appendum) + if err != nil { + return err + } + var jPck jsonPackage + json.Unmarshal(body, &jPck) + p.Status = jPck.Package.Status + p.Namespace = jPck.Package.Namespace + return nil +} + +// ChangeStatus : change status of the package in the repository +func (p *Package) ChangeStatus(initialRepo *abaputils.Repository) { + initialRepo.Status = string(p.Status) +} + +// Register : register package in AAKaaS +func (p *Package) Register() error { + if p.PackageName == "" { + return errors.New("Parameter missing. Please provide the name of the package which should be registered") + } + log.Entry().Infof("Register package %s", p.PackageName) + p.Connector.GetToken("/odata/aas_ocs_package") + appendum := "/odata/aas_ocs_package/RegisterPackage?Name='" + p.PackageName + "'" + body, err := p.Connector.Post(appendum, "") + if err != nil { + return err + } + + var jPck jsonPackage + json.Unmarshal(body, &jPck) + p.Status = jPck.Package.Status + log.Entry().Infof("Package status %s", p.Status) + return nil +} diff --git a/pkg/abap/build/connector.go b/pkg/abap/build/connector.go index c9e35846a3..cd4ab26390 100644 --- a/pkg/abap/build/connector.go +++ b/pkg/abap/build/connector.go @@ -104,3 +104,16 @@ func (conn *Connector) InitAAKaaS(aAKaaSEndpoint string, username string, passwo }) conn.Baseurl = aAKaaSEndpoint } + +// UploadSarFile : upload *.sar file +func (conn Connector) UploadSarFile(appendum string, sarFile []byte) error { + url := conn.Baseurl + appendum + response, err := conn.Client.SendRequest("PUT", url, bytes.NewBuffer(sarFile), conn.Header, nil) + if err != nil { + defer response.Body.Close() + errorbody, _ := ioutil.ReadAll(response.Body) + return errors.Wrapf(err, "Upload of SAR file failed: %v", string(errorbody)) + } + defer response.Body.Close() + return nil +} diff --git a/resources/metadata/abapAddonAssemblyKitRegisterPackages.yaml b/resources/metadata/abapAddonAssemblyKitRegisterPackages.yaml new file mode 100644 index 0000000000..742f3e1e3b --- /dev/null +++ b/resources/metadata/abapAddonAssemblyKitRegisterPackages.yaml @@ -0,0 +1,58 @@ +metadata: + name: abapAddonAssemblyKitRegisterPackages + description: This step uploads the SAR archives and creates physical Delivery Packages in in the File Content Management System of SAP. + longDescription: | + This step takes the list of Software Component Versions from the addonDescriptor in the commonPipelineEnvironment. + For Packages in status "P"lanned it uploads the SAR archive with the data file and metadata XML of the Delivery Packages composed and exported in the Final Assembly system. + and creates physical Delivery Packages. The Packages ares saved in the File Content Management System of SAP. + The new status "L"ocked is written back to the addonDescriptor in the commonPipelineEnvironment. + +spec: + inputs: + secrets: + - name: abapAddonAssemblyKitCredentialsId + description: Credential stored in Jenkins for the Addon Assembly Kit as a Service (AAKaaS) system + type: jenkins + params: + - name: abapAddonAssemblyKitEndpoint + type: string + description: Base URL to the Addon Assembly Kit as a Service (AAKaaS) system + scope: + - PARAMETERS + - STAGES + - STEPS + - GENERAL + mandatory: true + - name: username + type: string + description: User for the Addon Assembly Kit as a Service (AAKaaS) system + scope: + - PARAMETERS + - STAGES + - STEPS + mandatory: true + secret: true + - name: password + type: string + description: Password for the Addon Assembly Kit as a Service (AAKaaS) system + scope: + - PARAMETERS + mandatory: true + secret: true + - name: addonDescriptor + type: string + description: Structure in the commonPipelineEnvironment containing information about the Product Version and corresponding Software Component Versions + mandatory: true + 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 d73fb5b854..61eef95231 100644 --- a/test/groovy/CommonStepsTest.groovy +++ b/test/groovy/CommonStepsTest.groovy @@ -110,7 +110,7 @@ public class CommonStepsTest extends BasePiperTest{ 'abapAddonAssemblyKitCheckPV', //implementing new golang pattern without fields 'abapAddonAssemblyKitCreateTargetVector', //implementing new golang pattern without fields 'abapAddonAssemblyKitPublishTargetVector', //implementing new golang pattern without fields - //'abapAddonAssemblyKitRegisterPackages', //implementing new golang pattern without fields + 'abapAddonAssemblyKitRegisterPackages', //implementing new golang pattern without fields //'abapAddonAssemblyKitReleasePackages', //implementing new golang pattern without fields //'abapAddonAssemblyKitReserveNextPackages', //implementing new golang pattern without fields 'abapEnvironmentAssemblePackages', //implementing new golang pattern without fields diff --git a/vars/abapAddonAssemblyKitRegisterPackages.groovy b/vars/abapAddonAssemblyKitRegisterPackages.groovy new file mode 100644 index 0000000000..a621490a1f --- /dev/null +++ b/vars/abapAddonAssemblyKitRegisterPackages.groovy @@ -0,0 +1,11 @@ +import groovy.transform.Field + +@Field String STEP_NAME = getClass().getName() +@Field String METADATA_FILE = 'metadata/abapAddonAssemblyKitRegisterPackages.yaml' + +void call(Map parameters = [:]) { + List credentials = [ + [type: 'usernamePassword', id: 'abapAddonAssemblyKitCredentialsId', env: ['PIPER_username', 'PIPER_password']] + ] + piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials, false, false, true) +}