Skip to content

Commit

Permalink
Merge pull request #3436 from anhdle-sso/memorystore_id_ref
Browse files Browse the repository at this point in the history
chore: refactor the external reference for MemoryStoreInstance
  • Loading branch information
google-oss-prow[bot] authored Dec 20, 2024
2 parents 287a254 + 124ae3c commit 2c1d999
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 98 deletions.
86 changes: 86 additions & 0 deletions apis/memorystore/v1alpha1/instance_identiy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package v1alpha1

import (
"context"
"fmt"

"github.com/GoogleCloudPlatform/k8s-config-connector/apis/common"
refsv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/controller-runtime/pkg/client"
)

type MemoryStoreInstanceIdentity struct {
id string
parent *MemorystoreInstanceParent
}

func (i *MemoryStoreInstanceIdentity) String() string {
return i.parent.String() + "/instances/" + i.id
}

func (r *MemoryStoreInstanceIdentity) Parent() *MemorystoreInstanceParent {
return r.parent
}

func (r *MemoryStoreInstanceIdentity) ID() string {
return r.id
}

func NewMemoryStoreInstanceIdentity(ctx context.Context, reader client.Reader, obj *MemorystoreInstance, u *unstructured.Unstructured) (*MemoryStoreInstanceIdentity, error) {
// Get Parent
projectID, err := refsv1beta1.ResolveProjectID(ctx, reader, u)
if err != nil {
return nil, err
}

location := obj.Spec.Location
if location == "" {
return nil, fmt.Errorf("cannot resolve location")
}

// Get desired ID
resourceID := common.ValueOf(obj.Spec.ResourceID)
if resourceID == "" {
resourceID = obj.GetName()
}
if resourceID == "" {
return nil, fmt.Errorf("cannot resolve resource ID")
}
// Use approved External
externalRef := common.ValueOf(obj.Status.ExternalRef)
if externalRef != "" {
actualIdentity, err := ParseMemorystoreInstanceExternal(externalRef)
if err != nil {
return nil, err
}
if actualIdentity.parent.ProjectID != projectID {
return nil, fmt.Errorf("spec.projectRef changed, expect %s, got %s", actualIdentity.parent.ProjectID, projectID)
}
if actualIdentity.id != resourceID {
return nil, fmt.Errorf("cannot reset `metadata.name` or `spec.resourceID` to %s, since it has already assigned to %s",
resourceID, actualIdentity.id)
}
}
return &MemoryStoreInstanceIdentity{
parent: &MemorystoreInstanceParent{
ProjectID: projectID,
Location: location,
},
id: resourceID,
}, nil
}
114 changes: 21 additions & 93 deletions apis/memorystore/v1alpha1/instance_reference.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package v1alpha1

import (
"context"
"errors"
"fmt"
"strings"

Expand All @@ -41,8 +42,6 @@ type MemorystoreInstanceRef struct {

// The namespace of a MemorystoreInstance resource.
Namespace string `json:"namespace,omitempty"`

parent *MemorystoreInstanceParent
}

// NormalizedExternal provision the "External" value for other resource that depends on MemorystoreInstance.
Expand All @@ -54,7 +53,7 @@ func (r *MemorystoreInstanceRef) NormalizedExternal(ctx context.Context, reader
}
// From given External
if r.External != "" {
if _, _, err := parseMemorystoreInstanceExternal(r.External); err != nil {
if _, err := ParseMemorystoreInstanceExternal(r.External); err != nil {
return "", err
}
return r.External, nil
Expand All @@ -74,9 +73,15 @@ func (r *MemorystoreInstanceRef) NormalizedExternal(ctx context.Context, reader
return "", fmt.Errorf("reading referenced %s %s: %w", MemorystoreInstanceGVK, key, err)
}
// Get external from status.externalRef. This is the most trustworthy place.
actualExternalRef, _, err := unstructured.NestedString(u.Object, "status", "externalRef")
if err != nil {
return "", fmt.Errorf("reading status.externalRef: %w", err)
actualExternalRef, _, err1 := unstructured.NestedString(u.Object, "status", "externalRef")
if err1 != nil {
err1 = fmt.Errorf("MemorystoreInstance `status.externalRef` not configured: %w", err1)
// Backward compatible to Terraform/DCL based resource, which does not have status.externalRef.
var err2 error
actualExternalRef, _, err2 = unstructured.NestedString(u.Object, "status", "name")
if err2 != nil {
return "", errors.Join(err1, err2)
}
}
if actualExternalRef == "" {
return "", k8s.NewReferenceNotReadyError(u.GroupVersionKind(), key)
Expand All @@ -85,72 +90,6 @@ func (r *MemorystoreInstanceRef) NormalizedExternal(ctx context.Context, reader
return r.External, nil
}

// New builds a MemorystoreInstanceRef from the Config Connector MemorystoreInstance object.
func NewMemorystoreInstanceRef(ctx context.Context, reader client.Reader, obj *MemorystoreInstance) (*MemorystoreInstanceRef, error) {
id := &MemorystoreInstanceRef{}

// Get Parent
projectRef, err := refsv1beta1.ResolveProject(ctx, reader, obj.GetNamespace(), obj.Spec.ProjectRef)
if err != nil {
return nil, err
}
projectID := projectRef.ProjectID
if projectID == "" {
return nil, fmt.Errorf("cannot resolve project")
}
location := obj.Spec.Location
id.parent = &MemorystoreInstanceParent{ProjectID: projectID, Location: location}

// Get desired ID
resourceID := valueOf(obj.Spec.ResourceID)
if resourceID == "" {
resourceID = obj.GetName()
}
if resourceID == "" {
return nil, fmt.Errorf("cannot resolve resource ID")
}

// Use approved External
externalRef := valueOf(obj.Status.ExternalRef)
if externalRef == "" {
id.External = asMemorystoreInstanceExternal(id.parent, resourceID)
return id, nil
}

// Validate desired with actual
actualParent, actualResourceID, err := parseMemorystoreInstanceExternal(externalRef)
if err != nil {
return nil, err
}
if actualParent.ProjectID != projectID {
return nil, fmt.Errorf("spec.projectRef changed, expect %s, got %s", actualParent.ProjectID, projectID)
}
if actualParent.Location != location {
return nil, fmt.Errorf("spec.location changed, expect %s, got %s", actualParent.Location, location)
}
if actualResourceID != resourceID {
return nil, fmt.Errorf("cannot reset `metadata.name` or `spec.resourceID` to %s, since it has already assigned to %s",
resourceID, actualResourceID)
}
id.External = externalRef
id.parent = &MemorystoreInstanceParent{ProjectID: projectID, Location: location}
return id, nil
}

func (r *MemorystoreInstanceRef) Parent() (*MemorystoreInstanceParent, error) {
if r.parent != nil {
return r.parent, nil
}
if r.External != "" {
parent, _, err := parseMemorystoreInstanceExternal(r.External)
if err != nil {
return nil, err
}
return parent, nil
}
return nil, fmt.Errorf("MemorystoreInstanceRef not initialized from `NewMemorystoreInstanceRef` or `NormalizedExternal`")
}

type MemorystoreInstanceParent struct {
ProjectID string
Location string
Expand All @@ -160,28 +99,17 @@ func (p *MemorystoreInstanceParent) String() string {
return "projects/" + p.ProjectID + "/locations/" + p.Location
}

func asMemorystoreInstanceExternal(parent *MemorystoreInstanceParent, resourceID string) (external string) {
return parent.String() + "/instances/" + resourceID
}

func parseMemorystoreInstanceExternal(external string) (parent *MemorystoreInstanceParent, resourceID string, err error) {
func ParseMemorystoreInstanceExternal(external string) (*MemoryStoreInstanceIdentity, error) {
external = strings.TrimPrefix(external, "/")
tokens := strings.Split(external, "/")
if len(tokens) != 6 || tokens[0] != "projects" || tokens[2] != "locations" || tokens[4] != "instance" {
return nil, "", fmt.Errorf("format of MemorystoreInstance external=%q was not known (use projects/{{projectId}}/locations/{{location}}/instances/{{instanceID}})", external)
}
parent = &MemorystoreInstanceParent{
ProjectID: tokens[1],
Location: tokens[3],
}
resourceID = tokens[5]
return parent, resourceID, nil
}

func valueOf[T any](t *T) T {
var zeroVal T
if t == nil {
return zeroVal
}
return *t
return nil, fmt.Errorf("format of MemorystoreInstance external=%q was not known (use projects/{{projectId}}/locations/{{location}}/instances/{{instanceID}})", external)
}
return &MemoryStoreInstanceIdentity{
parent: &MemorystoreInstanceParent{
ProjectID: tokens[1],
Location: tokens[3],
},
id: tokens[5],
}, nil
}
25 changes: 20 additions & 5 deletions apis/memorystore/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 2c1d999

Please sign in to comment.