diff --git a/apis/refs/v1beta1/projectref.go b/apis/refs/v1beta1/projectref.go index ae41316515..f733d505d3 100644 --- a/apis/refs/v1beta1/projectref.go +++ b/apis/refs/v1beta1/projectref.go @@ -14,19 +14,6 @@ package v1beta1 -import ( - "context" - "fmt" - "strings" - - "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" -) - // The Project that this resource belongs to. type ProjectRef struct { /* The `projectID` field of a project, when not managed by KCC. */ @@ -39,82 +26,3 @@ 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/ or )", 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 -} diff --git a/apis/refs/v1beta1/resourceref.go b/apis/refs/v1beta1/resourceref.go deleted file mode 100644 index 7556744aaf..0000000000 --- a/apis/refs/v1beta1/resourceref.go +++ /dev/null @@ -1,126 +0,0 @@ -// 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/clients/generated/apis/k8s/v1alpha1" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -func ComputeNetworkRef_ConvertToExternal(ctx context.Context, reader client.Reader, src client.Object, ref *v1alpha1.ResourceRef) (*v1alpha1.ResourceRef, error) { - // Validate the ResourceRef - if err := ValidateResourceRef(ref); err != nil { - return nil, err - } - - // Validate and get external references if exist - if ref.External != "" { - if err := validateExternalComputeNetwork(ref); err != nil { - return nil, err - } - return ref, nil - } - - // Convert references to external references - // Parse the ComputeNetwork resource - computenetwork, err := ParseResourceRef(ctx, reader, src, ref, "compute.cnrm.cloud.google.com", "v1beta1", "ComputeNetwork") - if err != nil { - return nil, err - } - - // Get the resourceID - computenetworkID, err := GetResourceID(computenetwork) - if err != nil { - return nil, err - } - - // Get the project ID - computeNetworkProjectID, err := GetProjectID(ctx, reader, computenetwork) - if err != nil { - return nil, err - } - - return &v1alpha1.ResourceRef{ - External: fmt.Sprintf("projects/%s/global/networks/%s", computeNetworkProjectID, computenetworkID), - }, nil -} - -func ComputeTargetHTTPProxyRef_ConvertToExternal(ctx context.Context, reader client.Reader, src client.Object, ref *v1alpha1.ResourceRef) (*v1alpha1.ResourceRef, error) { - if err := ValidateResourceRef(ref); err != nil { - return nil, err - } - - if ref.External != "" { - if err := validateExternalComputeTargetHTTPProxy(ref); err != nil { - return nil, err - } - return ref, nil - } - - computeTargetHTTPProxy, err := ParseResourceRef(ctx, reader, src, ref, "compute.cnrm.cloud.google.com", "v1beta1", "computeTargetHTTPProxy") - if err != nil { - return nil, err - } - computeTargetHTTPProxyID, err := GetResourceID(computeTargetHTTPProxy) - if err != nil { - return nil, err - } - computeTargetHTTPProxyProjectID, err := GetProjectID(ctx, reader, computeTargetHTTPProxy) - if err != nil { - return nil, err - } - location, err := GetLocation(computeTargetHTTPProxy) - if err != nil { - return nil, err - } - - var external string - if location == "global" { - external = fmt.Sprintf("projects/%s/global/targetHttpProxies/%s", computeTargetHTTPProxyProjectID, computeTargetHTTPProxyID) - } else { - external = fmt.Sprintf("projects/%s/location/%s/targetHttpProxies/%s", computeTargetHTTPProxyProjectID, location, computeTargetHTTPProxyID) - } - - return &v1alpha1.ResourceRef{ - External: external, - }, nil -} - -func validateExternalComputeNetwork(ref *v1alpha1.ResourceRef) error { - tokens := strings.Split(ref.External, "/") - if len(tokens) == 5 && tokens[0] == "projects" && tokens[2] == "global" && tokens[3] == "networks" { - return nil - } - return fmt.Errorf( - "format of ComputeNetwork external=%q was not known (use projects//global/networks/)", - ref.External) -} - -func validateExternalComputeTargetHTTPProxy(ref *v1alpha1.ResourceRef) error { - tokens := strings.Split(ref.External, "/") - if len(tokens) == 5 && tokens[0] == "projects" && tokens[2] == "global" && tokens[3] == "targetHttpProxies" { - return nil - } else if len(tokens) == 6 && tokens[0] == "projects" && tokens[2] == "location" && tokens[4] == "targetHttpProxies" { - return nil - } - return fmt.Errorf( - "format of ComputeTargetHTTPProxy external=%q was not known "+ - "(use projects//global/targetHttpProxies/ or projects//location//targetHttpProxies/)", - ref.External) -} diff --git a/pkg/controller/direct/cloudbuild/workerpool_controller.go b/pkg/controller/direct/cloudbuild/workerpool_controller.go index d7b3c98da3..e67bc3a496 100644 --- a/pkg/controller/direct/cloudbuild/workerpool_controller.go +++ b/pkg/controller/direct/cloudbuild/workerpool_controller.go @@ -22,12 +22,13 @@ import ( "reflect" "strings" + "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/resolverefs" + gcp "cloud.google.com/go/cloudbuild/apiv1/v2" cloudbuildpb "cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb" "google.golang.org/api/option" krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/cloudbuild/v1beta1" - refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase" @@ -90,7 +91,7 @@ func (m *model) AdapterForObject(ctx context.Context, reader client.Reader, u *u } // Get GCP Project - projectRef, err := refs.ResolveProject(ctx, reader, obj, obj.Spec.ProjectRef) + projectRef, err := resolverefs.ResolveProjectRef(ctx, reader, obj, obj.Spec.ProjectRef) if err != nil { return nil, err } @@ -104,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 := refs.ResolveComputeNetwork(ctx, reader, obj, &obj.Spec.PrivatePoolConfig.NetworkConfig.PeeredNetworkRef) + networkRef, err := resolverefs.ResolveComputeNetworkRef(ctx, reader, obj, &obj.Spec.PrivatePoolConfig.NetworkConfig.PeeredNetworkRef) if err != nil { return nil, err diff --git a/pkg/controller/direct/dataform/repository_controller.go b/pkg/controller/direct/dataform/repository_controller.go index 4a59581371..6288d55a5e 100644 --- a/pkg/controller/direct/dataform/repository_controller.go +++ b/pkg/controller/direct/dataform/repository_controller.go @@ -19,8 +19,9 @@ import ( "fmt" "reflect" + "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/resolverefs" + krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/dataform/v1alpha1" - refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase" @@ -87,7 +88,7 @@ func (m *model) AdapterForObject(ctx context.Context, reader client.Reader, u *u return nil, fmt.Errorf("cannot resolve resource ID") } - projectRef, err := refs.ResolveProject(ctx, reader, obj, obj.Spec.ProjectRef) + projectRef, err := resolverefs.ResolveProjectRef(ctx, reader, obj, obj.Spec.ProjectRef) if err != nil { return nil, err } diff --git a/pkg/controller/direct/gkehub/featuremembership_controller.go b/pkg/controller/direct/gkehub/featuremembership_controller.go index 2801e003ea..8b6a409d2c 100644 --- a/pkg/controller/direct/gkehub/featuremembership_controller.go +++ b/pkg/controller/direct/gkehub/featuremembership_controller.go @@ -19,6 +19,8 @@ import ( "fmt" "reflect" + "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/resolverefs" + featureapi "google.golang.org/api/gkehub/v1beta" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -84,7 +86,7 @@ func (m *gkeHubModel) AdapterForObject(ctx context.Context, reader client.Reader Namespace: obj.Spec.ProjectRef.Namespace, External: obj.Spec.ProjectRef.External, } - project, err := refs.ResolveProject(ctx, reader, obj, projectRef) + project, err := resolverefs.ResolveProjectRef(ctx, reader, obj, projectRef) if err != nil { return nil, err } diff --git a/pkg/controller/direct/logging/logbucketref.go b/pkg/controller/direct/logging/logbucketref.go index 0c887268c1..4ff5a2d825 100644 --- a/pkg/controller/direct/logging/logbucketref.go +++ b/pkg/controller/direct/logging/logbucketref.go @@ -19,6 +19,8 @@ import ( "fmt" "strings" + "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/resolverefs" + refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/k8s/v1alpha1" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/logging/v1beta1" @@ -99,7 +101,7 @@ func LogBucketRef_ConvertToExternal(ctx context.Context, reader client.Reader, s Namespace: obj.Spec.ProjectRef.Namespace, External: obj.Spec.ProjectRef.External, } - project, err := refs.ResolveProject(ctx, reader, loggingLogBucket, projectRef) + project, err := resolverefs.ResolveProjectRef(ctx, reader, loggingLogBucket, projectRef) if err != nil { return fmt.Errorf("cannot get project for referenced LoggingLogBucket %v: %w", key, err) } diff --git a/pkg/controller/direct/logging/logmetric_controller.go b/pkg/controller/direct/logging/logmetric_controller.go index c8c6dd69a2..93a29925fc 100644 --- a/pkg/controller/direct/logging/logmetric_controller.go +++ b/pkg/controller/direct/logging/logmetric_controller.go @@ -20,6 +20,8 @@ import ( "reflect" "strings" + "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/resolverefs" + api "google.golang.org/api/logging/v2" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -28,7 +30,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/logging/v1beta1" - refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct" @@ -89,7 +90,7 @@ func (m *logMetricModel) AdapterForObject(ctx context.Context, reader client.Rea return nil, fmt.Errorf("cannot resolve resource ID") } - projectRef, err := refs.ResolveProject(ctx, reader, obj, &obj.Spec.ProjectRef) + projectRef, err := resolverefs.ResolveProjectRef(ctx, reader, obj, &obj.Spec.ProjectRef) if err != nil { return nil, err } diff --git a/pkg/controller/direct/monitoring/monitoringdashboard_controller.go b/pkg/controller/direct/monitoring/monitoringdashboard_controller.go index 3773b4db56..aa640f25a0 100644 --- a/pkg/controller/direct/monitoring/monitoringdashboard_controller.go +++ b/pkg/controller/direct/monitoring/monitoringdashboard_controller.go @@ -19,6 +19,8 @@ import ( "fmt" "strings" + "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/resolverefs" + api "cloud.google.com/go/monitoring/dashboard/apiv1" pb "cloud.google.com/go/monitoring/dashboard/apiv1/dashboardpb" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -27,7 +29,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/monitoring/v1beta1" - refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase" @@ -87,7 +88,7 @@ func (m *dashboardModel) AdapterForObject(ctx context.Context, kube client.Reade return nil, fmt.Errorf("cannot resolve resource ID") } - projectRef, err := refs.ResolveProject(ctx, kube, obj, &obj.Spec.ProjectRef) + projectRef, err := resolverefs.ResolveProjectRef(ctx, kube, obj, &obj.Spec.ProjectRef) if err != nil { return nil, err } diff --git a/pkg/controller/direct/monitoring/refs.go b/pkg/controller/direct/monitoring/refs.go index f602e9a557..d89c71a226 100644 --- a/pkg/controller/direct/monitoring/refs.go +++ b/pkg/controller/direct/monitoring/refs.go @@ -19,6 +19,8 @@ import ( "fmt" "strings" + "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/resolverefs" + krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/monitoring/v1beta1" refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/k8s/v1alpha1" @@ -54,7 +56,7 @@ func normalizeResourceName(ctx context.Context, reader client.Reader, src client switch ref.Kind { case "Project": - project, err := refs.ResolveProject(ctx, reader, src, &refs.ProjectRef{ + project, err := resolverefs.ResolveProjectRef(ctx, reader, src, &refs.ProjectRef{ Name: ref.Name, Namespace: ref.Namespace, External: ref.External, @@ -88,7 +90,7 @@ func normalizeResourceName(ctx context.Context, reader client.Reader, src client return ref, nil } -func normalizeMonitoringAlertPolicyRef(ctx context.Context, reader client.Reader, src client.Object, project refs.Project, ref *refs.MonitoringAlertPolicyRef) (*refs.MonitoringAlertPolicyRef, error) { +func normalizeMonitoringAlertPolicyRef(ctx context.Context, reader client.Reader, src client.Object, project resolverefs.Project, ref *refs.MonitoringAlertPolicyRef) (*refs.MonitoringAlertPolicyRef, error) { if ref == nil { return nil, nil } @@ -138,12 +140,12 @@ func normalizeMonitoringAlertPolicyRef(ctx context.Context, reader client.Reader return nil, fmt.Errorf("error reading referenced MonitoringAlertPolicy %v: %w", key, err) } - alertPolicyResourceID, err := refs.GetResourceID(alertPolicy) + alertPolicyResourceID, err := resolverefs.GetResourceID(alertPolicy) if err != nil { return nil, err } - alertPolicyProjectID, err := refs.GetProjectID(ctx, reader, alertPolicy) + alertPolicyProjectID, err := resolverefs.GetProjectID(ctx, reader, alertPolicy) if err != nil { return nil, err } @@ -160,7 +162,7 @@ func normalizeProjectRef(ctx context.Context, reader client.Reader, src client.O return nil, nil } - project, err := refs.ResolveProject(ctx, reader, src, ref) + project, err := resolverefs.ResolveProjectRef(ctx, reader, src, ref) if err != nil { return nil, err } @@ -174,7 +176,7 @@ type refNormalizer struct { ctx context.Context kube client.Reader src client.Object - project refs.Project + project resolverefs.Project } func (r *refNormalizer) VisitField(path string, v any) error { diff --git a/pkg/controller/direct/privateca/privatecapool_controller.go b/pkg/controller/direct/privateca/privatecapool_controller.go index 2d8e0e7695..65302a65f4 100644 --- a/pkg/controller/direct/privateca/privatecapool_controller.go +++ b/pkg/controller/direct/privateca/privatecapool_controller.go @@ -19,6 +19,8 @@ import ( "fmt" "strings" + "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/resolverefs" + iampb "cloud.google.com/go/iam/apiv1/iampb" api "cloud.google.com/go/security/privateca/apiv1" pb "cloud.google.com/go/security/privateca/apiv1/privatecapb" @@ -26,7 +28,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" - refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1" krm "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/privateca/v1beta1" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct" @@ -90,7 +91,7 @@ func (m *caPoolModel) AdapterForObject(ctx context.Context, reader client.Reader return nil, fmt.Errorf("cannot resolve location") } - projectRef, err := refs.ResolveProject(ctx, reader, obj, refs.AsProjectRef(&obj.Spec.ProjectRef)) + projectRef, err := resolverefs.ResolveProjectRef(ctx, reader, obj, resolverefs.AsProjectRef(&obj.Spec.ProjectRef)) if err != nil { return nil, err } diff --git a/apis/refs/v1beta1/computenetworkref.go b/pkg/controller/direct/resolverefs/computenetworkref.go similarity index 76% rename from apis/refs/v1beta1/computenetworkref.go rename to pkg/controller/direct/resolverefs/computenetworkref.go index cd4129cebc..3e4d483eda 100644 --- a/apis/refs/v1beta1/computenetworkref.go +++ b/pkg/controller/direct/resolverefs/computenetworkref.go @@ -12,13 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package v1beta1 +package resolverefs import ( "context" "fmt" "strings" + refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" @@ -26,15 +27,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -type ComputeNetworkRef struct { - /* The compute network selflink of form "projects//global/networks/", when not managed by KCC. */ - External string `json:"external,omitempty"` - /* The `name` field of a `ComputeNetwork` resource. */ - Name string `json:"name,omitempty"` - /* The `namespace` field of a `ComputeNetwork` resource. */ - Namespace string `json:"namespace,omitempty"` -} - type ComputeNetwork struct { Project string ComputeNetworkID string @@ -44,7 +36,7 @@ func (c *ComputeNetwork) String() string { return fmt.Sprintf("projects/%s/global/networks/%s", c.Project, c.ComputeNetworkID) } -func ResolveComputeNetwork(ctx context.Context, reader client.Reader, src client.Object, ref *ComputeNetworkRef) (*ComputeNetwork, error) { +func ResolveComputeNetworkRef(ctx context.Context, reader client.Reader, src client.Object, ref *refs.ComputeNetworkRef) (*ComputeNetwork, error) { if ref == nil { return nil, nil } @@ -88,12 +80,9 @@ func ResolveComputeNetwork(ctx context.Context, reader client.Reader, src client return nil, fmt.Errorf("error reading referenced ComputeNetwork %v: %w", key, err) } - computenetworkID, _, err := unstructured.NestedString(computenetwork.Object, "spec", "resourceID") + computenetworkID, err := GetResourceID(computenetwork) if err != nil { - return nil, fmt.Errorf("reading spec.resourceID from ComputeNetwork %v: %w", key, err) - } - if computenetworkID == "" { - computenetworkID = computenetwork.GetName() + return nil, err } computeNetworkProjectID, err := GetProjectID(ctx, reader, computenetwork) diff --git a/pkg/controller/direct/resolverefs/computetargethttpproxyref.go b/pkg/controller/direct/resolverefs/computetargethttpproxyref.go new file mode 100644 index 0000000000..239557e646 --- /dev/null +++ b/pkg/controller/direct/resolverefs/computetargethttpproxyref.go @@ -0,0 +1,114 @@ +// 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 resolverefs + +import ( + "context" + "fmt" + "strings" + + refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1" + 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" +) + +type ComputeTargetHTTPProxy struct { + Project string + Location string + ComputeTargetHTTPProxyID string +} + +func (c *ComputeTargetHTTPProxy) String() string { + if c.Location != "" { + return fmt.Sprintf("projects/%s/location/%s/targetHttpProxies/%s", c.Project, c.Location, c.ComputeTargetHTTPProxyID) + } + return fmt.Sprintf("projects/%s/global/targetHttpProxies/%s", c.Project, c.ComputeTargetHTTPProxyID) +} + +func ResolveTargetHTTPProxyRef(ctx context.Context, reader client.Reader, src client.Object, ref *refs.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], + 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//global/targetHttpProxies/)", 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 := GetProjectID(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 + +} diff --git a/apis/refs/v1beta1/util.go b/pkg/controller/direct/resolverefs/helper.go similarity index 65% rename from apis/refs/v1beta1/util.go rename to pkg/controller/direct/resolverefs/helper.go index 64cdde671c..a874adf7d0 100644 --- a/apis/refs/v1beta1/util.go +++ b/pkg/controller/direct/resolverefs/helper.go @@ -12,57 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -package v1beta1 +package resolverefs 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/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" ) -func ValidateResourceRef(ref *v1alpha1.ResourceRef) error { - if ref == nil { - return nil - } - if ref.Name == "" && ref.External == "" { - return fmt.Errorf("must specify either name or external on reference") - } - if ref.Name != "" && ref.External != "" { - return fmt.Errorf("cannot specify both name and external on reference") - } - return nil -} - -func ParseResourceRef(ctx context.Context, reader client.Reader, src client.Object, ref *v1alpha1.ResourceRef, group, version, kind string) (*unstructured.Unstructured, error) { - key := types.NamespacedName{ - Namespace: ref.Namespace, - Name: ref.Name, - } - if key.Namespace == "" { - key.Namespace = src.GetNamespace() - } - - resource := &unstructured.Unstructured{} - resource.SetGroupVersionKind(schema.GroupVersionKind{ - Group: group, - Version: version, - Kind: kind, - }) - if err := reader.Get(ctx, key, resource); err != nil { - if apierrors.IsNotFound(err) { - return nil, fmt.Errorf("referenced %s %v not found", kind, key) - } - return nil, fmt.Errorf("error reading referenced %s %v: %w", kind, key, err) - } - return resource, nil -} - func GetResourceID(u *unstructured.Unstructured) (string, error) { resourceID, _, err := unstructured.NestedString(u.Object, "spec", "resourceID") if err != nil { @@ -74,14 +34,26 @@ func GetResourceID(u *unstructured.Unstructured) (string, error) { return resourceID, nil } +// todo(yuhou): Location can be optional. Use default location when it's unset. +func GetLocation(obj *unstructured.Unstructured) (string, error) { + 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 +} + func GetProjectID(ctx context.Context, reader client.Reader, obj *unstructured.Unstructured) (string, error) { projectRefExternal, _, _ := unstructured.NestedString(obj.Object, "spec", "projectRef", "external") if projectRefExternal != "" { - projectRef := ProjectRef{ + projectRef := v1beta1.ProjectRef{ External: projectRefExternal, } - project, err := ResolveProject(ctx, reader, obj, &projectRef) + project, err := ResolveProjectRef(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) } @@ -92,7 +64,7 @@ func GetProjectID(ctx context.Context, reader client.Reader, obj *unstructured.U if projectRefName != "" { projectRefNamespace, _, _ := unstructured.NestedString(obj.Object, "spec", "projectRef", "namespace") - projectRef := ProjectRef{ + projectRef := v1beta1.ProjectRef{ Name: projectRefName, Namespace: projectRefNamespace, } @@ -100,7 +72,7 @@ func GetProjectID(ctx context.Context, reader client.Reader, obj *unstructured.U projectRef.Namespace = obj.GetNamespace() } - project, err := ResolveProject(ctx, reader, obj, &projectRef) + project, err := ResolveProjectRef(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) } @@ -113,15 +85,3 @@ func GetProjectID(ctx context.Context, reader client.Reader, obj *unstructured.U return "", fmt.Errorf("cannot find project id for %v %v/%v", obj.GetKind(), obj.GetNamespace(), obj.GetName()) } - -// todo(yuhou): Location can be optional. Use default location when it's unset. -func GetLocation(obj *unstructured.Unstructured) (string, error) { - 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 -} diff --git a/pkg/controller/direct/resolverefs/projectref.go b/pkg/controller/direct/resolverefs/projectref.go new file mode 100644 index 0000000000..4b019d8115 --- /dev/null +++ b/pkg/controller/direct/resolverefs/projectref.go @@ -0,0 +1,109 @@ +// 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 resolverefs + +import ( + "context" + "fmt" + "strings" + + "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1" + + "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" +) + +// AsProjectRef converts a generic ResourceRef into a ProjectRef +func AsProjectRef(in *v1alpha1.ResourceRef) *v1beta1.ProjectRef { + if in == nil { + return nil + } + return &v1beta1.ProjectRef{ + Namespace: in.Namespace, + Name: in.Name, + External: in.External, + Kind: in.Kind, + } +} + +type Project struct { + ProjectID string +} + +// ResolveProjectRef will resolve a ProjectRef to a Project, with the ProjectID. +func ResolveProjectRef(ctx context.Context, reader client.Reader, src client.Object, ref *v1beta1.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/ or )", 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 +}