Skip to content

Commit

Permalink
move resolver functions back into api/refs
Browse files Browse the repository at this point in the history
  • Loading branch information
gemmahou committed Jul 29, 2024
1 parent 6408587 commit d69b507
Show file tree
Hide file tree
Showing 15 changed files with 358 additions and 432 deletions.
164 changes: 164 additions & 0 deletions apis/refs/v1beta1/computerefs.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@

package v1beta1

import (
"context"
"fmt"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"strings"
)

type ComputeNetworkRef struct {
/* The compute network selflink of form "projects/<project>/global/networks/<network>", when not managed by KCC. */
External string `json:"external,omitempty"`
Expand All @@ -23,6 +34,74 @@ type ComputeNetworkRef struct {
Namespace string `json:"namespace,omitempty"`
}

type ComputeNetwork struct {
Project string
ComputeNetworkID string
}

func (c *ComputeNetwork) String() string {
return fmt.Sprintf("projects/%s/global/networks/%s", c.Project, c.ComputeNetworkID)
}

func ResolveComputeNetworkRef(ctx context.Context, reader client.Reader, src client.Object, ref *ComputeNetworkRef) (*ComputeNetwork, error) {
if ref == nil {
return nil, nil
}

if ref.External != "" {
if ref.Name != "" {
return nil, fmt.Errorf("cannot specify both name and external on computenetwork reference")
}

tokens := strings.Split(ref.External, "/")
if len(tokens) == 5 && tokens[0] == "projects" && tokens[2] == "global" && tokens[3] == "networks" {
return &ComputeNetwork{
Project: tokens[1],
ComputeNetworkID: tokens[4]}, nil
}
return nil, fmt.Errorf("format of computenetwork external=%q was not known (use projects/<projectId>/global/networks/<networkid>)", ref.External)
}

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

key := types.NamespacedName{
Namespace: ref.Namespace,
Name: ref.Name,
}
if key.Namespace == "" {
key.Namespace = src.GetNamespace()
}

computenetwork := &unstructured.Unstructured{}
computenetwork.SetGroupVersionKind(schema.GroupVersionKind{
Group: "compute.cnrm.cloud.google.com",
Version: "v1beta1",
Kind: "ComputeNetwork",
})
if err := reader.Get(ctx, key, computenetwork); err != nil {
if apierrors.IsNotFound(err) {
return nil, fmt.Errorf("referenced ComputeNetwork %v not found", key)
}
return nil, fmt.Errorf("error reading referenced ComputeNetwork %v: %w", key, err)
}

computenetworkID, err := GetResourceID(computenetwork)
if err != nil {
return nil, err
}

computeNetworkProjectID, err := ResolveProjectID(ctx, reader, computenetwork)
if err != nil {
return nil, err
}
return &ComputeNetwork{
Project: computeNetworkProjectID,
ComputeNetworkID: computenetworkID,
}, nil
}

type ComputeSubnetworkRef struct {
/* The ComputeSubnetwork selflink of form "projects/{{project}}/regions/{{region}}/subnetworks/{{name}}", when not managed by KCC. */
External string `json:"external,omitempty"`
Expand Down Expand Up @@ -77,6 +156,91 @@ type ComputeTargetHTTPProxyRef struct {
Namespace string `json:"namespace,omitempty"`
}

type ComputeTargetHTTPProxy struct {
Project string
Location string
ComputeTargetHTTPProxyID string
}

func (c *ComputeTargetHTTPProxy) String() string {
if c.Location == "global" {
return fmt.Sprintf("projects/%s/global/targetHttpProxies/%s", c.Project, c.ComputeTargetHTTPProxyID)
}
return fmt.Sprintf("projects/%s/location/%s/targetHttpProxies/%s", c.Project, c.Location, c.ComputeTargetHTTPProxyID)
}

func ResolveTargetHTTPProxyRef(ctx context.Context, reader client.Reader, src client.Object, ref *ComputeTargetHTTPProxyRef) (*ComputeTargetHTTPProxy, error) {
if ref == nil {
return nil, nil
}

if ref.External != "" {
if ref.Name != "" {
return nil, fmt.Errorf("cannot specify both name and external on ComputeNetwork reference")
}

tokens := strings.Split(ref.External, "/")
if len(tokens) == 5 && tokens[0] == "projects" && tokens[2] == "global" && tokens[3] == "targetHttpProxies" {
return &ComputeTargetHTTPProxy{
Project: tokens[1],
Location: "global",
ComputeTargetHTTPProxyID: tokens[4]}, nil
} else if len(tokens) == 6 && tokens[0] == "projects" && tokens[2] == "location" && tokens[4] == "targetHttpProxies" {
return &ComputeTargetHTTPProxy{
Project: tokens[1],
Location: tokens[3],
ComputeTargetHTTPProxyID: tokens[5]}, nil
}
return nil, fmt.Errorf("format of ComputeTargetHTTPProxy external=%q was not known (use projects/<projectId>/global/targetHttpProxies/<proxyId> or projects/<projectId>/location/<location>/targetHttpProxies/<proxyId>)", ref.External)
}

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

key := types.NamespacedName{
Namespace: ref.Namespace,
Name: ref.Name,
}
if key.Namespace == "" {
key.Namespace = src.GetNamespace()
}

computeTargetHTTPProxy := &unstructured.Unstructured{}
computeTargetHTTPProxy.SetGroupVersionKind(schema.GroupVersionKind{
Group: "compute.cnrm.cloud.google.com",
Version: "v1beta1",
Kind: "ComputeTargetHTTPProxy",
})
if err := reader.Get(ctx, key, computeTargetHTTPProxy); err != nil {
if apierrors.IsNotFound(err) {
return nil, fmt.Errorf("referenced ComputeTargetHTTPProxy %v not found", key)
}
return nil, fmt.Errorf("error reading referenced ComputeTargetHTTPProxy %v: %w", key, err)
}

computeTargetHTTPProxyID, err := GetResourceID(computeTargetHTTPProxy)
if err != nil {
return nil, err
}

computeTargetHTTPProxyProjectID, err := ResolveProjectID(ctx, reader, computeTargetHTTPProxy)
if err != nil {
return nil, err
}

computeTargetHTTPProxyLocation, err := GetLocation(computeTargetHTTPProxy)
if err != nil {
return nil, err
}

return &ComputeTargetHTTPProxy{
Project: computeTargetHTTPProxyProjectID,
Location: computeTargetHTTPProxyLocation,
ComputeTargetHTTPProxyID: computeTargetHTTPProxyID,
}, nil
}

type ComputeTargetHTTPSProxyRef struct {
/* The ComputeTargetHTTPSProxy selflink in the form "projects/{{project}}/global/targetHttpProxies/{{name}}" or "projects/{{project}}/regions/{{region}}/targetHttpProxies/{{name}}" when not managed by KCC. */
External string `json:"external,omitempty"`
Expand Down
44 changes: 44 additions & 0 deletions apis/refs/v1beta1/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// 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 (
"fmt"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

func GetResourceID(u *unstructured.Unstructured) (string, error) {
resourceID, _, err := unstructured.NestedString(u.Object, "spec", "resourceID")
if err != nil {
return "", fmt.Errorf("reading spec.resourceID from %v %v/%v: %w", u.GroupVersionKind().Kind, u.GetNamespace(), u.GetName(), err)
}
if resourceID == "" {
resourceID = u.GetName()
}
return resourceID, nil
}

// TODO(yuhou): Location can be optional. Use provider default location when it's unset.
func GetLocation(obj *unstructured.Unstructured) (string, error) {
// TODO(yuhou): field can be "location" or "region".
location, _, err := unstructured.NestedString(obj.Object, "spec", "location")
if err != nil {
return "", fmt.Errorf("cannot get location for referenced %s %v: %w", obj.GetKind(), obj.GetNamespace(), err)
}
if location == "" {
return "", fmt.Errorf("cannot get location for referenced %s %v (spec.location not set)", obj.GetKind(), obj.GetNamespace())
}
return location, nil
}
131 changes: 131 additions & 0 deletions apis/refs/v1beta1/projectref.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@

package v1beta1

import (
"context"
"fmt"
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/k8s/v1alpha1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"strings"
)

// The Project that this resource belongs to.
type ProjectRef struct {
/* The `projectID` field of a project, when not managed by KCC. */
Expand All @@ -26,3 +38,122 @@ type ProjectRef struct {
// +optional
Kind string `json:"kind,omitempty"`
}

// AsProjectRef converts a generic ResourceRef into a ProjectRef
func AsProjectRef(in *v1alpha1.ResourceRef) *ProjectRef {
if in == nil {
return nil
}
return &ProjectRef{
Namespace: in.Namespace,
Name: in.Name,
External: in.External,
Kind: in.Kind,
}
}

type Project struct {
ProjectID string
}

// ResolveProject will resolve a ProjectRef to a Project, with the ProjectID.
func ResolveProject(ctx context.Context, reader client.Reader, src client.Object, ref *ProjectRef) (*Project, error) {
if ref == nil {
return nil, nil
}

if ref.Kind != "" {
if ref.Kind != "Project" {
return nil, fmt.Errorf("kind is optional on project reference, but must be \"Project\" if provided")
}
}

if ref.External != "" {
if ref.Name != "" {
return nil, fmt.Errorf("cannot specify both name and external on project reference")
}

tokens := strings.Split(ref.External, "/")
if len(tokens) == 1 {
return &Project{ProjectID: tokens[0]}, nil
}
if len(tokens) == 2 && tokens[0] == "projects" {
return &Project{ProjectID: tokens[1]}, nil
}
return nil, fmt.Errorf("format of project external=%q was not known (use projects/<projectId> or <projectId>)", ref.External)
}

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

key := types.NamespacedName{
Namespace: ref.Namespace,
Name: ref.Name,
}
if key.Namespace == "" {
key.Namespace = src.GetNamespace()
}

project := &unstructured.Unstructured{}
project.SetGroupVersionKind(schema.GroupVersionKind{
Group: "resourcemanager.cnrm.cloud.google.com",
Version: "v1beta1",
Kind: "Project",
})
if err := reader.Get(ctx, key, project); err != nil {
if apierrors.IsNotFound(err) {
return nil, fmt.Errorf("referenced Project %v not found", key)
}
return nil, fmt.Errorf("error reading referenced Project %v: %w", key, err)
}

projectID, err := GetResourceID(project)
if err != nil {
return nil, err
}

return &Project{
ProjectID: projectID,
}, nil
}

func ResolveProjectID(ctx context.Context, reader client.Reader, obj *unstructured.Unstructured) (string, error) {
projectRefExternal, _, _ := unstructured.NestedString(obj.Object, "spec", "projectRef", "external")
if projectRefExternal != "" {
projectRef := ProjectRef{
External: projectRefExternal,
}

project, err := ResolveProject(ctx, reader, obj, &projectRef)
if err != nil {
return "", fmt.Errorf("cannot parse projectRef.external %q in %v %v/%v: %w", projectRefExternal, obj.GetKind(), obj.GetNamespace(), obj.GetName(), err)
}
return project.ProjectID, nil
}

projectRefName, _, _ := unstructured.NestedString(obj.Object, "spec", "projectRef", "name")
if projectRefName != "" {
projectRefNamespace, _, _ := unstructured.NestedString(obj.Object, "spec", "projectRef", "namespace")

projectRef := ProjectRef{
Name: projectRefName,
Namespace: projectRefNamespace,
}
if projectRef.Namespace == "" {
projectRef.Namespace = obj.GetNamespace()
}

project, err := ResolveProject(ctx, reader, obj, &projectRef)
if err != nil {
return "", fmt.Errorf("cannot parse projectRef in %v %v/%v: %w", obj.GetKind(), obj.GetNamespace(), obj.GetName(), err)
}
return project.ProjectID, nil
}

if projectID := obj.GetAnnotations()["cnrm.cloud.google.com/project-id"]; projectID != "" {
return projectID, nil
}

return "", fmt.Errorf("cannot find project id for %v %v/%v", obj.GetKind(), obj.GetNamespace(), obj.GetName())
}
6 changes: 3 additions & 3 deletions pkg/controller/direct/cloudbuild/workerpool_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
"reflect"
"strings"

"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/resolverefs"
refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"

gcp "cloud.google.com/go/cloudbuild/apiv1/v2"
cloudbuildpb "cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb"
Expand Down Expand Up @@ -91,7 +91,7 @@ func (m *model) AdapterForObject(ctx context.Context, reader client.Reader, u *u
}

// Get GCP Project
projectRef, err := resolverefs.ResolveProjectRef(ctx, reader, obj, obj.Spec.ProjectRef)
projectRef, err := refs.ResolveProject(ctx, reader, obj, obj.Spec.ProjectRef)
if err != nil {
return nil, err
}
Expand All @@ -105,7 +105,7 @@ func (m *model) AdapterForObject(ctx context.Context, reader client.Reader, u *u

// Get computeNetwork
if obj.Spec.PrivatePoolConfig.NetworkConfig != nil {
networkRef, err := resolverefs.ResolveComputeNetworkRef(ctx, reader, obj, &obj.Spec.PrivatePoolConfig.NetworkConfig.PeeredNetworkRef)
networkRef, err := refs.ResolveComputeNetworkRef(ctx, reader, obj, &obj.Spec.PrivatePoolConfig.NetworkConfig.PeeredNetworkRef)
if err != nil {
return nil, err

Expand Down
Loading

0 comments on commit d69b507

Please sign in to comment.