Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Add backend service reference and refactor reference code #3316

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
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
37 changes: 37 additions & 0 deletions apis/compute/v1beta1/backendservice_identity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// 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 v1beta1

type BackendServiceIdentity struct {
id string
parent *BackendServiceParent
}

type BackendServiceParent struct {
ProjectID string
Region string
}

func (p *BackendServiceParent) String() string {
if p.Region == "global" {
return "projects/" + p.ProjectID + "/global"
} else {
return "projects/" + p.ProjectID + "/regions/" + p.Region
}
}

func (i *BackendServiceIdentity) ID() string {
return i.id
}
133 changes: 133 additions & 0 deletions apis/compute/v1beta1/backendservice_reference.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// 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 v1beta1

import (
"context"
"fmt"
"strings"

"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"

refsv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

var _ refsv1beta1.ExternalNormalizer = &ComputeBackendServiceRef{}
var ComputeBackendServiceGVK = GroupVersion.WithKind("ComputeBackendService")

// ComputeBackendServiceRef defines the resource reference to ComputeBackendService, which "External" field
// holds the GCP identifier for the KRM object.
type ComputeBackendServiceRef struct {
// The value of an externally managed ComputeBackendService resource.
// Should be in the format "projects/{{project}}/global/backendServices/{{backendService}}"
// or "projects/{{project}}/regions/{{region}}/backendServices/{{backendService}}".
External string `json:"external,omitempty"`

// The name of a ComputeBackendService resource.
Name string `json:"name,omitempty"`

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

// NormalizedExternal provision the "External" value for other resource that depends on ComputeBackendService.
// If the "External" is given in the other resource's spec.ComputeBackendServiceRef, the given value will be used.
// Otherwise, the "Name" and "Namespace" will be used to query the actual ComputeBackendService object from the cluster.
func (r *ComputeBackendServiceRef) NormalizedExternal(ctx context.Context, reader client.Reader, otherNamespace string) (string, error) {
referenceContext := refsv1beta1.ReferenceContext{IsDirectOnly: false, TargetField: "status.selfLink"}
// Get value from spec.ComputeBackendServiceRef.external
if r.External != "" {
if r.Name != "" {
return "", fmt.Errorf("cannot specify both name and external on reference")
}
if referenceContext.IsDirectOnly {
if _, err := ParseBackendServiceExternal(r.External); err != nil {
return "", err
}
}
// To ensure backward compatibility for existing users, we do not enforce external format
// for non-direct resources
return r.External, nil
}

if r.Name == "" {
return "", fmt.Errorf("must specify either name or external on reference")
}

// Get value from the Config Connector object
if r.Namespace == "" {
r.Namespace = otherNamespace
}
key := types.NamespacedName{Name: r.Name, Namespace: r.Namespace}

u, err := refsv1beta1.ResolveResourceName(ctx, reader, key, ComputeBackendServiceGVK)
if err != nil {
return "", err
}

actualExternalRef, found, err := unstructured.NestedString(u.Object, "status", "externalRef")
if err != nil {
return "", fmt.Errorf("error getting status.externalRef for %s %s/%s: %w", u.GetKind(), u.GetNamespace(), u.GetName(), err)
}
// If object is DirectOnly, it is created by direct controller.
// Get value from status.externalRef, which is the most trustworthy source.
if referenceContext.IsDirectOnly {
if !found || actualExternalRef == "" {
return "", fmt.Errorf("status.externalRef is required for DirectOnly resources, but is missing or empty for %s %s/%s", u.GetKind(), u.GetNamespace(), u.GetName())
}
r.External = actualExternalRef
}

// If object not DirectOnly, it can be created by either direct controller or legacy controller, depends on user's settings.
// If status.externalRef does not exist, it's created by legacy controller. Get values from target field.
if !found {
targetField := referenceContext.TargetField
tokens := strings.Split(targetField, ".")
targetField, found, err := unstructured.NestedString(u.Object, tokens...)
if err != nil {
return "", fmt.Errorf("error getting target field %s for %s %s/%s: %w", targetField, u.GetKind(), u.GetNamespace(), u.GetName(), err)
}
if !found || targetField == "" {
return "", fmt.Errorf("target field %s is required but is missing or empty for %s %s/%s", targetField, u.GetKind(), u.GetNamespace(), u.GetName())
}
r.External = targetField
} else {
// If status.externalRef exists, it's created by direct controller. Get value from status.externalRef.
r.External = actualExternalRef
}
return r.External, nil
}

func ParseBackendServiceExternal(external string) (identity *BackendServiceIdentity, err error) {
external = strings.TrimPrefix(external, "/")
tokens := strings.Split(external, "/")
if len(tokens) == 5 && tokens[0] == "projects" && tokens[3] == "backendServices" {
return &BackendServiceIdentity{
Copy link
Collaborator Author

@gemmahou gemmahou Dec 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thought: maybe we could rename ResourceIdentity to ResourceExternal? I feel "Identity" is just the same thing as "ExternalRef", so that we don't introduce many new terms to people.

parent: &BackendServiceParent{ProjectID: tokens[1], Region: tokens[2]},
id: tokens[4],
}, nil
} else if len(tokens) == 6 && tokens[0] == "projects" && tokens[2] == "regions" && tokens[4] == "backendServices" {
return &BackendServiceIdentity{
parent: &BackendServiceParent{ProjectID: tokens[1], Region: tokens[3]},
id: tokens[5],
}, nil
}
acceptedFormat := "projects/{{project}}/global/backendServices/{{backendService}} or projects/{{project}}/regions/{{region}}/backendServices/{{backendService}}"
return nil, k8s.NewInvalidFormatError(external, acceptedFormat)
}
2 changes: 1 addition & 1 deletion apis/compute/v1beta1/forwardingrule_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ type ComputeForwardingRuleSpec struct {
/* A ComputeBackendService to receive the matched traffic. This is
used only for internal load balancing. */
// +optional
BackendServiceRef *refs.ComputeBackendServiceRef `json:"backendServiceRef,omitempty"`
BackendServiceRef *ComputeBackendServiceRef `json:"backendServiceRef,omitempty"`

/* Immutable. An optional description of this resource. Provide this property when
you create the resource. */
Expand Down
3 changes: 1 addition & 2 deletions apis/compute/v1beta1/targettcpproxy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
package v1beta1

import (
refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
Expand All @@ -34,7 +33,7 @@ var (
type ComputeTargetTCPProxySpec struct {
// A reference to the ComputeBackendService resource.
// +required
BackendServiceRef *refs.ComputeBackendServiceRef `json:"backendServiceRef"`
BackendServiceRef *ComputeBackendServiceRef `json:"backendServiceRef"`

// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Description is immutable"
// Immutable. An optional description of this resource.
Expand Down
54 changes: 52 additions & 2 deletions apis/compute/v1beta1/zz_generated.deepcopy.go

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

9 changes: 0 additions & 9 deletions apis/refs/v1beta1/computerefs.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,15 +256,6 @@ type ComputeAddressRef struct {
Namespace string `json:"namespace,omitempty"`
}

type ComputeBackendServiceRef struct {
/* The ComputeBackendService selflink in the form "projects/{{project}}/global/backendServices/{{name}}" or "projects/{{project}}/regions/{{region}}/backendServices/{{name}}" when not managed by Config Connector. */
External string `json:"external,omitempty"`
/* The `name` field of a `ComputeBackendService` resource. */
Name string `json:"name,omitempty"`
/* The `namespace` field of a `ComputeBackendService` resource. */
Namespace string `json:"namespace,omitempty"`
}

type ComputeServiceAttachmentRef struct {
/* The ComputeServiceAttachment selflink in the form "projects/{{project}}/regions/{{region}}/serviceAttachments/{{name}}" when not managed by Config Connector. */
External string `json:"external,omitempty"`
Expand Down
19 changes: 19 additions & 0 deletions apis/refs/v1beta1/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@
package v1beta1

import (
"context"
"fmt"

"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/client"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

Expand All @@ -41,3 +47,16 @@ func GetLocation(u *unstructured.Unstructured) (string, error) {
}
return location, nil
}

func ResolveResourceName(ctx context.Context, reader client.Reader, key client.ObjectKey, gvk schema.GroupVersionKind) (*unstructured.Unstructured, error) {
resource := &unstructured.Unstructured{}
resource.SetGroupVersionKind(gvk)
if err := reader.Get(ctx, key, resource); err != nil {
if apierrors.IsNotFound(err) {
return nil, k8s.NewReferenceNotFoundError(resource.GroupVersionKind(), key)
}
return nil, fmt.Errorf("error reading referenced %v %v: %w", gvk.Kind, key, err)
}

return resource, nil
}
9 changes: 9 additions & 0 deletions apis/refs/v1beta1/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
)

type ReferenceContext struct {
// Whether a referenced resource is only managed by direct controller
IsDirectOnly bool

// the referenced resource's field that will be extracted and set as the value of referenced field
// If IsDirectOnly is false, TargetField is required
TargetField string
}

type ExternalNormalizer interface {
// NormalizedExternal expects the implemented struct has a "External" field, and this function
// assigns a value to the "External" field if it is empty.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,16 +113,15 @@ spec:
- external
properties:
external:
description: The ComputeBackendService selflink in the form "projects/{{project}}/global/backendServices/{{name}}"
or "projects/{{project}}/regions/{{region}}/backendServices/{{name}}"
when not managed by Config Connector.
description: The value of an externally managed ComputeBackendService
resource. Should be in the format "projects/{{project}}/global/backendServices/{{backendService}}"
or "projects/{{project}}/regions/{{region}}/backendServices/{{backendService}}".
type: string
name:
description: The `name` field of a `ComputeBackendService` resource.
description: The name of a ComputeBackendService resource.
type: string
namespace:
description: The `namespace` field of a `ComputeBackendService`
resource.
description: The namespace of a ComputeBackendService resource.
type: string
type: object
description:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,15 @@ spec:
- external
properties:
external:
description: The ComputeBackendService selflink in the form "projects/{{project}}/global/backendServices/{{name}}"
or "projects/{{project}}/regions/{{region}}/backendServices/{{name}}"
when not managed by Config Connector.
description: The value of an externally managed ComputeBackendService
resource. Should be in the format "projects/{{project}}/global/backendServices/{{backendService}}"
or "projects/{{project}}/regions/{{region}}/backendServices/{{backendService}}".
type: string
name:
description: The `name` field of a `ComputeBackendService` resource.
description: The name of a ComputeBackendService resource.
type: string
namespace:
description: The `namespace` field of a `ComputeBackendService`
resource.
description: The namespace of a ComputeBackendService resource.
type: string
type: object
description:
Expand Down
Loading
Loading