From 458832768617cb52a0ecf4081a3d27ba6ca3b167 Mon Sep 17 00:00:00 2001 From: Gemma Hou Date: Fri, 23 Aug 2024 20:34:05 +0000 Subject: [PATCH] Support status.ExternalRef --- .../v1beta1/computeforwardingrule_types.go | 10 +- apis/compute/v1beta1/zz_generated.deepcopy.go | 12 +- ...ools.cloudbuild.cnrm.cloud.google.com.yaml | 2 +- pkg/apis/common/v1alpha1/types.go | 2 +- .../v1beta1/cloudbuildworkerpool_types.go | 2 +- .../compute/forwardingrule_controller.go | 170 ++++++++++-------- .../forwardingrule_externalresource.go | 80 +++++++++ 7 files changed, 183 insertions(+), 95 deletions(-) create mode 100644 pkg/controller/direct/compute/forwardingrule_externalresource.go diff --git a/apis/compute/v1beta1/computeforwardingrule_types.go b/apis/compute/v1beta1/computeforwardingrule_types.go index 62dbdb0bbfe..b83c1b1975b 100644 --- a/apis/compute/v1beta1/computeforwardingrule_types.go +++ b/apis/compute/v1beta1/computeforwardingrule_types.go @@ -18,7 +18,7 @@ package v1beta1 import ( refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1" - "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/k8s/v1alpha1" + commonv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/common/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/scheme" @@ -365,9 +365,7 @@ type ComputeForwardingRuleSpec struct { // +kcc:proto=google.cloud.compute.v1.ForwardingRule type ComputeForwardingRuleStatus struct { - /* Conditions represent the latest available observations of the - ComputeForwardingRule's current state. */ - Conditions []v1alpha1.Condition `json:"conditions,omitempty"` + commonv1alpha1.CommonStatus `json:",inline"` /* [Output Only] The URL for the corresponding base Forwarding Rule. By base Forwarding Rule, we mean the Forwarding Rule that has the same IP address, protocol, and port settings with the current Forwarding Rule, but without sourceIPRanges specified. Always empty if the current Forwarding Rule does not have sourceIPRanges specified. */ // +optional BaseForwardingRule *string `json:"baseForwardingRule,omitempty"` @@ -381,10 +379,6 @@ type ComputeForwardingRuleStatus struct { // +optional LabelFingerprint *string `json:"labelFingerprint,omitempty"` - /* ObservedGeneration is the generation of the resource that was most recently observed by the Config Connector controller. If this is equal to metadata.generation, then that means that the current reported status reflects the most recent desired state of the resource. */ - // +optional - ObservedGeneration *int64 `json:"observedGeneration,omitempty"` - /* The PSC connection id of the PSC Forwarding Rule. */ // +optional PscConnectionId *string `json:"pscConnectionId,omitempty"` diff --git a/apis/compute/v1beta1/zz_generated.deepcopy.go b/apis/compute/v1beta1/zz_generated.deepcopy.go index 760846739cf..b26a9c70262 100644 --- a/apis/compute/v1beta1/zz_generated.deepcopy.go +++ b/apis/compute/v1beta1/zz_generated.deepcopy.go @@ -20,7 +20,6 @@ package v1beta1 import ( refsv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1" - v1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/k8s/v1alpha1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -218,11 +217,7 @@ func (in *ComputeForwardingRuleSpec) DeepCopy() *ComputeForwardingRuleSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ComputeForwardingRuleStatus) DeepCopyInto(out *ComputeForwardingRuleStatus) { *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]v1alpha1.Condition, len(*in)) - copy(*out, *in) - } + in.CommonStatus.DeepCopyInto(&out.CommonStatus) if in.BaseForwardingRule != nil { in, out := &in.BaseForwardingRule, &out.BaseForwardingRule *out = new(string) @@ -238,11 +233,6 @@ func (in *ComputeForwardingRuleStatus) DeepCopyInto(out *ComputeForwardingRuleSt *out = new(string) **out = **in } - if in.ObservedGeneration != nil { - in, out := &in.ObservedGeneration, &out.ObservedGeneration - *out = new(int64) - **out = **in - } if in.PscConnectionId != nil { in, out := &in.PscConnectionId, &out.PscConnectionId *out = new(string) diff --git a/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_cloudbuildworkerpools.cloudbuild.cnrm.cloud.google.com.yaml b/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_cloudbuildworkerpools.cloudbuild.cnrm.cloud.google.com.yaml index 74bce5ca9e8..3b08a7a622b 100644 --- a/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_cloudbuildworkerpools.cloudbuild.cnrm.cloud.google.com.yaml +++ b/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_cloudbuildworkerpools.cloudbuild.cnrm.cloud.google.com.yaml @@ -384,7 +384,7 @@ spec: type: object type: array externalRef: - description: A unique specifier for the CloudBuild workerpool resource + description: A unique Config Connector specifier for the resource in GCP. type: string observedGeneration: diff --git a/pkg/apis/common/v1alpha1/types.go b/pkg/apis/common/v1alpha1/types.go index a048ab41624..e363182f9c2 100644 --- a/pkg/apis/common/v1alpha1/types.go +++ b/pkg/apis/common/v1alpha1/types.go @@ -40,7 +40,7 @@ type CommonStatus struct { // +optional ObservedGeneration *int64 `json:"observedGeneration,omitempty"` - /* A unique specifier for the CloudBuild workerpool resource in GCP.*/ + /* A unique Config Connector specifier for the resource in GCP.*/ // +optional ExternalRef *string `json:"externalRef,omitempty"` } diff --git a/pkg/clients/generated/apis/cloudbuild/v1beta1/cloudbuildworkerpool_types.go b/pkg/clients/generated/apis/cloudbuild/v1beta1/cloudbuildworkerpool_types.go index 2bd0b6aafde..a4fc17ca242 100644 --- a/pkg/clients/generated/apis/cloudbuild/v1beta1/cloudbuildworkerpool_types.go +++ b/pkg/clients/generated/apis/cloudbuild/v1beta1/cloudbuildworkerpool_types.go @@ -135,7 +135,7 @@ type CloudBuildWorkerPoolStatus struct { /* Conditions represent the latest available observations of the CloudBuildWorkerPool's current state. */ Conditions []v1alpha1.Condition `json:"conditions,omitempty"` - /* A unique specifier for the CloudBuild workerpool resource in GCP. */ + /* A unique Config Connector specifier for the resource in GCP. */ // +optional ExternalRef *string `json:"externalRef,omitempty"` diff --git a/pkg/controller/direct/compute/forwardingrule_controller.go b/pkg/controller/direct/compute/forwardingrule_controller.go index 55a6a8b8ea4..b34a89302bb 100644 --- a/pkg/controller/direct/compute/forwardingrule_controller.go +++ b/pkg/controller/direct/compute/forwardingrule_controller.go @@ -53,9 +53,7 @@ type forwardingRuleModel struct { var _ directbase.Model = &forwardingRuleModel{} type forwardingRuleAdapter struct { - resourceID string - projectID string - Location string + id *ForwardingRuleIdentity forwardingRulesClient *gcp.ForwardingRulesClient globalForwardingRulesClient *gcp.GlobalForwardingRulesClient desired *krm.ComputeForwardingRule @@ -133,11 +131,35 @@ func (m *forwardingRuleModel) AdapterForObject(ctx context.Context, reader clien obj.Spec.LoadBalancingScheme = direct.LazyPtr("EXTERNAL") } + // Validate ExternalRef + var id *ForwardingRuleIdentity + externalRef := direct.ValueOf(obj.Status.ExternalRef) + if externalRef == "" { + id = BuildID(projectID, location, resourceID) + } else { + id, err = asID(externalRef) + if err != nil { + return nil, err + } + + if id.project != projectID { + return nil, fmt.Errorf("ComputeForwardingRule %s/%s has spec.projectRef changed, expect %s, got %s", + u.GetNamespace(), u.GetName(), id.project, projectID) + } + if id.location != location { + return nil, fmt.Errorf("ComputeForwardingRule %s/%s has spec.location changed, expect %s, got %s", + u.GetNamespace(), u.GetName(), id.location, location) + } + // TODO: need to support more cases + if id.forwardingRule != resourceID { + return nil, fmt.Errorf("ComputeForwardingRule %s/%s has metadata.name or spec.resourceID changed, expect %s, got %s", + u.GetNamespace(), u.GetName(), id.forwardingRule, resourceID) + } + } + forwardingRuleAdapter := &forwardingRuleAdapter{ - resourceID: resourceID, - projectID: projectID, - Location: location, - desired: obj, + id: id, + desired: obj, } // Get GCP client @@ -167,19 +189,19 @@ func (m *forwardingRuleModel) AdapterForURL(ctx context.Context, url string) (di } func (a *forwardingRuleAdapter) Find(ctx context.Context) (bool, error) { - if a.resourceID == "" { + if a.id.forwardingRule == "" { return false, nil } log := klog.FromContext(ctx).WithName(ctrlName) - log.V(2).Info("getting ComputeForwardingRule", "name", a.resourceID) + log.V(2).Info("getting ComputeForwardingRule", "name", a.id.forwardingRule) var err error forwardingRule := &computepb.ForwardingRule{} - if a.Location == "global" { + if a.id.location == "global" { req := &computepb.GetGlobalForwardingRuleRequest{ - ForwardingRule: a.resourceID, - Project: a.projectID, + ForwardingRule: a.id.forwardingRule, + Project: a.id.project, } forwardingRule, err = a.globalForwardingRulesClient.Get(ctx, req) if err != nil { @@ -190,9 +212,9 @@ func (a *forwardingRuleAdapter) Find(ctx context.Context) (bool, error) { } } else { req := &computepb.GetForwardingRuleRequest{ - ForwardingRule: a.resourceID, - Region: a.Location, - Project: a.projectID, + ForwardingRule: a.id.forwardingRule, + Region: a.id.location, + Project: a.id.project, } forwardingRule, err = a.forwardingRulesClient.Get(ctx, req) if err != nil { @@ -207,40 +229,39 @@ func (a *forwardingRuleAdapter) Find(ctx context.Context) (bool, error) { } func (a *forwardingRuleAdapter) Create(ctx context.Context, u *unstructured.Unstructured) error { - if a.projectID == "" { + if a.id.project == "" { return fmt.Errorf("project is empty") } - if a.resourceID == "" { + if a.id.forwardingRule == "" { return fmt.Errorf("resourceID is empty") } log := klog.FromContext(ctx).WithName(ctrlName) - log.V(2).Info("creating ComputeForwardingRule", "name", a.resourceID) + log.V(2).Info("creating ComputeForwardingRule", "name", a.id.forwardingRule) mapCtx := &direct.MapContext{} - a.desired.Labels["managed-by-cnrm"] = "true" desired := a.desired.DeepCopy() forwardingRule := ComputeForwardingRuleSpec_ToProto(mapCtx, &desired.Spec) if mapCtx.Err() != nil { return mapCtx.Err() } - forwardingRule.Name = direct.LazyPtr(a.resourceID) + forwardingRule.Name = direct.LazyPtr(a.id.forwardingRule) forwardingRule.Labels = desired.Labels var err error op := &gcp.Operation{} - if a.Location == "global" { + if a.id.location == "global" { req := &computepb.InsertGlobalForwardingRuleRequest{ ForwardingRuleResource: forwardingRule, - Project: a.projectID, + Project: a.id.project, } op, err = a.globalForwardingRulesClient.Insert(ctx, req) } else { req := &computepb.InsertForwardingRuleRequest{ ForwardingRuleResource: forwardingRule, - Region: a.Location, - Project: a.projectID, + Region: a.id.location, + Project: a.id.project, } op, err = a.forwardingRulesClient.Insert(ctx, req) } @@ -250,17 +271,17 @@ func (a *forwardingRuleAdapter) Create(ctx context.Context, u *unstructured.Unst log.V(2).Info("successfully created ComputeForwardingRule", "name", a.fullyQualifiedName()) // Get the created resource created := &computepb.ForwardingRule{} - if a.Location == "global" { + if a.id.location == "global" { getReq := &computepb.GetGlobalForwardingRuleRequest{ - ForwardingRule: a.resourceID, - Project: a.projectID, + ForwardingRule: a.id.forwardingRule, + Project: a.id.project, } created, err = a.globalForwardingRulesClient.Get(ctx, getReq) } else { getReq := &computepb.GetForwardingRuleRequest{ - ForwardingRule: a.resourceID, - Region: a.Location, - Project: a.projectID, + ForwardingRule: a.id.forwardingRule, + Region: a.id.location, + Project: a.id.project, } created, err = a.forwardingRulesClient.Get(ctx, getReq) } @@ -273,16 +294,17 @@ func (a *forwardingRuleAdapter) Create(ctx context.Context, u *unstructured.Unst CreationTimestamp: op.Proto().InsertTime, SelfLink: created.SelfLink, } + status.ExternalRef = a.id.AsExternalRef() return setStatus(u, status) } func (a *forwardingRuleAdapter) Update(ctx context.Context, u *unstructured.Unstructured) error { - if a.resourceID == "" { + if a.id.forwardingRule == "" { return fmt.Errorf("resourceID is empty") } log := klog.FromContext(ctx).WithName(ctrlName) - log.V(2).Info("updating ComputeForwardingRule", "name", a.resourceID) + log.V(2).Info("updating ComputeForwardingRule", "name", a.id.forwardingRule) mapCtx := &direct.MapContext{} desired := a.desired.DeepCopy() @@ -290,7 +312,7 @@ func (a *forwardingRuleAdapter) Update(ctx context.Context, u *unstructured.Unst if mapCtx.Err() != nil { return mapCtx.Err() } - forwardingRule.Name = direct.LazyPtr(a.resourceID) + forwardingRule.Name = direct.LazyPtr(a.id.forwardingRule) forwardingRule.Labels = desired.Labels // Patch only support update on networkTier field, which KCC does not support yet. @@ -300,19 +322,19 @@ func (a *forwardingRuleAdapter) Update(ctx context.Context, u *unstructured.Unst updated := &computepb.ForwardingRule{} // TODO(yuhou): Checked the realGCP logs, setLabels request is being sent even when there are no updates to labels. // That might because of the generated labelsFingerPrint? - if a.Location == "global" { + if a.id.location == "global" { setLabelsReq := &computepb.SetLabelsGlobalForwardingRuleRequest{ - Resource: a.resourceID, + Resource: a.id.forwardingRule, GlobalSetLabelsRequestResource: &computepb.GlobalSetLabelsRequest{LabelFingerprint: a.actual.LabelFingerprint, Labels: forwardingRule.Labels}, - Project: a.projectID, + Project: a.id.project, } op, err = a.globalForwardingRulesClient.SetLabels(ctx, setLabelsReq) } else { setLabelsReq := &computepb.SetLabelsForwardingRuleRequest{ - Resource: a.resourceID, + Resource: a.id.forwardingRule, RegionSetLabelsRequestResource: &computepb.RegionSetLabelsRequest{LabelFingerprint: a.actual.LabelFingerprint, Labels: forwardingRule.Labels}, - Project: a.projectID, - Region: a.Location, + Project: a.id.project, + Region: a.id.location, } op, err = a.forwardingRulesClient.SetLabels(ctx, setLabelsReq) } @@ -325,39 +347,39 @@ func (a *forwardingRuleAdapter) Update(ctx context.Context, u *unstructured.Unst } log.V(2).Info("successfully updated ComputeForwardingRule labels", "name", a.fullyQualifiedName()) // Get the updated resource - if a.Location == "global" { + if a.id.location == "global" { getReq := &computepb.GetGlobalForwardingRuleRequest{ - ForwardingRule: a.resourceID, - Project: a.projectID, + ForwardingRule: a.id.forwardingRule, + Project: a.id.project, } updated, err = a.globalForwardingRulesClient.Get(ctx, getReq) } else { getReq := &computepb.GetForwardingRuleRequest{ - ForwardingRule: a.resourceID, - Region: a.Location, - Project: a.projectID, + ForwardingRule: a.id.forwardingRule, + Region: a.id.location, + Project: a.id.project, } updated, err = a.forwardingRulesClient.Get(ctx, getReq) } if err != nil { - return fmt.Errorf("getting ComputeForwardingRule %q failed: %w", a.resourceID, err) + return fmt.Errorf("getting ComputeForwardingRule %q failed: %w", a.id.forwardingRule, err) } // setTarget request is sent when there are updates to target. if !reflect.DeepEqual(forwardingRule.Target, a.actual.Target) { - if a.Location == "global" { + if a.id.location == "global" { setTargetReq := &computepb.SetTargetGlobalForwardingRuleRequest{ - ForwardingRule: a.resourceID, + ForwardingRule: a.id.forwardingRule, TargetReferenceResource: &computepb.TargetReference{Target: forwardingRule.Target}, - Project: a.projectID, + Project: a.id.project, } op, err = a.globalForwardingRulesClient.SetTarget(ctx, setTargetReq) } else { setTargetReq := &computepb.SetTargetForwardingRuleRequest{ - ForwardingRule: a.resourceID, + ForwardingRule: a.id.forwardingRule, TargetReferenceResource: &computepb.TargetReference{Target: forwardingRule.Target}, - Project: a.projectID, - Region: a.Location, + Project: a.id.project, + Region: a.id.location, } op, err = a.forwardingRulesClient.SetTarget(ctx, setTargetReq) } @@ -372,22 +394,22 @@ func (a *forwardingRuleAdapter) Update(ctx context.Context, u *unstructured.Unst } // Get the updated resource - if a.Location == "global" { + if a.id.location == "global" { getReq := &computepb.GetGlobalForwardingRuleRequest{ - ForwardingRule: a.resourceID, - Project: a.projectID, + ForwardingRule: a.id.forwardingRule, + Project: a.id.project, } updated, err = a.globalForwardingRulesClient.Get(ctx, getReq) } else { getReq := &computepb.GetForwardingRuleRequest{ - ForwardingRule: a.resourceID, - Region: a.Location, - Project: a.projectID, + ForwardingRule: a.id.forwardingRule, + Region: a.id.location, + Project: a.id.project, } updated, err = a.forwardingRulesClient.Get(ctx, getReq) } if err != nil { - return fmt.Errorf("getting ComputeForwardingRule %q failed: %w", a.resourceID, err) + return fmt.Errorf("getting ComputeForwardingRule %q failed: %w", a.id.forwardingRule, err) } status := &krm.ComputeForwardingRuleStatus{ @@ -405,12 +427,12 @@ func (a *forwardingRuleAdapter) Export(ctx context.Context) (*unstructured.Unstr // Delete implements the Adapter interface. func (a *forwardingRuleAdapter) Delete(ctx context.Context) (bool, error) { - if a.resourceID == "" { + if a.id.forwardingRule == "" { return false, fmt.Errorf("resourceID is empty") } log := klog.FromContext(ctx).WithName(ctrlName) - log.V(2).Info("deleting ComputeForwardingRule", "name", a.resourceID) + log.V(2).Info("deleting ComputeForwardingRule", "name", a.id.forwardingRule) exist, err := a.Find(ctx) if err != nil { @@ -422,36 +444,37 @@ func (a *forwardingRuleAdapter) Delete(ctx context.Context) (bool, error) { } op := &gcp.Operation{} - if a.Location == "global" { + if a.id.location == "global" { req := &computepb.DeleteGlobalForwardingRuleRequest{ - ForwardingRule: a.resourceID, - Project: a.projectID, + ForwardingRule: a.id.forwardingRule, + Project: a.id.project, } op, err = a.globalForwardingRulesClient.Delete(ctx, req) } else { req := &computepb.DeleteForwardingRuleRequest{ - ForwardingRule: a.resourceID, - Region: a.Location, - Project: a.projectID, + ForwardingRule: a.id.forwardingRule, + Region: a.id.location, + Project: a.id.project, } op, err = a.forwardingRulesClient.Delete(ctx, req) } if err != nil { - return false, fmt.Errorf("deleting ComputeForwardingRule %s failed: %w", a.resourceID, err) + return false, fmt.Errorf("deleting ComputeForwardingRule %s failed: %w", a.id.forwardingRule, err) } err = op.Wait(ctx) if err != nil { - return false, fmt.Errorf("waiting ComputeForwardingRule %s delete failed: %w", a.resourceID, err) + return false, fmt.Errorf("waiting ComputeForwardingRule %s delete failed: %w", a.id.forwardingRule, err) } - log.V(2).Info("successfully deleted ComputeForwardingRule", "name", a.resourceID) + log.V(2).Info("successfully deleted ComputeForwardingRule", "name", a.id.forwardingRule) return true, nil } func (a *forwardingRuleAdapter) fullyQualifiedName() string { - if a.Location == "global" { - return fmt.Sprintf("projects/%s/global/forwardingRules/%s", a.projectID, a.resourceID) + if a.id.location == "global" { + return fmt.Sprintf("projects/%s/global/forwardingRules/%s", a.id.project, a.id.forwardingRule) } - return fmt.Sprintf("projects/%s/regions/%s/forwardingRules/%s", a.projectID, a.Location, a.resourceID) + return fmt.Sprintf("projects/%s/regions/%s/forwardingRules/%s", a.id.project, a.id.location, a.id.forwardingRule) + } func setStatus(u *unstructured.Unstructured, typedStatus any) error { @@ -464,6 +487,7 @@ func setStatus(u *unstructured.Unstructured, typedStatus any) error { if old != nil { status["conditions"] = old["conditions"] status["observedGeneration"] = old["observedGeneration"] + status["externalRef"] = old["externalRef"] } u.Object["status"] = status diff --git a/pkg/controller/direct/compute/forwardingrule_externalresource.go b/pkg/controller/direct/compute/forwardingrule_externalresource.go new file mode 100644 index 00000000000..f16f66916c2 --- /dev/null +++ b/pkg/controller/direct/compute/forwardingrule_externalresource.go @@ -0,0 +1,80 @@ +/* +Copyright 2024. + +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 compute + +import ( + "fmt" + "strings" +) + +const ( + serviceDomain = "//compute.googleapis.com" +) + +type ForwardingRuleIdentity struct { + project string + location string + forwardingRule string +} + +// FullyQualifiedName builds a ForwardingRuleIdentity resource +func (c *ForwardingRuleIdentity) FullyQualifiedName() string { + if c.location == "global" { + return fmt.Sprintf("projects/%s/global/forwardingrules/%s", c.project, c.forwardingRule) + } else { + return fmt.Sprintf("projects/%s/locations/%s/forwardingrules/%s", c.project, c.location, c.forwardingRule) + } +} + +// AsExternalRef builds a externalRef from a ForwardingRuleIdentity +func (c *ForwardingRuleIdentity) AsExternalRef() *string { + e := serviceDomain + "/" + c.FullyQualifiedName() + return &e +} + +// asID builds a ForwardingRuleIdentity from a externalRef +func asID(externalRef string) (*ForwardingRuleIdentity, error) { + if !strings.HasPrefix(externalRef, serviceDomain) { + return nil, fmt.Errorf("externalRef should have prefix %s, got %s", serviceDomain, externalRef) + } + path := strings.TrimPrefix(externalRef, serviceDomain+"/") + tokens := strings.Split(path, "/") + if len(tokens) == 5 || tokens[0] == "projects" || tokens[2] == "global" || tokens[3] == "forwardingrules" { + return &ForwardingRuleIdentity{ + project: tokens[1], + location: "global", + forwardingRule: tokens[4], + }, nil + } + if len(tokens) == 6 || tokens[0] == "projects" || tokens[2] == "locations" || tokens[4] == "forwardingrules" { + return &ForwardingRuleIdentity{ + project: tokens[1], + location: tokens[3], + forwardingRule: tokens[5], + }, nil + } + return nil, fmt.Errorf("ExternalRef format invalid: %s", externalRef) +} + +// BuildID builds a ForwardingRuleIdentity from resource components. +func BuildID(project, location, forwardingRule string) *ForwardingRuleIdentity { + return &ForwardingRuleIdentity{ + project: project, + location: location, + forwardingRule: forwardingRule, + } +}