Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions backend/pkg/controllers/controllerutils/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func (k *OperationKey) InitialController(controllerName string) *api.Controller
resourceID := api.Must(azcorearm.ParseResourceID(k.GetParentResourceID().String() + "/" + api.ControllerResourceTypeName + "/" + controllerName))
return &api.Controller{
CosmosMetadata: api.CosmosMetadata{
ResourceID: *resourceID,
ResourceID: resourceID,
},
ResourceID: resourceID, ExternalID: k.GetParentResourceID(),
Status: api.ControllerStatus{
Expand Down Expand Up @@ -123,7 +123,7 @@ func (k *HCPClusterKey) InitialController(controllerName string) *api.Controller
resourceID := api.Must(azcorearm.ParseResourceID(k.GetResourceID().String() + "/" + api.ControllerResourceTypeName + "/" + controllerName))
return &api.Controller{
CosmosMetadata: api.CosmosMetadata{
ResourceID: *resourceID,
ResourceID: resourceID,
},
ResourceID: resourceID,
ExternalID: k.GetResourceID(),
Expand Down
3 changes: 1 addition & 2 deletions frontend/pkg/frontend/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,7 @@ func (f *Frontend) updateHCPClusterInCosmos(ctx context.Context, writer http.Res
}

// Read back the resource document so the response body is accurate.
resultingUncastObj, err := transactionResult.GetItem(oldInternalCluster.ServiceProviderProperties.CosmosUID)
resultingUncastObj, err := transactionResult.GetItem(oldInternalCluster.GetCosmosData().GetCosmosUID())
if err != nil {
return utils.TrackError(err)
}
Expand Down Expand Up @@ -824,7 +824,6 @@ func mergeToInternalCluster(csCluster *arohcpv1alpha1.Cluster, internalCluster *
clusterServiceBasedInternalCluster.ServiceProviderProperties.ProvisioningState = internalCluster.ServiceProviderProperties.ProvisioningState
clusterServiceBasedInternalCluster.ServiceProviderProperties.ActiveOperationID = internalCluster.ServiceProviderProperties.ActiveOperationID
clusterServiceBasedInternalCluster.ServiceProviderProperties.ClusterServiceID = internalCluster.ServiceProviderProperties.ClusterServiceID
clusterServiceBasedInternalCluster.ServiceProviderProperties.CosmosUID = internalCluster.ServiceProviderProperties.CosmosUID
if clusterServiceBasedInternalCluster.Identity == nil {
clusterServiceBasedInternalCluster.Identity = &arm.ManagedServiceIdentity{}
}
Expand Down
3 changes: 1 addition & 2 deletions frontend/pkg/frontend/external_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ func (f *Frontend) updateExternalAuthInCosmos(ctx context.Context, writer http.R
}

// Read back the resource document so the response body is accurate.
resultingUncastInternalExternalAuth, err := transactionResult.GetItem(oldInternalExternalAuth.ServiceProviderProperties.CosmosUID)
resultingUncastInternalExternalAuth, err := transactionResult.GetItem(oldInternalExternalAuth.GetCosmosData().GetCosmosUID())
if err != nil {
return utils.TrackError(err)
}
Expand Down Expand Up @@ -690,7 +690,6 @@ func mergeToInternalExternalAuth(csEternalAuth *arohcpv1alpha1.ExternalAuth, int
// this does not use conversion.CopyReadOnly* because some ServiceProvider properties come from cluster-service-only or live reads
mergedExternalAuth.SystemData = internalObj.SystemData
mergedExternalAuth.Properties.ProvisioningState = internalObj.Properties.ProvisioningState
mergedExternalAuth.ServiceProviderProperties.CosmosUID = internalObj.ServiceProviderProperties.CosmosUID
mergedExternalAuth.ServiceProviderProperties.ClusterServiceID = internalObj.ServiceProviderProperties.ClusterServiceID
mergedExternalAuth.ServiceProviderProperties.ActiveOperationID = internalObj.ServiceProviderProperties.ActiveOperationID

Expand Down
3 changes: 1 addition & 2 deletions frontend/pkg/frontend/node_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ func (f *Frontend) updateNodePoolInCosmos(ctx context.Context, writer http.Respo
}

// Read back the resource document so the response body is accurate.
resultingUncastInternalNodePool, err := transactionResult.GetItem(oldInternalNodePool.ServiceProviderProperties.CosmosUID)
resultingUncastInternalNodePool, err := transactionResult.GetItem(oldInternalNodePool.GetCosmosData().GetCosmosUID())
if err != nil {
return utils.TrackError(err)
}
Expand Down Expand Up @@ -741,7 +741,6 @@ func mergeToInternalNodePool(clusterServiceNode *arohcpv1alpha1.NodePool, intern
mergedOldClusterServiceNodePool.SystemData = internalNodePool.SystemData
mergedOldClusterServiceNodePool.Tags = maps.Clone(internalNodePool.Tags)
mergedOldClusterServiceNodePool.Properties.ProvisioningState = internalNodePool.Properties.ProvisioningState
mergedOldClusterServiceNodePool.ServiceProviderProperties.CosmosUID = internalNodePool.ServiceProviderProperties.CosmosUID
mergedOldClusterServiceNodePool.ServiceProviderProperties.ClusterServiceID = internalNodePool.ServiceProviderProperties.ClusterServiceID
mergedOldClusterServiceNodePool.ServiceProviderProperties.ActiveOperationID = internalNodePool.ServiceProviderProperties.ActiveOperationID

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
"_rid": "LKgdAIiT-BgDAAAAAAAAAA==",
"_self": "dbs/LKgdAA==/colls/LKgdAIiT-Bg=/docs/LKgdAIiT-BgDAAAAAAAAAA==/",
"_ts": 1765995430,
"id": "b3fa2ee1-d1b3-4eaa-beae-7dd4003b4987",
"id": "|subscriptions|b3fa2ee1-d1b3-4eaa-beae-7dd4003b4987",
"partitionKey": "b3fa2ee1-d1b3-4eaa-beae-7dd4003b4987",
"properties": {
"resourceId": "/subscriptions/b3fa2ee1-d1b3-4eaa-beae-7dd4003b4987",
"properties": null,
"registrationDate": "2025-12-17T18:16:37+00:00",
"state": "Registered"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"_rid": "zDEpAKTEnZ4DAAAAAAAAAA==",
"_self": "dbs/zDEpAA==/colls/zDEpAKTEnZ4=/docs/zDEpAKTEnZ4DAAAAAAAAAA==/",
"_ts": 1765995984,
"id": "ddfbdeeb-89a1-4a9a-9469-2895f63e2d82",
"id": "|subscriptions|ddfbdeeb-89a1-4a9a-9469-2895f63e2d82",
"partitionKey": "ddfbdeeb-89a1-4a9a-9469-2895f63e2d82",
"properties": {
"properties": null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
"_rid": "LKgdAIiT-BgDAAAAAAAAAA==",
"_self": "dbs/LKgdAA==/colls/LKgdAIiT-Bg=/docs/LKgdAIiT-BgDAAAAAAAAAA==/",
"_ts": 1765995430,
"id": "b3fa2ee1-d1b3-4eaa-beae-7dd4003b4987",
"id": "|subscriptions|b3fa2ee1-d1b3-4eaa-beae-7dd4003b4987",
"partitionKey": "b3fa2ee1-d1b3-4eaa-beae-7dd4003b4987",
"properties": {
"resourceId": "/subscriptions/b3fa2ee1-d1b3-4eaa-beae-7dd4003b4987",
"properties": null,
"registrationDate": "2025-12-17T18:16:37+00:00",
"state": "Registered"
Expand Down
25 changes: 3 additions & 22 deletions internal/api/arm/subscription.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,6 @@ import (
azcorearm "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
)

// CosmosData contains the information that persisted resources must have for us to support CRUD against them.
// These are not (currently) all stored in the same place in our various types.
type CosmosData struct {
CosmosUID string
PartitionKey string
ItemID *azcorearm.ResourceID
}

// SubscriptionAPIVersion is the system API version for the subscription endpoint.
const SubscriptionAPIVersion = "2.0"

Expand All @@ -58,20 +50,9 @@ type Subscription struct {
CosmosUID string `json:"-"`
}

func (o *Subscription) GetCosmosData() CosmosData {
cosmosUID := strings.ReplaceAll(strings.ToLower(o.ResourceID.String()), "/", "|")
if len(o.CosmosUID) != 0 {
// if this is an item that is being serialized for the first time, then we can force it to use the new scheme.
// if it already thinks it knows its CosmosID, then we must accept what it thinks because this could be a case
// where we have a new backend and an old frontend. In that case, the content still has random UIDs, but the backend
// must be able to read AND write the records. This means we cannot assume that all UIDs have already changed.
cosmosUID = o.CosmosUID
}

return CosmosData{
CosmosUID: cosmosUID,
PartitionKey: strings.ToLower(o.ResourceID.Name),
ItemID: o.ResourceID,
func (o *Subscription) GetCosmosData() *CosmosMetadata {
return &CosmosMetadata{
ResourceID: o.ResourceID,
}
}

Expand Down
76 changes: 76 additions & 0 deletions internal/api/arm/types_cosmosdata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package arm

import (
"errors"
"strings"

azcorearm "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
)

// CosmosMetadata contains the information that persisted resources must have for us to support CRUD against them.
// These are not (currently) all stored in the same place in our various types.
type CosmosMetadata struct {
ResourceID *azcorearm.ResourceID `json:"resourceID"`
}

var (
_ CosmosPersistable = &CosmosMetadata{}
_ CosmosMetadataAccessor = &CosmosMetadata{}
)

type CosmosPersistable interface {
GetCosmosData() *CosmosMetadata
}

func (o *CosmosMetadata) GetCosmosUID() string {
return Must(ResourceIDToCosmosID(o.ResourceID))
}

func (o *CosmosMetadata) GetPartitionKey() string {
return strings.ToLower(o.ResourceID.SubscriptionID)
}

func (o *CosmosMetadata) GetResourceID() *azcorearm.ResourceID {
return o.ResourceID
}

func (o *CosmosMetadata) SetResourceID(resourceID *azcorearm.ResourceID) {
o.ResourceID = resourceID
}

func (o *CosmosMetadata) GetCosmosData() *CosmosMetadata {
return &CosmosMetadata{
ResourceID: o.ResourceID,
}
}

type CosmosMetadataAccessor interface {
GetResourceID() *azcorearm.ResourceID
SetResourceID(*azcorearm.ResourceID)
}

func ResourceIDToCosmosID(resourceID *azcorearm.ResourceID) (string, error) {
if resourceID == nil {
return "", errors.New("resource ID is nil")
}
return ResourceIDStringToCosmosID(resourceID.String())
}

func ResourceIDStringToCosmosID(resourceID string) (string, error) {
if len(resourceID) == 0 {
return "", errors.New("resource ID is empty")
}
// cosmos uses a REST API, which means that IDs that contain slashes cause problems with URL handling.
// We chose | because that is a delimiter that is not allowed inside of an ARM resource ID because it is a separator
// for multiple resource IDs.
return strings.ReplaceAll(strings.ToLower(resourceID), "/", "|"), nil
}

// Must is a helper function that takes a value and error, returns the value if no error occurred,
// or panics if an error occurred. This is useful for test setup where we don't expect errors.
func Must[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}
20 changes: 3 additions & 17 deletions internal/api/types_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
package api

import (
"strings"

azcorearm "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"

"github.com/Azure/ARO-HCP/internal/api/arm"
Expand All @@ -33,20 +31,9 @@ type HCPOpenShiftCluster struct {

var _ CosmosPersistable = &HCPOpenShiftCluster{}

func (o *HCPOpenShiftCluster) GetCosmosData() CosmosData {
cosmosUID := Must(ResourceIDToCosmosID(o.ID))
if len(o.ServiceProviderProperties.CosmosUID) != 0 {
// if this is an item that is being serialized for the first time, then we can force it to use the new scheme.
// if it already thinks it knows its CosmosID, then we must accept what it thinks because this could be a case
// where we have a new backend and an old frontend. In that case, the content still has random UIDs, but the backend
// must be able to read AND write the records. This means we cannot assume that all UIDs have already changed.
cosmosUID = o.ServiceProviderProperties.CosmosUID
}

return CosmosData{
CosmosUID: cosmosUID,
PartitionKey: strings.ToLower(o.ID.SubscriptionID),
ItemID: o.ID,
func (o *HCPOpenShiftCluster) GetCosmosData() *CosmosData {
return &CosmosData{
ResourceID: o.ID,
}
}

Expand All @@ -66,7 +53,6 @@ type HCPOpenShiftClusterCustomerProperties struct {
// HCPOpenShiftClusterCustomerProperties represents the property bag of a HCPOpenShiftCluster resource.
type HCPOpenShiftClusterServiceProviderProperties struct {
ProvisioningState arm.ProvisioningState `json:"provisioningState,omitempty"`
CosmosUID string `json:"cosmosUID,omitempty"`
ClusterServiceID InternalID `json:"clusterServiceID,omitempty"`
ActiveOperationID string `json:"activeOperationId,omitempty"`
DNS ServiceProviderDNSProfile `json:"dns,omitempty"`
Expand Down
2 changes: 0 additions & 2 deletions internal/api/types_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ type Controller struct {
// we need to be sure that all new records have it too.
ResourceID *azcorearm.ResourceID `json:"resourceId,omitempty"`

CosmosUID string `json:"cosmosUID,omitempty"`

// ExternalID is the Azure resource ID of the type this is associated with.
ExternalID *azcorearm.ResourceID `json:"externalId,omitempty"`

Expand Down
73 changes: 8 additions & 65 deletions internal/api/types_cosmosdata.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,79 +15,22 @@
package api

import (
"errors"
"strings"

azcorearm "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"

"github.com/Azure/ARO-HCP/internal/api/arm"
)

// CosmosMetadata is metadata required for all data we store in cosmos
type CosmosMetadata struct {
// resourceID is used as the cosmosUID after replacing all '/' with '|'
ResourceID azcorearm.ResourceID `json:"resourceID"`
type CosmosMetadata = arm.CosmosMetadata
type CosmosMetadataAccessor = arm.CosmosMetadataAccessor
type CosmosPersistable = arm.CosmosPersistable
type CosmosData = arm.CosmosMetadata

// TODO add an etag that is not serialized to cosmos, but is set on read.
// When non-empty it will cause a conditional replace to be used
// When empty it will cause an unconditional replace
}

func (c *CosmosMetadata) GetResourceID() azcorearm.ResourceID {
return c.ResourceID
}

func (c *CosmosMetadata) SetResourceID(resourceID azcorearm.ResourceID) {
c.ResourceID = resourceID
}

type CosmosMetadataAccessor interface {
GetResourceID() azcorearm.ResourceID
SetResourceID(azcorearm.ResourceID)
}

var _ CosmosPersistable = &CosmosMetadata{}

func (o *CosmosMetadata) GetCosmosData() CosmosData {
return CosmosData{
CosmosUID: Must(ResourceIDToCosmosID(&o.ResourceID)),
// partitionkeys are case-sensitive in cosmos, so we need all of our cases to be the same
// and we have no guarantee that prior to this the case is consistent.
PartitionKey: strings.ToLower(o.ResourceID.SubscriptionID),
ItemID: &o.ResourceID,
}
}

func (o *CosmosMetadata) SetCosmosDocumentData(cosmosUID string) {
panic("not supported")
}

var _ CosmosMetadataAccessor = &CosmosMetadata{}

type CosmosPersistable interface {
GetCosmosData() CosmosData
}

// CosmosData contains the information that persisted resources must have for us to support CRUD against them.
// These are not (currently) all stored in the same place in our various types.
type CosmosData = arm.CosmosData

func ResourceIDToCosmosID(resourceID *azcorearm.ResourceID) (string, error) {
if resourceID == nil {
return "", errors.New("resource ID is nil")
}
return ResourceIDStringToCosmosID(resourceID.String())
}

func ResourceIDStringToCosmosID(resourceID string) (string, error) {
if len(resourceID) == 0 {
return "", errors.New("resource ID is empty")
}
// cosmos uses a REST API, which means that IDs that contain slashes cause problems with URL handling.
// We chose | because that is a delimiter that is not allowed inside of an ARM resource ID because it is a separator
// for multiple resource IDs.
return strings.ReplaceAll(strings.ToLower(resourceID), "/", "|"), nil
}
var (
ResourceIDToCosmosID = arm.ResourceIDToCosmosID
ResourceIDStringToCosmosID = arm.ResourceIDStringToCosmosID
)

func CosmosIDToResourceID(resourceID string) (*azcorearm.ResourceID, error) {
return azcorearm.ParseResourceID(strings.ReplaceAll(resourceID, "|", "/"))
Expand Down
19 changes: 3 additions & 16 deletions internal/api/types_externalauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
package api

import (
"strings"
"time"

azcorearm "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
Expand All @@ -33,20 +32,9 @@ type HCPOpenShiftClusterExternalAuth struct {

var _ CosmosPersistable = &HCPOpenShiftClusterExternalAuth{}

func (o *HCPOpenShiftClusterExternalAuth) GetCosmosData() CosmosData {
cosmosUID := Must(ResourceIDToCosmosID(o.ID))
if len(o.ServiceProviderProperties.CosmosUID) != 0 {
// if this is an item that is being serialized for the first time, then we can force it to use the new scheme.
// if it already thinks it knows its CosmosID, then we must accept what it thinks because this could be a case
// where we have a new backend and an old frontend. In that case, the content still has random UIDs, but the backend
// must be able to read AND write the records. This means we cannot assume that all UIDs have already changed.
cosmosUID = o.ServiceProviderProperties.CosmosUID
}

return CosmosData{
CosmosUID: cosmosUID,
PartitionKey: strings.ToLower(o.ID.SubscriptionID),
ItemID: o.ID,
func (o *HCPOpenShiftClusterExternalAuth) GetCosmosData() *CosmosData {
return &CosmosData{
ResourceID: o.ID,
}
}

Expand All @@ -61,7 +49,6 @@ type HCPOpenShiftClusterExternalAuthProperties struct {
}

type HCPOpenShiftClusterExternalAuthServiceProviderProperties struct {
CosmosUID string `json:"cosmosUID,omitempty"`
ClusterServiceID InternalID `json:"clusterServiceID,omitempty"`
ActiveOperationID string `json:"activeOperationId,omitempty"`
}
Expand Down
Loading