diff --git a/cmd/abapAddonAssemblyKitCheckCVs.go b/cmd/abapAddonAssemblyKitCheckCVs.go index e17755599..1a111bb66 100644 --- a/cmd/abapAddonAssemblyKitCheckCVs.go +++ b/cmd/abapAddonAssemblyKitCheckCVs.go @@ -1,10 +1,6 @@ package cmd import ( - "encoding/json" - "fmt" - "net/url" - "github.com/SAP/jenkins-library/pkg/abap/aakaas" abapbuild "github.com/SAP/jenkins-library/pkg/abap/build" "github.com/SAP/jenkins-library/pkg/abaputils" @@ -21,6 +17,11 @@ func abapAddonAssemblyKitCheckCVs(config abapAddonAssemblyKitCheckCVsOptions, te } func runAbapAddonAssemblyKitCheckCVs(config *abapAddonAssemblyKitCheckCVsOptions, telemetryData *telemetry.CustomData, utils *aakaas.AakUtils, cpe *abapAddonAssemblyKitCheckCVsCommonPipelineEnvironment) error { + + log.Entry().Info("╔══════════════════════════════╗") + log.Entry().Info("║ abapAddonAssemblyKitCheckCVs ║") + log.Entry().Info("╚══════════════════════════════╝") + conn := new(abapbuild.Connector) if err := conn.InitAAKaaS(config.AbapAddonAssemblyKitEndpoint, config.Username, config.Password, *utils); err != nil { return err @@ -32,14 +33,22 @@ func runAbapAddonAssemblyKitCheckCVs(config *abapAddonAssemblyKitCheckCVsOptions return err } - for i := range addonDescriptor.Repositories { - var c componentVersion - c.initCV(addonDescriptor.Repositories[i], *conn) - err := c.validate() - if err != nil { + for i, repo := range addonDescriptor.Repositories { + componentVersion := new(aakaas.ComponentVersion) + if err := componentVersion.ConstructComponentVersion(addonDescriptor.Repositories[i], *conn); err != nil { return err } - c.copyFieldsToRepo(&addonDescriptor.Repositories[i]) + if err := componentVersion.Validate(); err != nil { + return err + } + componentVersion.CopyVersionFieldsToRepo(&addonDescriptor.Repositories[i]) + + log.Entry().Infof("Using cCTS %t", repo.UseClassicCTS) + log.Entry().Infof("CommitId %s", repo.CommitID) + + if !repo.UseClassicCTS && repo.CommitID == "" { + return errors.Errorf("CommitID missing in repo '%s' of the addon.yml", repo.Name) + } } // now Software Component Versions fields are valid, but maybe Product Version was checked before, so copy that part from CPE @@ -63,57 +72,3 @@ func combineYAMLRepositoriesWithCPEProduct(addonDescriptor abaputils.AddonDescri addonDescriptorFromCPE.Repositories = addonDescriptor.Repositories return addonDescriptorFromCPE } - -func (c *componentVersion) initCV(repo abaputils.Repository, conn abapbuild.Connector) { - c.Connector = conn - c.Name = repo.Name - c.VersionYAML = repo.VersionYAML - c.CommitID = repo.CommitID - c.UseClassicCTS = repo.UseClassicCTS -} - -func (c *componentVersion) copyFieldsToRepo(initialRepo *abaputils.Repository) { - initialRepo.Version = c.Version - initialRepo.SpLevel = c.SpLevel - initialRepo.PatchLevel = c.PatchLevel -} - -func (c *componentVersion) validate() error { - log.Entry().Infof("Validate component %s version %s and resolve version", c.Name, c.VersionYAML) - appendum := "/odata/aas_ocs_package/ValidateComponentVersion?Name='" + url.QueryEscape(c.Name) + "'&Version='" + url.QueryEscape(c.VersionYAML) + "'" - body, err := c.Connector.Get(appendum) - if err != nil { - return err - } - var jCV jsonComponentVersion - if err := json.Unmarshal(body, &jCV); err != nil { - return errors.Wrap(err, "Unexpected AAKaaS response for Validate Component Version: "+string(body)) - } - c.Name = jCV.ComponentVersion.Name - c.Version = jCV.ComponentVersion.Version - c.SpLevel = jCV.ComponentVersion.SpLevel - c.PatchLevel = jCV.ComponentVersion.PatchLevel - log.Entry().Infof("Resolved version %s, splevel %s, patchlevel %s", c.Version, c.SpLevel, c.PatchLevel) - log.Entry().Infof("Using cCTS %t", c.UseClassicCTS) - - if !c.UseClassicCTS && c.CommitID == "" { - return fmt.Errorf("CommitID missing in repo '%s' of the addon.yml", c.Name) - } - - return nil -} - -type jsonComponentVersion struct { - ComponentVersion *componentVersion `json:"d"` -} - -type componentVersion struct { - abapbuild.Connector - Name string `json:"Name"` - VersionYAML string - Version string `json:"Version"` - SpLevel string `json:"SpLevel"` - PatchLevel string `json:"PatchLevel"` - UseClassicCTS bool - CommitID string -} diff --git a/cmd/abapAddonAssemblyKitCheckCVs_test.go b/cmd/abapAddonAssemblyKitCheckCVs_test.go index 13e36b83d..375987449 100644 --- a/cmd/abapAddonAssemblyKitCheckCVs_test.go +++ b/cmd/abapAddonAssemblyKitCheckCVs_test.go @@ -5,9 +5,7 @@ import ( "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" ) @@ -15,7 +13,7 @@ func TestCheckCVsStep(t *testing.T) { var config abapAddonAssemblyKitCheckCVsOptions var cpe abapAddonAssemblyKitCheckCVsCommonPipelineEnvironment bundle := aakaas.NewAakBundleMock() - bundle.SetBody(responseCheckCVs) + bundle.SetBody(aakaas.ResponseCheckCVs) utils := bundle.GetUtils() config.Username = "dummyUser" config.Password = "dummyPassword" @@ -52,78 +50,6 @@ func TestCheckCVsStep(t *testing.T) { }) } -func TestInitCV(t *testing.T) { - t.Run("test init", func(t *testing.T) { - conn := new(abapbuild.Connector) - conn.Client = &abaputils.ClientMock{} - repo := abaputils.Repository{ - Name: "/DRNMSPC/COMP01", - VersionYAML: "1.2.3", - } - var c componentVersion - c.initCV(repo, *conn) - assert.Equal(t, "/DRNMSPC/COMP01", c.Name) - assert.Equal(t, "1.2.3", c.VersionYAML) - }) -} - -func TestValidateCV(t *testing.T) { - conn := new(abapbuild.Connector) - t.Run("test validate - success", func(t *testing.T) { - conn.Client = &abaputils.ClientMock{ - Body: responseCheckCVs, - } - c := componentVersion{ - Connector: *conn, - Name: "/DRNMSPC/COMP01", - VersionYAML: "1.2.3", - CommitID: "HUGO1234", - } - conn.Client = &abaputils.ClientMock{ - Body: responseCheckCVs, - } - err := c.validate() - assert.NoError(t, err) - assert.Equal(t, "0001", c.Version) - assert.Equal(t, "0002", c.SpLevel) - assert.Equal(t, "0003", c.PatchLevel) - }) - t.Run("test validate - with error", func(t *testing.T) { - conn.Client = &abaputils.ClientMock{ - Body: "ErrorBody", - Error: errors.New("Validation failed"), - } - c := componentVersion{ - Connector: *conn, - Name: "/DRNMSPC/COMP01", - VersionYAML: "1.2.3", - CommitID: "HUGO1234", - } - err := c.validate() - assert.Error(t, err) - assert.Equal(t, "", c.Version) - assert.Equal(t, "", c.SpLevel) - assert.Equal(t, "", c.PatchLevel) - }) -} - -func TestCopyFieldsCV(t *testing.T) { - t.Run("test copyFieldsToRepo", func(t *testing.T) { - repo := abaputils.Repository{ - Name: "/DRNMSPC/COMP01", - VersionYAML: "1.2.3", - } - var c componentVersion - c.Version = "0001" - c.SpLevel = "0002" - c.PatchLevel = "0003" - c.copyFieldsToRepo(&repo) - assert.Equal(t, "0001", repo.Version) - assert.Equal(t, "0002", repo.SpLevel) - assert.Equal(t, "0003", repo.PatchLevel) - }) -} - func TestCombineYAMLRepositoriesWithCPEProduct(t *testing.T) { t.Run("test combineYAMLRepositoriesWithCPEProduct", func(t *testing.T) { addonDescriptor := abaputils.AddonDescriptor{ @@ -151,19 +77,3 @@ func TestCombineYAMLRepositoriesWithCPEProduct(t *testing.T) { assert.Equal(t, "3.2.1", finalAddonDescriptor.Repositories[1].VersionYAML) }) } - -var responseCheckCVs = `{ - "d": { - "__metadata": { - "id": "https://W7Q.DMZWDF.SAP.CORP:443/odata/aas_ocs_package/SoftwareComponentVersionSet(Name='%2FDRNMSPC%2FCOMP01',Version='0001')", - "uri": "https://W7Q.DMZWDF.SAP.CORP:443/odata/aas_ocs_package/SoftwareComponentVersionSet(Name='%2FDRNMSPC%2FCOMP01',Version='0001')", - "type": "SSDA.AAS_ODATA_PACKAGE_SRV.SoftwareComponentVersion" - }, - "Name": "/DRNMSPC/COMP01", - "Version": "0001", - "SpLevel": "0002", - "PatchLevel": "0003", - "Vendor": "", - "VendorType": "" - } -}` diff --git a/cmd/abapAddonAssemblyKitCheckPV.go b/cmd/abapAddonAssemblyKitCheckPV.go index e36218a68..2f9782033 100644 --- a/cmd/abapAddonAssemblyKitCheckPV.go +++ b/cmd/abapAddonAssemblyKitCheckPV.go @@ -1,16 +1,12 @@ package cmd import ( - "encoding/json" - "net/url" - "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" - "github.com/pkg/errors" ) func abapAddonAssemblyKitCheckPV(config abapAddonAssemblyKitCheckPVOptions, telemetryData *telemetry.CustomData, cpe *abapAddonAssemblyKitCheckPVCommonPipelineEnvironment) { @@ -22,6 +18,10 @@ func abapAddonAssemblyKitCheckPV(config abapAddonAssemblyKitCheckPVOptions, tele } func runAbapAddonAssemblyKitCheckPV(config *abapAddonAssemblyKitCheckPVOptions, telemetryData *telemetry.CustomData, utils aakaas.AakUtils, cpe *abapAddonAssemblyKitCheckPVCommonPipelineEnvironment) error { + log.Entry().Info("╔═════════════════════════════╗") + log.Entry().Info("║ abapAddonAssemblyKitCheckPV ║") + log.Entry().Info("╚═════════════════════════════╝") + conn := new(abapbuild.Connector) if err := conn.InitAAKaaS(config.AbapAddonAssemblyKitEndpoint, config.Username, config.Password, utils); err != nil { return err @@ -33,12 +33,14 @@ func runAbapAddonAssemblyKitCheckPV(config *abapAddonAssemblyKitCheckPVOptions, return err } - pv := new(productVersion).init(addonDescriptor, *conn) - err = pv.validateAndResolveVersionFields() - if err != nil { + pv := new(aakaas.ProductVersion) + if err := pv.ConstructProductversion(addonDescriptor, *conn); err != nil { return err } - pv.transferVersionFields(&addonDescriptor) + if err = pv.ValidateAndResolveVersionFields(); err != nil { + return err + } + pv.CopyVersionFieldsToDescriptor(&addonDescriptor) // now Product Version fields are valid, but maybe Component Versions (Repositories) were checked before, so copy that part from CPE // we don't care for errors @@ -64,50 +66,3 @@ func runAbapAddonAssemblyKitCheckPV(config *abapAddonAssemblyKitCheckPVOptions, return nil } - -func (p *productVersion) init(desc abaputils.AddonDescriptor, conn abapbuild.Connector) *productVersion { - p.Connector = conn - p.Name = desc.AddonProduct - p.VersionYAML = desc.AddonVersionYAML - - return p -} - -func (p *productVersion) transferVersionFields(initialAddonDescriptor *abaputils.AddonDescriptor) { - initialAddonDescriptor.AddonVersion = p.Version - initialAddonDescriptor.AddonSpsLevel = p.SpsLevel - initialAddonDescriptor.AddonPatchLevel = p.PatchLevel -} - -func (p *productVersion) validateAndResolveVersionFields() error { - log.Entry().Infof("Validate product '%s' version '%s' and resolve version", p.Name, p.VersionYAML) - appendum := "/odata/aas_ocs_package/ValidateProductVersion?Name='" + url.QueryEscape(p.Name) + "'&Version='" + url.QueryEscape(p.VersionYAML) + "'" - body, err := p.Connector.Get(appendum) - if err != nil { - return err - } - var jPV jsonProductVersion - if err := json.Unmarshal(body, &jPV); err != nil { - return errors.Wrap(err, "Unexpected AAKaaS response for Validate Product Version: "+string(body)) - } - p.Name = jPV.ProductVersion.Name - p.Version = jPV.ProductVersion.Version - p.SpsLevel = jPV.ProductVersion.SpsLevel - p.PatchLevel = jPV.ProductVersion.PatchLevel - log.Entry().Infof("Resolved version %s, spslevel %s, patchlevel %s", p.Version, p.SpsLevel, p.PatchLevel) - return nil -} - -type jsonProductVersion struct { - ProductVersion *productVersion `json:"d"` -} - -type productVersion struct { - abapbuild.Connector - Name string `json:"Name"` - VersionYAML string - Version string `json:"Version"` - SpsLevel string `json:"SpsLevel"` - PatchLevel string `json:"PatchLevel"` - TargetVectorID string -} diff --git a/cmd/abapAddonAssemblyKitCheckPV_test.go b/cmd/abapAddonAssemblyKitCheckPV_test.go index a8bed770b..0b31e0e03 100644 --- a/cmd/abapAddonAssemblyKitCheckPV_test.go +++ b/cmd/abapAddonAssemblyKitCheckPV_test.go @@ -5,9 +5,7 @@ import ( "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" ) @@ -15,7 +13,7 @@ func TestCheckPVStep(t *testing.T) { var config abapAddonAssemblyKitCheckPVOptions var cpe abapAddonAssemblyKitCheckPVCommonPipelineEnvironment bundle := aakaas.NewAakBundleMock() - bundle.SetBody(responseCheckPV) + bundle.SetBody(aakaas.ResponseCheckPV) utils := bundle.GetUtils() config.Username = "dummyUser" config.Password = "dummyPassword" @@ -44,87 +42,3 @@ func TestCheckPVStep(t *testing.T) { assert.Error(t, err, "Did expect error") }) } - -func TestInitPV(t *testing.T) { - t.Run("test init", func(t *testing.T) { - conn := new(abapbuild.Connector) - conn.Client = &abaputils.ClientMock{} - prodvers := abaputils.AddonDescriptor{ - AddonProduct: "/DRNMSPC/PRD01", - AddonVersionYAML: "3.2.1", - } - - var pv productVersion - pv.init(prodvers, *conn) - assert.Equal(t, "/DRNMSPC/PRD01", pv.Name) - assert.Equal(t, "3.2.1", pv.VersionYAML) - }) -} - -func TestValidatePV(t *testing.T) { - conn := new(abapbuild.Connector) - t.Run("test validate - success", func(t *testing.T) { - conn.Client = &abaputils.ClientMock{ - Body: responseCheckPV, - } - pv := productVersion{ - Connector: *conn, - Name: "/DRNMSPC/PRD01", - VersionYAML: "3.2.1", - } - err := pv.validateAndResolveVersionFields() - assert.NoError(t, err) - assert.Equal(t, "0003", pv.Version) - assert.Equal(t, "0002", pv.SpsLevel) - assert.Equal(t, "0001", pv.PatchLevel) - }) - t.Run("test validate - with error", func(t *testing.T) { - conn.Client = &abaputils.ClientMock{ - Body: "ErrorBody", - Error: errors.New("Validation failed"), - } - pv := productVersion{ - Connector: *conn, - Name: "/DRNMSPC/PRD01", - VersionYAML: "3.2.1", - } - err := pv.validateAndResolveVersionFields() - assert.Error(t, err) - assert.Equal(t, "", pv.Version) - assert.Equal(t, "", pv.SpsLevel) - assert.Equal(t, "", pv.PatchLevel) - }) -} - -func TestCopyFieldsPV(t *testing.T) { - t.Run("test copyFieldsToRepo", func(t *testing.T) { - prodVers := abaputils.AddonDescriptor{ - AddonProduct: "/DRNMSPC/PRD01", - AddonVersionYAML: "1.2.3", - } - var pv productVersion - pv.Version = "0003" - pv.SpsLevel = "0002" - pv.PatchLevel = "0001" - pv.transferVersionFields(&prodVers) - assert.Equal(t, "0003", prodVers.AddonVersion) - assert.Equal(t, "0002", prodVers.AddonSpsLevel) - assert.Equal(t, "0001", prodVers.AddonPatchLevel) - }) -} - -var responseCheckPV = `{ - "d": { - "__metadata": { - "id": "https://W7Q.DMZWDF.SAP.CORP:443/odata/aas_ocs_package/ProductVersionSet(Name='%2FDRNMSPC%2FPRD01',Version='0001')", - "uri": "https://W7Q.DMZWDF.SAP.CORP:443/odata/aas_ocs_package/ProductVersionSet(Name='%2FDRNMSPC%2FPRD01',Version='0001')", - "type": "SSDA.AAS_ODATA_PACKAGE_SRV.ProductVersion" - }, - "Name": "/DRNMSPC/PRD01", - "Version": "0003", - "SpsLevel": "0002", - "PatchLevel": "0001", - "Vendor": "", - "VendorType": "" - } -}` diff --git a/pkg/abap/aakaas/componentVersion.go b/pkg/abap/aakaas/componentVersion.go new file mode 100644 index 000000000..9956a523d --- /dev/null +++ b/pkg/abap/aakaas/componentVersion.go @@ -0,0 +1,69 @@ +package aakaas + +import ( + "encoding/json" + "net/url" + + 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/pkg/errors" +) + +const cvQueryURL string = "/odata/aas_ocs_package/xSSDAxC_Component_Version" +const cvValidateURL string = "/odata/aas_ocs_package/ValidateComponentVersion" + +type ComponentVersion struct { + versionable +} + +func (c *ComponentVersion) ConstructComponentVersion(repo abaputils.Repository, conn abapbuild.Connector) error { + if err := c.constructVersionable(repo.Name, repo.VersionYAML, conn, pvQueryURL); err != nil { + return err + } + if err := c.resolveNext(); err != nil { + return err + } + + return nil +} + +func (c *ComponentVersion) CopyVersionFieldsToRepo(repo *abaputils.Repository) { + repo.Version = c.TechRelease + repo.SpLevel = c.TechSpLevel + repo.PatchLevel = c.TechPatchLevel +} + +func (c *ComponentVersion) Validate() error { + log.Entry().Infof("Validate component %s version %s and resolve version", c.Name, c.Version) + + values := url.Values{} + values.Set("Name", "'"+c.Name+"'") + values.Set("Version", "'"+c.Version+"'") + requestUrl := cvValidateURL + "?" + values.Encode() + + body, err := c.connector.Get(requestUrl) + if err != nil { + return err + } + var response jsonComponentVersionValidationResponse + if err := json.Unmarshal(body, &response); err != nil { + return errors.Wrap(err, "Unexpected AAKaaS response for Validate Component Version: "+string(body)) + } + c.Name = response.Wrapper.Name + c.TechRelease = response.Wrapper.TechRelease + c.TechSpLevel = response.Wrapper.TechSpLevel + c.TechPatchLevel = response.Wrapper.TechPatchLevel + log.Entry().Infof("Resolved version %s, splevel %s, patchlevel %s", c.TechRelease, c.TechSpLevel, c.TechPatchLevel) + + return nil +} + +type jsonComponentVersionValidationResponse struct { + Wrapper struct { + Name string `json:"Name"` + TechRelease string `json:"Version"` + TechSpLevel string `json:"SpLevel"` + TechPatchLevel string `json:"PatchLevel"` + } `json:"d"` +} diff --git a/pkg/abap/aakaas/componentVersion_test.go b/pkg/abap/aakaas/componentVersion_test.go new file mode 100644 index 000000000..e3c469958 --- /dev/null +++ b/pkg/abap/aakaas/componentVersion_test.go @@ -0,0 +1,64 @@ +package aakaas + +import ( + "testing" + + 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 TestInitCV(t *testing.T) { + conn := new(abapbuild.Connector) + conn.Client = &abaputils.ClientMock{} + repo := abaputils.Repository{ + Name: "/DRNMSPC/COMP01", + VersionYAML: "1.2.3", + } + var c ComponentVersion + + t.Run("test init", func(t *testing.T) { + c.ConstructComponentVersion(repo, *conn) + assert.Equal(t, "/DRNMSPC/COMP01", c.Name) + assert.Equal(t, "1.2.3", c.Version) + }) + + t.Run("test validate - success", func(t *testing.T) { + conn.Client = &abaputils.ClientMock{ + Body: ResponseCheckCVs, + } + c.ConstructComponentVersion(repo, *conn) + + err := c.Validate() + + assert.NoError(t, err) + assert.Equal(t, "0001", c.TechRelease) + assert.Equal(t, "0002", c.TechSpLevel) + assert.Equal(t, "0003", c.TechPatchLevel) + }) + + t.Run("test validate - with error", func(t *testing.T) { + conn.Client = &abaputils.ClientMock{ + Body: "ErrorBody", + Error: errors.New("Validation failed"), + } + c.ConstructComponentVersion(repo, *conn) + + err := c.Validate() + + assert.Error(t, err) + }) + + t.Run("test copyFieldsToRepo", func(t *testing.T) { + + var c ComponentVersion + c.TechRelease = "0001" + c.TechSpLevel = "0002" + c.TechPatchLevel = "0003" + c.CopyVersionFieldsToRepo(&repo) + assert.Equal(t, "0001", repo.Version) + assert.Equal(t, "0002", repo.SpLevel) + assert.Equal(t, "0003", repo.PatchLevel) + }) +} diff --git a/pkg/abap/aakaas/productVersion.go b/pkg/abap/aakaas/productVersion.go new file mode 100644 index 000000000..3a5e9f75f --- /dev/null +++ b/pkg/abap/aakaas/productVersion.go @@ -0,0 +1,67 @@ +package aakaas + +import ( + "encoding/json" + "net/url" + + 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/pkg/errors" +) + +const pvQueryURL string = "/odata/aas_ocs_package/xSSDAxC_Product_Version" +const pvValidateURL string = "/odata/aas_ocs_package/ValidateProductVersion" + +type ProductVersion struct { + versionable +} + +func (p *ProductVersion) ConstructProductversion(desc abaputils.AddonDescriptor, conn abapbuild.Connector) error { + if err := p.constructVersionable(desc.AddonProduct, desc.AddonVersionYAML, conn, pvQueryURL); err != nil { + return err + } + if err := p.resolveNext(); err != nil { + return err + } + return nil +} + +func (p *ProductVersion) CopyVersionFieldsToDescriptor(desc *abaputils.AddonDescriptor) { + desc.AddonVersion = p.TechRelease + desc.AddonSpsLevel = p.TechSpLevel + desc.AddonPatchLevel = p.TechPatchLevel +} + +func (p *ProductVersion) ValidateAndResolveVersionFields() error { + log.Entry().Infof("Validate product '%s' version '%s' and resolve version", p.Name, p.Version) + + values := url.Values{} + values.Set("Name", "'"+p.Name+"'") + values.Set("Version", "'"+p.Version+"'") + requestUrl := pvValidateURL + "?" + values.Encode() + + body, err := p.connector.Get(requestUrl) + if err != nil { + return err + } + var response jsonProductVersionValidationResponse + if err := json.Unmarshal(body, &response); err != nil { + return errors.Wrap(err, "Unexpected AAKaaS response for Validate Product Version: "+string(body)) + } + p.Name = response.Wrapper.Name + p.TechRelease = response.Wrapper.TechRelease + p.TechSpLevel = response.Wrapper.TechSpLevel + p.TechPatchLevel = response.Wrapper.TechPatchLevel + log.Entry().Infof("Resolved version %s, spslevel %s, patchlevel %s", p.TechRelease, p.TechSpLevel, p.TechPatchLevel) + return nil +} + +type jsonProductVersionValidationResponse struct { + Wrapper struct { + Name string `json:"Name"` + TechRelease string `json:"Version"` + TechSpLevel string `json:"SpsLevel"` + TechPatchLevel string `json:"PatchLevel"` + } `json:"d"` +} diff --git a/pkg/abap/aakaas/productVersion_test.go b/pkg/abap/aakaas/productVersion_test.go new file mode 100644 index 000000000..35163adec --- /dev/null +++ b/pkg/abap/aakaas/productVersion_test.go @@ -0,0 +1,58 @@ +package aakaas + +import ( + "testing" + + 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 TestInitPV(t *testing.T) { + conn := new(abapbuild.Connector) + conn.Client = &abaputils.ClientMock{} + prodVers := abaputils.AddonDescriptor{ + AddonProduct: "/DRNMSPC/PRD01", + AddonVersionYAML: "3.2.1", + } + var pv ProductVersion + + t.Run("test init", func(t *testing.T) { + pv.ConstructProductversion(prodVers, *conn) + assert.Equal(t, "/DRNMSPC/PRD01", pv.Name) + assert.Equal(t, "3.2.1", pv.Version) + }) + + t.Run("test validate - success", func(t *testing.T) { + conn.Client = &abaputils.ClientMock{ + Body: ResponseCheckPV, + } + pv.ConstructProductversion(prodVers, *conn) + err := pv.ValidateAndResolveVersionFields() + assert.NoError(t, err) + assert.Equal(t, "0003", pv.TechRelease) + assert.Equal(t, "0002", pv.TechSpLevel) + assert.Equal(t, "0001", pv.TechPatchLevel) + }) + + t.Run("test validate - with error", func(t *testing.T) { + conn.Client = &abaputils.ClientMock{ + Body: "ErrorBody", + Error: errors.New("Validation failed"), + } + pv.ConstructProductversion(prodVers, *conn) + err := pv.ValidateAndResolveVersionFields() + assert.Error(t, err) + }) + + t.Run("test copyFieldsToRepo", func(t *testing.T) { + pv.TechRelease = "0003" + pv.TechSpLevel = "0002" + pv.TechPatchLevel = "0001" + pv.CopyVersionFieldsToDescriptor(&prodVers) + assert.Equal(t, "0003", prodVers.AddonVersion) + assert.Equal(t, "0002", prodVers.AddonSpsLevel) + assert.Equal(t, "0001", prodVers.AddonPatchLevel) + }) +} diff --git a/pkg/abap/aakaas/testData.go b/pkg/abap/aakaas/testData.go new file mode 100644 index 000000000..864470e6e --- /dev/null +++ b/pkg/abap/aakaas/testData.go @@ -0,0 +1,142 @@ +//go:build !release +// +build !release + +/* +** The Test Data is partly re-used by the steps in cmd folder, thus we Export them but remove the file from release build + */ + +package aakaas + +import abapbuild "github.com/SAP/jenkins-library/pkg/abap/build" + +var ResponseCheckPV = `{ + "d": { + "Name": "/DRNMSPC/PRD01", + "Version": "0003", + "SpsLevel": "0002", + "PatchLevel": "0001", + "Vendor": "", + "VendorType": "" + } +}` + +var ResponseCheckCVs = `{ + "d": { + "Name": "/DRNMSPC/COMP01", + "Version": "0001", + "SpLevel": "0002", + "PatchLevel": "0003", + "Vendor": "", + "VendorType": "" + } +}` + +var emptyResultBody = `{ + "d": { + "results": [] + } +}` + +var testDataAakaasCVGetReleaseExisting = abapbuild.MockData{ + Method: `GET`, + Url: `/odata/aas_ocs_package/xSSDAxC_Component_Version?%24filter=Name+eq+%27DummyComp%27+and+TechSpLevel+eq+%270000%27+and+TechPatchLevel+eq+%270000%27+and+%28+DeliveryStatus+eq+%27R%27+or+DeliveryStatus+eq+%27C%27+or+DeliveryStatus+eq+%27T%27+or+DeliveryStatus+eq+%27P%27+%29&%24format=json&%24orderby=TechRelease+desc&%24select=Name%2CVersion%2CTechRelease%2CTechSpLevel%2CTechPatchLevel&%24top=1`, + Body: `{ + "d": { + "results": [ + { + "Name": "DummyComp", + "Version": "1.0.0", + "TechRelease": "1", + "TechSpLevel": "0000", + "TechPatchLevel": "0000" + } + ] + } + }`, + StatusCode: 200, +} + +var testDataAakaasCVGetReleaseNonExisting = abapbuild.MockData{ + Method: `GET`, + Url: `/odata/aas_ocs_package/xSSDAxC_Component_Version?%24filter=Name+eq+%27DummyComp%27+and+TechSpLevel+eq+%270000%27+and+TechPatchLevel+eq+%270000%27+and+%28+DeliveryStatus+eq+%27R%27+or+DeliveryStatus+eq+%27C%27+or+DeliveryStatus+eq+%27T%27+or+DeliveryStatus+eq+%27P%27+%29&%24format=json&%24orderby=TechRelease+desc&%24select=Name%2CVersion%2CTechRelease%2CTechSpLevel%2CTechPatchLevel&%24top=1`, + Body: emptyResultBody, + StatusCode: 200, +} + +var testDataAakaasCVGetSpLevelExisting = abapbuild.MockData{ + Method: `GET`, + Url: `/odata/aas_ocs_package/xSSDAxC_Component_Version?%24filter=Name+eq+%27DummyComp%27+and+TechRelease+eq+%271%27+and+TechPatchLevel+eq+%270000%27++and+%28+DeliveryStatus+eq+%27R%27+or+DeliveryStatus+eq+%27C%27+or+DeliveryStatus+eq+%27T%27+or+DeliveryStatus+eq+%27P%27+%29&%24format=json&%24orderby=TechSpLevel+desc&%24select=Name%2CVersion%2CTechRelease%2CTechSpLevel%2CTechPatchLevel&%24top=1`, + Body: `{ + "d": { + "results": [ + { + "Name": "DummyComp", + "Version": "1.7.0", + "TechRelease": "1", + "TechSpLevel": "0007", + "TechPatchLevel": "0000" + } + ] + } + }`, + StatusCode: 200, +} + +var testDataAakaasCVGetSpLevelNonExisting = abapbuild.MockData{ + Method: `GET`, + Url: `/odata/aas_ocs_package/xSSDAxC_Component_Version?%24filter=Name+eq+%27DummyComp%27+and+TechRelease+eq+%271%27+and+TechPatchLevel+eq+%270000%27++and+%28+DeliveryStatus+eq+%27R%27+or+DeliveryStatus+eq+%27C%27+or+DeliveryStatus+eq+%27T%27+or+DeliveryStatus+eq+%27P%27+%29&%24format=json&%24orderby=TechSpLevel+desc&%24select=Name%2CVersion%2CTechRelease%2CTechSpLevel%2CTechPatchLevel&%24top=1`, + Body: emptyResultBody, + StatusCode: 200, +} + +var testDataAakaasCVGetPatchLevelExisting = abapbuild.MockData{ + Method: `GET`, + Url: `/odata/aas_ocs_package/xSSDAxC_Component_Version?%24filter=Name+eq+%27DummyComp%27+and+TechRelease+eq+%271%27+and+TechSpLevel+eq+%270003%27+and+%28+DeliveryStatus+eq+%27R%27+or+DeliveryStatus+eq+%27C%27+or+DeliveryStatus+eq+%27T%27+or+DeliveryStatus+eq+%27P%27+%29&%24format=json&%24orderby=TechPatchLevel+desc&%24select=Name%2CVersion%2CTechRelease%2CTechSpLevel%2CTechPatchLevel&%24top=1`, + Body: `{ + "d": { + "results": [ + { + "Name": "DummyComp", + "Version": "1.3.46", + "TechRelease": "1", + "TechSpLevel": "0003", + "TechPatchLevel": "0046" + } + ] + } + }`, + StatusCode: 200, +} + +var testDataAakaasCVGetPatchLevelNonExisting = abapbuild.MockData{ + Method: `GET`, + Url: `/odata/aas_ocs_package/xSSDAxC_Component_Version?%24filter=Name+eq+%27DummyComp%27+and+TechRelease+eq+%271%27+and+TechSpLevel+eq+%270003%27+and+%28+DeliveryStatus+eq+%27R%27+or+DeliveryStatus+eq+%27C%27+or+DeliveryStatus+eq+%27T%27+or+DeliveryStatus+eq+%27P%27+%29&%24format=json&%24orderby=TechPatchLevel+desc&%24select=Name%2CVersion%2CTechRelease%2CTechSpLevel%2CTechPatchLevel&%24top=1`, + Body: emptyResultBody, + StatusCode: 200, +} + +var testDataAakaasPVGetReleaseExisting = abapbuild.MockData{ + Method: `GET`, + Url: `/odata/aas_ocs_package/xSSDAxC_Product_Version?%24filter=Name+eq+%27DummyProd%27+and+TechSpLevel+eq+%270000%27+and+TechPatchLevel+eq+%270000%27+and+%28+DeliveryStatus+eq+%27R%27+or+DeliveryStatus+eq+%27C%27+or+DeliveryStatus+eq+%27T%27+or+DeliveryStatus+eq+%27P%27+%29&%24format=json&%24orderby=TechRelease+desc&%24select=Name%2CVersion%2CTechRelease%2CTechSpLevel%2CTechPatchLevel&%24top=1`, + Body: `{ + "d": { + "results": [ + { + "Name": "DummyProd", + "Version": "1.0.0", + "TechRelease": "0001", + "TechSpLevel": "0000", + "TechPatchLevel": "0000" + } + ] + } + }`, + StatusCode: 200, +} + +var testDataAakaasPVGetReleaseNonExisting = abapbuild.MockData{ + Method: `GET`, + Url: `/odata/aas_ocs_package/xSSDAxC_Product_Version?%24filter=Name+eq+%27DummyProd%27+and+TechSpLevel+eq+%270000%27+and+TechPatchLevel+eq+%270000%27+and+%28+DeliveryStatus+eq+%27R%27+or+DeliveryStatus+eq+%27C%27+or+DeliveryStatus+eq+%27T%27+or+DeliveryStatus+eq+%27P%27+%29&%24format=json&%24orderby=TechRelease+desc&%24select=Name%2CVersion%2CTechRelease%2CTechSpLevel%2CTechPatchLevel&%24top=1`, + Body: emptyResultBody, + StatusCode: 200, +} diff --git a/pkg/abap/aakaas/versionables.go b/pkg/abap/aakaas/versionables.go new file mode 100644 index 000000000..67a781d13 --- /dev/null +++ b/pkg/abap/aakaas/versionables.go @@ -0,0 +1,177 @@ +package aakaas + +import ( + "encoding/json" + "fmt" + "net/url" + "strconv" + "strings" + + abapbuild "github.com/SAP/jenkins-library/pkg/abap/build" + "github.com/SAP/jenkins-library/pkg/log" + "github.com/pkg/errors" +) + +const wildCard string = "NEXT" + +type versionable struct { + Name string + Version string + TechRelease string + TechSpLevel string + TechPatchLevel string + + connector abapbuild.Connector + queryUrl string +} + +type versionables struct { + Wrapper struct { + Vs []versionable `json:"results"` + } `json:"d"` +} + +func (v *versionable) constructVersionable(name string, dottedVersionString string, connector abapbuild.Connector, queryURL string) error { + if name == "" { + return errors.New("No Component/Product Name provided") + } + subStrings := strings.Split(dottedVersionString, ".") + if len(subStrings) != 3 { + return errors.New("Provide a dotted-version-string with 2 '.' [Release.SP.Patch]") + } + v.Name = name + v.TechRelease = subStrings[0] + v.TechSpLevel = fmt.Sprintf("%04s", subStrings[1]) + v.TechPatchLevel = fmt.Sprintf("%04s", subStrings[2]) + v.connector = connector + v.queryUrl = queryURL + v.Version = dottedVersionString + return nil +} + +func (v *versionable) resolveNext() error { + + switch strings.Count(v.Version, wildCard) { + case 0: + return nil + case 1: + log.Entry().Info("Wildcard detected in dotted-version-string. Looking up highest existing package in AAKaaS...") + var err error + switch wildCard { + case v.TechRelease: + err = v.resolveRelease() + case v.TechSpLevel: + err = v.resolveSpLevel() + case v.TechPatchLevel: + err = v.resolvePatchLevel() + } + if err != nil { + return err + } + if v.Version, err = v.getDottedVersionString(); err != nil { + return err + } + default: + return errors.New("The dotted-version-string must contain only one wildcard " + wildCard) + } + + return nil +} + +func (v *versionable) resolveRelease() error { + //take only unrevertable status R/C for packages and T/P for TargetVectors + filter := "Name eq '" + v.Name + "' and TechSpLevel eq '0000' and TechPatchLevel eq '0000' and ( DeliveryStatus eq 'R' or DeliveryStatus eq 'C' or DeliveryStatus eq 'T' or DeliveryStatus eq 'P' )" + orderBy := "TechRelease desc" + + if queryResuult, err := v.queryVersion(filter, orderBy); err != nil { + return err + } else { + if newRelease, err := strconv.Atoi(queryResuult.TechRelease); err != nil { + return err + } else { + v.TechRelease = strconv.Itoa(newRelease + 1) + return nil + } + } +} + +func (v *versionable) resolveSpLevel() error { + filter := "Name eq '" + v.Name + "' and TechRelease eq '" + v.TechRelease + "' and TechPatchLevel eq '0000' and ( DeliveryStatus eq 'R' or DeliveryStatus eq 'C' or DeliveryStatus eq 'T' or DeliveryStatus eq 'P' )" + orderBy := "TechSpLevel desc" + + if queryResuult, err := v.queryVersion(filter, orderBy); err != nil { + return err + } else { + if newSpLevel, err := strconv.Atoi(queryResuult.TechSpLevel); err != nil { + return err + } else { + v.TechSpLevel = fmt.Sprintf("%04d", newSpLevel+1) + return nil + } + } +} + +func (v *versionable) resolvePatchLevel() error { + filter := "Name eq '" + v.Name + "' and TechRelease eq '" + v.TechRelease + "' and TechSpLevel eq '" + v.TechSpLevel + "' and ( DeliveryStatus eq 'R' or DeliveryStatus eq 'C' or DeliveryStatus eq 'T' or DeliveryStatus eq 'P' )" + orderBy := "TechPatchLevel desc" + + if queryResuult, err := v.queryVersion(filter, orderBy); err != nil { + return err + } else { + if newPatchLevel, err := strconv.Atoi(queryResuult.TechPatchLevel); err != nil { + return err + } else { + v.TechPatchLevel = fmt.Sprintf("%04d", newPatchLevel+1) + return nil + } + } +} + +func (v *versionable) queryVersion(filter string, orderBy string) (*versionable, error) { + result := versionable{} + + values := url.Values{} + values.Set("$filter", filter) + values.Set("$orderby", orderBy) + values.Set("$select", "Name,Version,TechRelease,TechSpLevel,TechPatchLevel") + values.Set("$format", "json") + values.Set("$top", "1") + + requestUrl := v.queryUrl + "?" + values.Encode() + + if body, err := v.connector.Get(requestUrl); err != nil { + return &result, err + } else { + Versions := versionables{} + if err := json.Unmarshal(body, &Versions); err != nil { + return &result, errors.Wrap(err, "Unexpected AAKaaS response for Component Version Query: "+string(body)) + } + switch len(Versions.Wrapper.Vs) { + case 0: + result = versionable{ + TechRelease: "0", + TechSpLevel: "0000", + TechPatchLevel: "0000", + } + case 1: + result = Versions.Wrapper.Vs[0] + default: + return &result, errors.New("Unexpected Number of CVs in result: " + fmt.Sprint(len(Versions.Wrapper.Vs))) + } + } + return &result, nil +} + +func (v *versionable) getDottedVersionString() (string, error) { + var spLevelAsnumber int + var patchLevelAsNumber int + var err error + if spLevelAsnumber, err = strconv.Atoi(v.TechSpLevel); err != nil { + return "", err + } + if patchLevelAsNumber, err = strconv.Atoi(v.TechPatchLevel); err != nil { + return "", err + } + dottedVersionString := strings.Join([]string{v.TechRelease, strconv.Itoa(spLevelAsnumber), strconv.Itoa(patchLevelAsNumber)}, ".") + return dottedVersionString, nil +} diff --git a/pkg/abap/aakaas/versionables_test.go b/pkg/abap/aakaas/versionables_test.go new file mode 100644 index 000000000..4b9023d30 --- /dev/null +++ b/pkg/abap/aakaas/versionables_test.go @@ -0,0 +1,131 @@ +package aakaas + +import ( + "testing" + + abapbuild "github.com/SAP/jenkins-library/pkg/abap/build" + "github.com/stretchr/testify/assert" +) + +func TestCvResolve(t *testing.T) { + //arrange + conn := new(abapbuild.Connector) + mc := abapbuild.NewMockClient() + conn.Client = &mc + vers := versionable{} + + t.Run("Factory Success", func(t *testing.T) { + //act + err := vers.constructVersionable("DummyComp", "1.2.3", *conn, "") + //assert + assert.NoError(t, err) + assert.Equal(t, "DummyComp", vers.Name) + assert.Equal(t, "1", vers.TechRelease) + assert.Equal(t, "0002", vers.TechSpLevel) + assert.Equal(t, "0003", vers.TechPatchLevel) + assert.Equal(t, "1.2.3", vers.Version) + }) + t.Run("Factory No Name", func(t *testing.T) { + err := vers.constructVersionable("", "1.2.3", *conn, "") + assert.Error(t, err) + }) + t.Run("Factory Version too long", func(t *testing.T) { + err := vers.constructVersionable("DummyComp", "1.0.0.0", *conn, "") + assert.Error(t, err) + }) + t.Run("Factory Version too short", func(t *testing.T) { + + err := vers.constructVersionable("DummyComp", "1.0", *conn, "") + assert.Error(t, err) + }) + t.Run("ComponentVersion NEXT Release Existing", func(t *testing.T) { + mc.AddData(testDataAakaasCVGetReleaseExisting) + err := vers.constructVersionable("DummyComp", wildCard+".0.0", *conn, cvQueryURL) + assert.NoError(t, err) + err = vers.resolveNext() + assert.NoError(t, err) + assert.Equal(t, "2", vers.TechRelease) + assert.Equal(t, "0000", vers.TechSpLevel) + assert.Equal(t, "0000", vers.TechPatchLevel) + assert.Equal(t, "2.0.0", vers.Version) + }) + t.Run("ComponentVersion NEXT Release Non Existing", func(t *testing.T) { + mc.AddData(testDataAakaasCVGetReleaseNonExisting) + err := vers.constructVersionable("DummyComp", wildCard+".0.0", *conn, cvQueryURL) + assert.NoError(t, err) + err = vers.resolveNext() + assert.NoError(t, err) + assert.Equal(t, "1", vers.TechRelease) + assert.Equal(t, "0000", vers.TechSpLevel) + assert.Equal(t, "0000", vers.TechPatchLevel) + assert.Equal(t, "1.0.0", vers.Version) + }) + t.Run("ComponentVersion NEXT SP Level Existing", func(t *testing.T) { + mc.AddData(testDataAakaasCVGetSpLevelExisting) + err := vers.constructVersionable("DummyComp", "1."+wildCard+".0", *conn, cvQueryURL) + assert.NoError(t, err) + err = vers.resolveNext() + assert.NoError(t, err) + assert.Equal(t, "1", vers.TechRelease) + assert.Equal(t, "0008", vers.TechSpLevel) + assert.Equal(t, "0000", vers.TechPatchLevel) + assert.Equal(t, "1.8.0", vers.Version) + }) + t.Run("ComponentVersion NEXT SP Level Non Existing", func(t *testing.T) { + //This one should lead to an error later on as AOI is needed - anyway we can't just produce a differen package then customized... + mc.AddData(testDataAakaasCVGetSpLevelNonExisting) + err := vers.constructVersionable("DummyComp", "1."+wildCard+".0", *conn, cvQueryURL) + assert.NoError(t, err) + err = vers.resolveNext() + assert.NoError(t, err) + assert.Equal(t, "1", vers.TechRelease) + assert.Equal(t, "0001", vers.TechSpLevel) + assert.Equal(t, "0000", vers.TechPatchLevel) + assert.Equal(t, "1.1.0", vers.Version) + }) + t.Run("ComponentVersion NEXT Patch Level Existing", func(t *testing.T) { + mc.AddData(testDataAakaasCVGetPatchLevelExisting) + err := vers.constructVersionable("DummyComp", "1.3."+wildCard, *conn, cvQueryURL) + assert.NoError(t, err) + err = vers.resolveNext() + assert.NoError(t, err) + assert.Equal(t, "1", vers.TechRelease) + assert.Equal(t, "0003", vers.TechSpLevel) + assert.Equal(t, "0047", vers.TechPatchLevel) + assert.Equal(t, "1.3.47", vers.Version) + }) + t.Run("ComponentVersion NEXT Patch Level Non Existing", func(t *testing.T) { + //This one should lead to an error later on as AOI is needed - anyway we can't just produce a differen package then customized... + mc.AddData(testDataAakaasCVGetPatchLevelNonExisting) + err := vers.constructVersionable("DummyComp", "1.3."+wildCard, *conn, cvQueryURL) + assert.NoError(t, err) + err = vers.resolveNext() + assert.NoError(t, err) + assert.Equal(t, "1", vers.TechRelease) + assert.Equal(t, "0003", vers.TechSpLevel) + assert.Equal(t, "0001", vers.TechPatchLevel) + assert.Equal(t, "1.3.1", vers.Version) + }) + t.Run("Product Version NEXT Release Existing", func(t *testing.T) { + mc.AddData(testDataAakaasPVGetReleaseExisting) + err := vers.constructVersionable("DummyProd", wildCard+".0.0", *conn, pvQueryURL) + assert.NoError(t, err) + err = vers.resolveNext() + assert.NoError(t, err) + assert.Equal(t, "2", vers.TechRelease) + assert.Equal(t, "0000", vers.TechSpLevel) + assert.Equal(t, "0000", vers.TechPatchLevel) + assert.Equal(t, "2.0.0", vers.Version) + }) + t.Run("Product Version NEXT Release Non Existing", func(t *testing.T) { + mc.AddData(testDataAakaasPVGetReleaseNonExisting) + err := vers.constructVersionable("DummyProd", wildCard+".0.0", *conn, pvQueryURL) + assert.NoError(t, err) + err = vers.resolveNext() + assert.NoError(t, err) + assert.Equal(t, "1", vers.TechRelease) + assert.Equal(t, "0000", vers.TechSpLevel) + assert.Equal(t, "0000", vers.TechPatchLevel) + assert.Equal(t, "1.0.0", vers.Version) + }) +}