diff --git a/cmd/server/internal/handler.go b/cmd/server/internal/handler.go index 33acc72e..8b05030c 100644 --- a/cmd/server/internal/handler.go +++ b/cmd/server/internal/handler.go @@ -42,6 +42,7 @@ const ( const ( LabelBTPApplicationIdentifierHash = "sme.sap.com/btp-app-identifier-hash" + LabelAppIdHash = "sme.sap.com/app-identifier-hash" LabelTenantId = "sme.sap.com/btp-tenant-id" LabelTenantType = "sme.sap.com/tenant-type" LabelSubscriptionGUID = "sme.sap.com/subscription-guid" @@ -94,12 +95,13 @@ const ( ) type payloadDetails struct { - subscriptionGUID string - tenantId string - subdomain string - globalAccountId string - appName string - raw *map[string]any + subscriptionGUID string + tenantId string + subdomain string + globalAccountId string + providerSubaccountId string + appName string + raw *map[string]any } type requestHeaderDetails struct { @@ -159,7 +161,7 @@ func (s *SubscriptionHandler) CreateTenant(reqInfo *RequestInfo) *Result { var smsData *util.SmsCredentials // Check if CAPApplication instance for the given btpApp exists - ca, err := s.checkCAPApp(reqInfo.payload.globalAccountId, reqInfo.payload.appName) + ca, err := s.checkCAPApp(reqInfo.payload.globalAccountId, reqInfo.payload.providerSubaccountId, reqInfo.payload.appName) if err != nil { util.LogError(err, ErrorOccurred, TenantProvisioning, ca, nil) return &Result{Tenant: nil, Message: err.Error()} @@ -172,7 +174,7 @@ func (s *SubscriptionHandler) CreateTenant(reqInfo *RequestInfo) *Result { } // Check if A CRO for CAPTenant already exists - tenant := s.getTenantByBtpAppIdentifier(reqInfo.payload.globalAccountId, reqInfo.payload.appName, reqInfo.payload.tenantId, ca.Namespace, TenantProvisioning).Tenant + tenant := s.getTenantByBtpAppIdentifier(ca.Spec.GlobalAccountId, reqInfo.payload.appName, reqInfo.payload.tenantId, ca.Namespace, TenantProvisioning).Tenant // If the resource doesn't exist, we'll create it if tenant == nil { @@ -224,7 +226,7 @@ func (s *SubscriptionHandler) createTenant(reqInfo *RequestInfo, ca *v1alpha1.CA GenerateName: ca.Name + "-consumer-", Namespace: ca.Namespace, Labels: map[string]string{ - LabelBTPApplicationIdentifierHash: sha1Sum(reqInfo.payload.globalAccountId, reqInfo.payload.appName), + LabelBTPApplicationIdentifierHash: sha1Sum(ca.Spec.GlobalAccountId, reqInfo.payload.appName), LabelTenantId: reqInfo.payload.tenantId, LabelSubscriptionGUID: subscriptionGUID, }, @@ -248,7 +250,7 @@ func (s *SubscriptionHandler) createTenant(reqInfo *RequestInfo, ca *v1alpha1.CA AnnotationSubscriptionContextSecret: secret.Name, // Store the secret name in the tenant annotation }, Labels: map[string]string{ - LabelBTPApplicationIdentifierHash: sha1Sum(reqInfo.payload.globalAccountId, reqInfo.payload.appName), + LabelBTPApplicationIdentifierHash: sha1Sum(ca.Spec.GlobalAccountId, reqInfo.payload.appName), LabelTenantId: reqInfo.payload.tenantId, LabelSubscriptionGUID: subscriptionGUID, LabelTenantType: "consumer", // Default tenant type for consumer tenants @@ -456,22 +458,28 @@ func (s *SubscriptionHandler) DeleteTenant(reqInfo *RequestInfo) *Result { // Check if tenant exists by subscriptionGUID and tenantId tenant = s.getTenantBySubscriptionGUID(reqInfo.payload.subscriptionGUID, reqInfo.payload.tenantId, TenantDeprovisioning).Tenant - if tenant == nil && reqInfo.subscriptionType == SaaS { + if tenant != nil { + ca, err = s.Clientset.SmeV1alpha1().CAPApplications(tenant.Namespace).Get(context.TODO(), tenant.Spec.CAPApplicationInstance, metav1.GetOptions{}) + if err != nil { + util.LogError(err, "CAPApplication not found", TenantDeprovisioning, tenant, nil) + return &Result{Tenant: nil, Message: err.Error()} + } + } else if reqInfo.subscriptionType == SaaS { + ca, err = s.checkCAPApp(reqInfo.payload.globalAccountId, reqInfo.payload.providerSubaccountId, reqInfo.payload.appName) + if err != nil { + util.LogError(err, "CAPApplication not found", TenantDeprovisioning, tenant, nil) + return &Result{Tenant: nil, Message: TenantNotFound} + } // if tenant is not found in SaaS subscription scenario, check if it exists by btpApp identifier to handle cases where tenant was created without subscriptionGUID util.LogInfo("Tenant not found by subscriptionGUID, checking by BTP app identifier", TenantDeprovisioning, "DeleteTenant", nil, "subscriptionGUID", reqInfo.payload.subscriptionGUID) - tenant = s.getTenantByBtpAppIdentifier(reqInfo.payload.globalAccountId, reqInfo.payload.appName, reqInfo.payload.tenantId, metav1.NamespaceAll, TenantDeprovisioning).Tenant + tenant = s.getTenantByBtpAppIdentifier(ca.Spec.GlobalAccountId, reqInfo.payload.appName, reqInfo.payload.tenantId, metav1.NamespaceAll, TenantDeprovisioning).Tenant } + if tenant == nil { util.LogWarning("CAPTenant not found", TenantDeprovisioning) return &Result{Tenant: nil, Message: TenantNotFound} } - ca, err = s.Clientset.SmeV1alpha1().CAPApplications(tenant.Namespace).Get(context.TODO(), tenant.Spec.CAPApplicationInstance, metav1.GetOptions{}) - if err != nil { - util.LogError(err, "CAPApplication not found", TenantDeprovisioning, tenant, nil) - return &Result{Tenant: nil, Message: err.Error()} - } - saasData, smsData, err = s.authorizationCheck(reqInfo.headerDetails, ca, reqInfo.subscriptionType, TenantDeprovisioning) if err != nil { util.LogError(err, AuthorizationCheckFailed, TenantDeprovisioning, ca, nil) @@ -518,14 +526,30 @@ func (s *SubscriptionHandler) authorizationCheck(headerDetails *requestHeaderDet return } -func (s *SubscriptionHandler) checkCAPApp(globalAccountId, btpAppName string) (*v1alpha1.CAPApplication, error) { - labelSelector, err := labels.ValidatedSelectorFromSet(map[string]string{ - LabelBTPApplicationIdentifierHash: sha1Sum(globalAccountId, btpAppName), +func (s *SubscriptionHandler) checkCAPApp(globalAccountId, providerSubaccountId, btpAppName string) (*v1alpha1.CAPApplication, error) { + // First try to find CAPApplication by providerSubaccountId (appIdHash) + labelSelector, _ := labels.ValidatedSelectorFromSet(map[string]string{ + LabelAppIdHash: sha1Sum(providerSubaccountId, btpAppName), }) - if err != nil { + + ca, err := s.getAppByLabelSelector(labelSelector) + if err != nil && err.Error() != ResourceNotFound { return nil, err } + if ca != nil { + return ca, nil + } + + // If no CAPApplication is found by providerSubaccountId, try to find by globalAccountId (btpAppIdentifierHash) to cover previous cases where appIdHash label might not be present + labelSelector, _ = labels.ValidatedSelectorFromSet(map[string]string{ + LabelBTPApplicationIdentifierHash: sha1Sum(globalAccountId, btpAppName), + }) + + return s.getAppByLabelSelector(labelSelector) +} + +func (s *SubscriptionHandler) getAppByLabelSelector(labelSelector labels.Selector) (*v1alpha1.CAPApplication, error) { capAppsList, err := s.Clientset.SmeV1alpha1().CAPApplications(metav1.NamespaceAll).List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector.String()}) if err != nil { return nil, err @@ -959,7 +983,7 @@ func (s *SubscriptionHandler) HandleSMSRequest(w http.ResponseWriter, req *http. } func ProcessRequest(req *http.Request, subscriptionType subscriptionType) (*RequestInfo, error) { - var subscriptionGUID, tenantId, subdomain, globalAccountId, appName string + var subscriptionGUID, tenantId, subdomain, globalAccountId, providerSubaccountId, appName string var jsonPayload map[string]any if !(req.Method == http.MethodDelete && subscriptionType == SMS) { @@ -984,6 +1008,7 @@ func ProcessRequest(req *http.Request, subscriptionType subscriptionType) (*Requ subdomain = subscriber["subaccountSubdomain"].(string) globalAccountId = subscriber["globalAccountId"].(string) rootApp := jsonPayload["rootApplication"].(map[string]any) + providerSubaccountId = rootApp["providerSubaccountId"].(string) appName = rootApp["appName"].(string) case http.MethodDelete: // get paramater from URL @@ -1005,17 +1030,19 @@ func ProcessRequest(req *http.Request, subscriptionType subscriptionType) (*Requ tenantId = jsonPayload["subscribedTenantId"].(string) subdomain = jsonPayload["subscribedSubdomain"].(string) globalAccountId = jsonPayload["globalAccountGUID"].(string) + providerSubaccountId = jsonPayload["providerSubaccountId"].(string) appName = jsonPayload["subscriptionAppName"].(string) } payload := &payloadDetails{ // GTID - subscriptionGUID: subscriptionGUID, - tenantId: tenantId, - subdomain: subdomain, - globalAccountId: globalAccountId, - appName: appName, - raw: &jsonPayload, + subscriptionGUID: subscriptionGUID, + tenantId: tenantId, + subdomain: subdomain, + globalAccountId: globalAccountId, + providerSubaccountId: providerSubaccountId, + appName: appName, + raw: &jsonPayload, } return &RequestInfo{ subscriptionType: subscriptionType, diff --git a/cmd/server/internal/handler_test.go b/cmd/server/internal/handler_test.go index fd636335..51a364be 100644 --- a/cmd/server/internal/handler_test.go +++ b/cmd/server/internal/handler_test.go @@ -44,6 +44,7 @@ const ( catName = caName + "-provider" appName = "some-app-name" globalAccountId = "cap-app-global" + providerSubaccountId = "012012012-1234-1234-123456012345" subDomain = "foo" tenantId = "012012012-1234-1234-123456" subscriptionGUID = "012301234-2345-6789-ABCDEF" @@ -358,7 +359,7 @@ func Test_provisioning(t *testing.T) { { name: "Provisioning Request without CROs", method: http.MethodPut, - body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, + body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","providerSubaccountId":"` + providerSubaccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, expectedStatusCode: http.StatusNotAcceptable, expectedResponse: Result{ Message: "the server could not find the requested resource (get capapplications.sme.sap.com)", //TODO @@ -367,7 +368,7 @@ func Test_provisioning(t *testing.T) { { name: "Provisioning Request with CROs with invalid app name", method: http.MethodPut, - body: `{"subscriptionAppName":"test-app","globalAccountGUID":"` + globalAccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, + body: `{"subscriptionAppName":"test-app","globalAccountGUID":"` + globalAccountId + `","providerSubaccountId":"` + providerSubaccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, createCROs: true, expectedStatusCode: http.StatusNotAcceptable, expectedResponse: Result{ @@ -377,7 +378,7 @@ func Test_provisioning(t *testing.T) { { name: "Provisioning Request valid (without domains)", method: http.MethodPut, - body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, + body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","providerSubaccountId":"` + providerSubaccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, createCROs: true, expectedStatusCode: http.StatusAccepted, expectedResponse: Result{ @@ -387,7 +388,7 @@ func Test_provisioning(t *testing.T) { { name: "Provisioning Request valid (invalid domain)", method: http.MethodPut, - body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, + body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","providerSubaccountId":"` + providerSubaccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, createCROs: true, invalidDomain: true, expectedStatusCode: http.StatusAccepted, @@ -398,7 +399,7 @@ func Test_provisioning(t *testing.T) { { name: "Provisioning Request valid (invalid clusterdomains)", method: http.MethodPut, - body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, + body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","providerSubaccountId":"` + providerSubaccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, createCROs: true, invalidClusterDomain: true, expectedStatusCode: http.StatusAccepted, @@ -409,7 +410,7 @@ func Test_provisioning(t *testing.T) { { name: "Provisioning Request valid (with domain)", method: http.MethodPut, - body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, + body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","providerSubaccountId":"` + providerSubaccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, createCROs: true, existingDomain: true, expectedStatusCode: http.StatusAccepted, @@ -420,7 +421,7 @@ func Test_provisioning(t *testing.T) { { name: "Provisioning Request valid (with Cluster domain)", method: http.MethodPut, - body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, + body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","providerSubaccountId":"` + providerSubaccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, createCROs: true, existingClusterDomain: true, expectedStatusCode: http.StatusAccepted, @@ -431,7 +432,7 @@ func Test_provisioning(t *testing.T) { { name: "Provisioning Request valid with additional data and existing tenant", method: http.MethodPut, - body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, + body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","providerSubaccountId":"` + providerSubaccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, createCROs: true, withAdditionalData: true, existingTenant: true, @@ -443,7 +444,7 @@ func Test_provisioning(t *testing.T) { { name: "Provisioning Request valid with additional data and existing tenant and existing tenant output", method: http.MethodPut, - body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, + body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","providerSubaccountId":"` + providerSubaccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, createCROs: true, withAdditionalData: true, existingTenant: true, @@ -456,7 +457,7 @@ func Test_provisioning(t *testing.T) { { name: "Provisioning Request valid with invalid additional data and existing tenant", method: http.MethodPut, - body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, + body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","providerSubaccountId":"` + providerSubaccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, createCROs: true, withAdditionalData: true, invalidAdditionalData: true, @@ -469,7 +470,7 @@ func Test_provisioning(t *testing.T) { { name: "Provisioning Request with existing tenant", method: http.MethodPut, - body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, + body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","providerSubaccountId":"` + providerSubaccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, createCROs: true, existingTenant: true, expectedStatusCode: http.StatusAccepted, @@ -584,7 +585,7 @@ func Test_sms_provisioning(t *testing.T) { { name: "Provisioning Request without CROs", method: http.MethodPut, - body: `{"rootApplication":{"appName":"` + appName + `","commercialAppName":"` + appName + `"},"subscriber":{"subscriptionGUID":"` + subscriptionGUID + `","app_tid":"` + tenantId + `","globalAccountId":"` + globalAccountId + `","subaccountSubdomain":"` + subDomain + `"}}`, + body: `{"rootApplication":{"appName":"` + appName + `","providerSubaccountId":"` + providerSubaccountId + `","commercialAppName":"` + appName + `"},"subscriber":{"subscriptionGUID":"` + subscriptionGUID + `","app_tid":"` + tenantId + `","globalAccountId":"` + globalAccountId + `","subaccountSubdomain":"` + subDomain + `"}}`, expectedStatusCode: http.StatusNotAcceptable, expectedResponse: Result{ Message: "the server could not find the requested resource (get capapplications.sme.sap.com)", //TODO @@ -593,7 +594,7 @@ func Test_sms_provisioning(t *testing.T) { { name: "Provisioning Request with CROs with invalid app name", method: http.MethodPut, - body: `{"rootApplication":{"appName":"test-app","commercialAppName":"test-app"},"subscriber":{"subscriptionGUID":"` + subscriptionGUID + `","app_tid":"` + tenantId + `","globalAccountId":"` + globalAccountId + `","subaccountSubdomain":"` + subDomain + `"}}`, + body: `{"rootApplication":{"appName":"test-app","commercialAppName":"test-app","providerSubaccountId":"` + providerSubaccountId + `"},"subscriber":{"subscriptionGUID":"` + subscriptionGUID + `","app_tid":"` + tenantId + `","globalAccountId":"` + globalAccountId + `","subaccountSubdomain":"` + subDomain + `"}}`, createCROs: true, expectedStatusCode: http.StatusNotAcceptable, expectedResponse: Result{ @@ -603,7 +604,7 @@ func Test_sms_provisioning(t *testing.T) { { name: "Provisioning Request valid (without domains)", method: http.MethodPut, - body: `{"rootApplication":{"appName":"` + appName + `","commercialAppName":"` + appName + `"},"subscriber":{"subscriptionGUID":"` + subscriptionGUID + `","app_tid":"` + tenantId + `","globalAccountId":"` + globalAccountId + `","subaccountSubdomain":"` + subDomain + `"}}`, + body: `{"rootApplication":{"appName":"` + appName + `","providerSubaccountId":"` + providerSubaccountId + `","commercialAppName":"` + appName + `"},"subscriber":{"subscriptionGUID":"` + subscriptionGUID + `","app_tid":"` + tenantId + `","globalAccountId":"` + globalAccountId + `","subaccountSubdomain":"` + subDomain + `"}}`, createCROs: true, expectedStatusCode: http.StatusAccepted, expectedResponse: Result{ @@ -613,7 +614,7 @@ func Test_sms_provisioning(t *testing.T) { { name: "Provisioning Request valid (invalid domain)", method: http.MethodPut, - body: `{"rootApplication":{"appName":"` + appName + `","commercialAppName":"` + appName + `"},"subscriber":{"subscriptionGUID":"` + subscriptionGUID + `","app_tid":"` + tenantId + `","globalAccountId":"` + globalAccountId + `","subaccountSubdomain":"` + subDomain + `"}}`, + body: `{"rootApplication":{"appName":"` + appName + `","providerSubaccountId":"` + providerSubaccountId + `","commercialAppName":"` + appName + `"},"subscriber":{"subscriptionGUID":"` + subscriptionGUID + `","app_tid":"` + tenantId + `","globalAccountId":"` + globalAccountId + `","subaccountSubdomain":"` + subDomain + `"}}`, createCROs: true, invalidDomain: true, expectedStatusCode: http.StatusAccepted, @@ -624,7 +625,7 @@ func Test_sms_provisioning(t *testing.T) { { name: "Provisioning Request valid (invalid clusterdomains)", method: http.MethodPut, - body: `{"rootApplication":{"appName":"` + appName + `","commercialAppName":"` + appName + `"},"subscriber":{"subscriptionGUID":"` + subscriptionGUID + `","app_tid":"` + tenantId + `","globalAccountId":"` + globalAccountId + `","subaccountSubdomain":"` + subDomain + `"}}`, + body: `{"rootApplication":{"appName":"` + appName + `","providerSubaccountId":"` + providerSubaccountId + `","commercialAppName":"` + appName + `"},"subscriber":{"subscriptionGUID":"` + subscriptionGUID + `","app_tid":"` + tenantId + `","globalAccountId":"` + globalAccountId + `","subaccountSubdomain":"` + subDomain + `"}}`, createCROs: true, invalidClusterDomain: true, expectedStatusCode: http.StatusAccepted, @@ -635,7 +636,7 @@ func Test_sms_provisioning(t *testing.T) { { name: "Provisioning Request valid (with domain)", method: http.MethodPut, - body: `{"rootApplication":{"appName":"` + appName + `","commercialAppName":"` + appName + `"},"subscriber":{"subscriptionGUID":"` + subscriptionGUID + `","app_tid":"` + tenantId + `","globalAccountId":"` + globalAccountId + `","subaccountSubdomain":"` + subDomain + `"}}`, + body: `{"rootApplication":{"appName":"` + appName + `","providerSubaccountId":"` + providerSubaccountId + `","commercialAppName":"` + appName + `"},"subscriber":{"subscriptionGUID":"` + subscriptionGUID + `","app_tid":"` + tenantId + `","globalAccountId":"` + globalAccountId + `","subaccountSubdomain":"` + subDomain + `"}}`, createCROs: true, existingDomain: true, expectedStatusCode: http.StatusAccepted, @@ -646,7 +647,7 @@ func Test_sms_provisioning(t *testing.T) { { name: "Provisioning Request valid (with Cluster domain)", method: http.MethodPut, - body: `{"rootApplication":{"appName":"` + appName + `","commercialAppName":"` + appName + `"},"subscriber":{"subscriptionGUID":"` + subscriptionGUID + `","app_tid":"` + tenantId + `","globalAccountId":"` + globalAccountId + `","subaccountSubdomain":"` + subDomain + `"}}`, + body: `{"rootApplication":{"appName":"` + appName + `","providerSubaccountId":"` + providerSubaccountId + `","commercialAppName":"` + appName + `"},"subscriber":{"subscriptionGUID":"` + subscriptionGUID + `","app_tid":"` + tenantId + `","globalAccountId":"` + globalAccountId + `","subaccountSubdomain":"` + subDomain + `"}}`, createCROs: true, existingClusterDomain: true, expectedStatusCode: http.StatusAccepted, @@ -657,7 +658,7 @@ func Test_sms_provisioning(t *testing.T) { { name: "Provisioning Request valid with additional data and existing tenant", method: http.MethodPut, - body: `{"rootApplication":{"appName":"` + appName + `","commercialAppName":"` + appName + `"},"subscriber":{"subscriptionGUID":"` + subscriptionGUID + `","app_tid":"` + tenantId + `","globalAccountId":"` + globalAccountId + `","subaccountSubdomain":"` + subDomain + `"}}`, + body: `{"rootApplication":{"appName":"` + appName + `","providerSubaccountId":"` + providerSubaccountId + `","commercialAppName":"` + appName + `"},"subscriber":{"subscriptionGUID":"` + subscriptionGUID + `","app_tid":"` + tenantId + `","globalAccountId":"` + globalAccountId + `","subaccountSubdomain":"` + subDomain + `"}}`, createCROs: true, withAdditionalData: true, existingTenant: true, @@ -669,7 +670,7 @@ func Test_sms_provisioning(t *testing.T) { { name: "Provisioning Request valid with additional data and existing tenant and existing tenant output", method: http.MethodPut, - body: `{"rootApplication":{"appName":"` + appName + `","commercialAppName":"` + appName + `"},"subscriber":{"subscriptionGUID":"` + subscriptionGUID + `","app_tid":"` + tenantId + `","globalAccountId":"` + globalAccountId + `","subaccountSubdomain":"` + subDomain + `"}}`, + body: `{"rootApplication":{"appName":"` + appName + `","providerSubaccountId":"` + providerSubaccountId + `","commercialAppName":"` + appName + `"},"subscriber":{"subscriptionGUID":"` + subscriptionGUID + `","app_tid":"` + tenantId + `","globalAccountId":"` + globalAccountId + `","subaccountSubdomain":"` + subDomain + `"}}`, createCROs: true, withAdditionalData: true, existingTenant: true, @@ -682,7 +683,7 @@ func Test_sms_provisioning(t *testing.T) { { name: "Provisioning Request valid with invalid additional data and existing tenant", method: http.MethodPut, - body: `{"rootApplication":{"appName":"` + appName + `","commercialAppName":"` + appName + `"},"subscriber":{"subscriptionGUID":"` + subscriptionGUID + `","app_tid":"` + tenantId + `","globalAccountId":"` + globalAccountId + `","subaccountSubdomain":"` + subDomain + `"}}`, + body: `{"rootApplication":{"appName":"` + appName + `","providerSubaccountId":"` + providerSubaccountId + `","commercialAppName":"` + appName + `"},"subscriber":{"subscriptionGUID":"` + subscriptionGUID + `","app_tid":"` + tenantId + `","globalAccountId":"` + globalAccountId + `","subaccountSubdomain":"` + subDomain + `"}}`, createCROs: true, withAdditionalData: true, invalidAdditionalData: true, @@ -695,7 +696,7 @@ func Test_sms_provisioning(t *testing.T) { { name: "Provisioning Request with existing tenant", method: http.MethodPut, - body: `{"rootApplication":{"appName":"` + appName + `","commercialAppName":"` + appName + `"},"subscriber":{"subscriptionGUID":"` + subscriptionGUID + `","app_tid":"` + tenantId + `","globalAccountId":"` + globalAccountId + `","subaccountSubdomain":"` + subDomain + `"}}`, + body: `{"rootApplication":{"appName":"` + appName + `","providerSubaccountId":"` + providerSubaccountId + `","commercialAppName":"` + appName + `"},"subscriber":{"subscriptionGUID":"` + subscriptionGUID + `","app_tid":"` + tenantId + `","globalAccountId":"` + globalAccountId + `","subaccountSubdomain":"` + subDomain + `"}}`, createCROs: true, existingTenant: true, expectedStatusCode: http.StatusAccepted, @@ -706,7 +707,7 @@ func Test_sms_provisioning(t *testing.T) { { name: "Provisioning Request with existing tenant but different subscriptionGUID (If provisioning fails due to callback issue, the tenant exists and in BTP provisioned failed; retriggering sends a new subscriptionGUID in the payload)", method: http.MethodPut, - body: `{"rootApplication":{"appName":"` + appName + `","commercialAppName":"` + appName + `"},"subscriber":{"subscriptionGUID":"` + subscriptionGUID + "update" + `","app_tid":"` + tenantId + `","globalAccountId":"` + globalAccountId + `","subaccountSubdomain":"` + subDomain + `"}}`, + body: `{"rootApplication":{"appName":"` + appName + `","providerSubaccountId":"` + providerSubaccountId + `","commercialAppName":"` + appName + `"},"subscriber":{"subscriptionGUID":"` + subscriptionGUID + "update" + `","app_tid":"` + tenantId + `","globalAccountId":"` + globalAccountId + `","subaccountSubdomain":"` + subDomain + `"}}`, createCROs: true, existingTenant: true, expectedStatusCode: http.StatusAccepted, @@ -822,7 +823,7 @@ func Test_deprovisioning(t *testing.T) { { name: "Deprovisioning Request without CAPApplication and CAPTenant", method: http.MethodDelete, - body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, + body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","providerSubaccountId":"` + providerSubaccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, expectedStatusCode: http.StatusNotFound, expectedResponse: Result{ Message: "the server could not find the requested resource (get capapplications.sme.sap.com)", //TODO @@ -832,7 +833,7 @@ func Test_deprovisioning(t *testing.T) { name: "Deprovisioning Request valid without existing tenant)", method: http.MethodDelete, createCROs: true, - body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, + body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","providerSubaccountId":"` + providerSubaccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, expectedStatusCode: http.StatusNotFound, expectedResponse: Result{ Message: ResourceDeleted, @@ -843,7 +844,7 @@ func Test_deprovisioning(t *testing.T) { method: http.MethodDelete, createCROs: true, existingTenant: true, - body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, + body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","providerSubaccountId":"` + providerSubaccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, expectedStatusCode: http.StatusAccepted, expectedResponse: Result{ Message: ResourceDeleted, @@ -855,7 +856,7 @@ func Test_deprovisioning(t *testing.T) { createCROs: true, existingTenant: true, withGlobalTenantId: true, - body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, + body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","providerSubaccountId":"` + providerSubaccountId + `","subscriptionGUID":"` + subscriptionGUID + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`, expectedStatusCode: http.StatusAccepted, expectedResponse: Result{ Message: ResourceDeleted, diff --git a/crds/sme.sap.com_capapplications.yaml b/crds/sme.sap.com_capapplications.yaml index b7bc4418..041c2b16 100644 --- a/crds/sme.sap.com_capapplications.yaml +++ b/crds/sme.sap.com_capapplications.yaml @@ -112,6 +112,8 @@ spec: - subDomain - tenantId type: object + providerSubaccountId: + type: string required: - btp - btpAppName diff --git a/internal/controller/reconcile-capapplication.go b/internal/controller/reconcile-capapplication.go index 1779abe8..b1f20365 100644 --- a/internal/controller/reconcile-capapplication.go +++ b/internal/controller/reconcile-capapplication.go @@ -579,8 +579,9 @@ func (c *Controller) prepareCAPApplication(ca *v1alpha1.CAPApplication) (update // add Label/Annotation for BTP App appMetadata := appMetadataIdentifiers{ - globalAccountId: ca.Spec.GlobalAccountId, - appName: ca.Spec.BTPAppName, + globalAccountId: ca.Spec.GlobalAccountId, + appName: ca.Spec.BTPAppName, + providerSubaccountId: ca.Spec.ProviderSubaccountId, } if updateLabelAnnotationMetadata(&ca.ObjectMeta, &appMetadata) { update = true diff --git a/internal/controller/reconcile.go b/internal/controller/reconcile.go index b1bc90b1..ef764f14 100644 --- a/internal/controller/reconcile.go +++ b/internal/controller/reconcile.go @@ -33,6 +33,7 @@ const ( LabelWorkloadType = "sme.sap.com/workload-type" LabelResourceCategory = "sme.sap.com/category" LabelBTPApplicationIdentifierHash = "sme.sap.com/btp-app-identifier-hash" + LabelAppIdHash = "sme.sap.com/app-identifier-hash" LabelTenantType = "sme.sap.com/tenant-type" LabelTenantId = "sme.sap.com/btp-tenant-id" LabelTenantOperationType = "sme.sap.com/tenant-operation-type" @@ -45,6 +46,7 @@ const ( LabelSubscriptionGUID = "sme.sap.com/subscription-guid" AnnotationOwnerIdentifier = "sme.sap.com/owner-identifier" AnnotationBTPApplicationIdentifier = "sme.sap.com/btp-app-identifier" + AnnotationAppId = "sme.sap.com/app-identifier" AnnotationResourceHash = "sme.sap.com/resource-hash" AnnotationControllerClass = "sme.sap.com/controller-class" AnnotationIstioSidecarInject = "sidecar.istio.io/inject" diff --git a/internal/controller/utils.go b/internal/controller/utils.go index 28288f23..163aa761 100644 --- a/internal/controller/utils.go +++ b/internal/controller/utils.go @@ -44,9 +44,10 @@ type ownerInfo struct { ownerGeneration int64 } type appMetadataIdentifiers struct { - globalAccountId string - appName string - ownerInfo *ownerInfo + globalAccountId string + appName string + providerSubaccountId string + ownerInfo *ownerInfo } func getOwnerByKind(owners []metav1.OwnerReference, kind string) (*metav1.OwnerReference, bool) { @@ -227,6 +228,11 @@ func updateLabelAnnotationMetadata(object *metav1.ObjectMeta, appMetadata *appMe updated = true } + // Update App Identifier + if appMetadata.providerSubaccountId != "" && amendObjectMetadata(object, AnnotationAppId, LabelAppIdHash, strings.Join([]string{appMetadata.providerSubaccountId, appMetadata.appName}, "."), sha1Sum(appMetadata.providerSubaccountId, appMetadata.appName)) { + updated = true + } + // Update OwnerInfo if owner details exists if appMetadata.ownerInfo != nil { if amendObjectMetadata(object, AnnotationOwnerIdentifier, LabelOwnerIdentifierHash, strings.Join([]string{appMetadata.ownerInfo.ownerNamespace, appMetadata.ownerInfo.ownerName}, "."), sha1Sum(appMetadata.ownerInfo.ownerNamespace, appMetadata.ownerInfo.ownerName)) { diff --git a/pkg/apis/sme.sap.com/v1alpha1/types.go b/pkg/apis/sme.sap.com/v1alpha1/types.go index 2402073a..22127bed 100644 --- a/pkg/apis/sme.sap.com/v1alpha1/types.go +++ b/pkg/apis/sme.sap.com/v1alpha1/types.go @@ -92,7 +92,10 @@ type CAPApplicationSpec struct { // [DEPRECATED] Domains used by the application // Will be removed in future versions Domains ApplicationDomains `json:"domains,omitempty"` // SAP BTP Global Account Identifier where services are entitles for the current application + // Will soon be deprecated, use ProviderSubaccountId instead GlobalAccountId string `json:"globalAccountId"` + // The subaccount ID in which the application is provided (will soon replace GlobalAccountId) + ProviderSubaccountId string `json:"providerSubaccountId,omitempty"` // Short name for the application (similar to BTP XSAPPNAME) BTPAppName string `json:"btpAppName"` // Provider subaccount where application services are created diff --git a/pkg/client/applyconfiguration/sme.sap.com/v1alpha1/capapplicationspec.go b/pkg/client/applyconfiguration/sme.sap.com/v1alpha1/capapplicationspec.go index dba20b80..1c5e22fb 100644 --- a/pkg/client/applyconfiguration/sme.sap.com/v1alpha1/capapplicationspec.go +++ b/pkg/client/applyconfiguration/sme.sap.com/v1alpha1/capapplicationspec.go @@ -17,7 +17,10 @@ type CAPApplicationSpecApplyConfiguration struct { // [DEPRECATED] Domains used by the application // Will be removed in future versions Domains *ApplicationDomainsApplyConfiguration `json:"domains,omitempty"` // SAP BTP Global Account Identifier where services are entitles for the current application + // Will soon be deprecated, use ProviderSubaccountId instead GlobalAccountId *string `json:"globalAccountId,omitempty"` + // The subaccount ID in which the application is provided (will soon replace GlobalAccountId) + ProviderSubaccountId *string `json:"providerSubaccountId,omitempty"` // Short name for the application (similar to BTP XSAPPNAME) BTPAppName *string `json:"btpAppName,omitempty"` // Provider subaccount where application services are created @@ -61,6 +64,14 @@ func (b *CAPApplicationSpecApplyConfiguration) WithGlobalAccountId(value string) return b } +// WithProviderSubaccountId sets the ProviderSubaccountId field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ProviderSubaccountId field is set to the value of the last call. +func (b *CAPApplicationSpecApplyConfiguration) WithProviderSubaccountId(value string) *CAPApplicationSpecApplyConfiguration { + b.ProviderSubaccountId = &value + return b +} + // WithBTPAppName sets the BTPAppName field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the BTPAppName field is set to the value of the last call. diff --git a/website/includes/api-reference.html b/website/includes/api-reference.html index 0cf0694d..e5183889 100644 --- a/website/includes/api-reference.html +++ b/website/includes/api-reference.html @@ -113,7 +113,19 @@
SAP BTP Global Account Identifier where services are entitles for the current application
+SAP BTP Global Account Identifier where services are entitles for the current application +Will soon be deprecated, use ProviderSubaccountId instead
+providerSubaccountIdThe subaccount ID in which the application is provided (will soon replace GlobalAccountId)
SAP BTP Global Account Identifier where services are entitles for the current application
+SAP BTP Global Account Identifier where services are entitles for the current application +Will soon be deprecated, use ProviderSubaccountId instead
+providerSubaccountIdThe subaccount ID in which the application is provided (will soon replace GlobalAccountId)
Generated with gen-crd-api-reference-docs
-on git commit a79e9ba.
+on git commit 34c872a.