From f5fcfd20246572e00ae8418fcaadf367456d62f5 Mon Sep 17 00:00:00 2001 From: Anh Le Date: Tue, 17 Dec 2024 00:59:35 +0000 Subject: [PATCH 1/9] Spanner Instance generate mapper command --- dev/tools/controllerbuilder/generate.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dev/tools/controllerbuilder/generate.sh b/dev/tools/controllerbuilder/generate.sh index af084ea513..ff7b5329cc 100755 --- a/dev/tools/controllerbuilder/generate.sh +++ b/dev/tools/controllerbuilder/generate.sh @@ -170,9 +170,12 @@ go run . generate-mapper \ # Spanner go run main.go generate-types \ --service google.spanner.admin.instance.v1 \ - --output-api $REPO_ROOT/apis \ --resource SpannerInstance:Instance \ --api-version "spanner.cnrm.cloud.google.com/v1beta1" +go run . generate-mapper \ + --service google.spanner.admin.instance.v1 \ + --api-version "spanner.cnrm.cloud.google.com/v1beta1" \ + # Fix up formatting ${REPO_ROOT}/dev/tasks/fix-gofmt From b42eccf61bb8d33722c5feae3cafa55ef27d4096 Mon Sep 17 00:00:00 2001 From: Anh Le Date: Tue, 17 Dec 2024 01:00:16 +0000 Subject: [PATCH 2/9] Update api to match to proto types --- apis/spanner/v1beta1/instance_reference.go | 4 ++++ apis/spanner/v1beta1/instance_types.go | 4 ++-- apis/spanner/v1beta1/zz_generated.deepcopy.go | 4 ++-- ...nition_spannerinstances.spanner.cnrm.cloud.google.com.yaml | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/apis/spanner/v1beta1/instance_reference.go b/apis/spanner/v1beta1/instance_reference.go index 82f4245a8e..35c6cb6b76 100644 --- a/apis/spanner/v1beta1/instance_reference.go +++ b/apis/spanner/v1beta1/instance_reference.go @@ -168,6 +168,10 @@ func parseSpannerInstanceExternal(external string) (parent *SpannerInstanceParen return parent, resourceID, nil } +func (r *SpannerInstanceRef) SpannerInstanceConfigPrefix() string { + return fmt.Sprintf("projects/%s/instanceConfigs/", r.parent.ProjectID) +} + func valueOf[T any](t *T) T { var zeroVal T if t == nil { diff --git a/apis/spanner/v1beta1/instance_types.go b/apis/spanner/v1beta1/instance_types.go index a39f99208b..501de9686c 100644 --- a/apis/spanner/v1beta1/instance_types.go +++ b/apis/spanner/v1beta1/instance_types.go @@ -40,10 +40,10 @@ type SpannerInstanceSpec struct { DisplayName string `json:"displayName"` // +optional - NumNodes *int64 `json:"numNodes,omitempty"` + NumNodes *int32 `json:"numNodes,omitempty"` // +optional - ProcessingUnits *int64 `json:"processingUnits,omitempty"` + ProcessingUnits *int32 `json:"processingUnits,omitempty"` // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable" // Immutable. diff --git a/apis/spanner/v1beta1/zz_generated.deepcopy.go b/apis/spanner/v1beta1/zz_generated.deepcopy.go index 3069646da6..7c027ee58b 100644 --- a/apis/spanner/v1beta1/zz_generated.deepcopy.go +++ b/apis/spanner/v1beta1/zz_generated.deepcopy.go @@ -418,12 +418,12 @@ func (in *SpannerInstanceSpec) DeepCopyInto(out *SpannerInstanceSpec) { *out = *in if in.NumNodes != nil { in, out := &in.NumNodes, &out.NumNodes - *out = new(int64) + *out = new(int32) **out = **in } if in.ProcessingUnits != nil { in, out := &in.ProcessingUnits, &out.ProcessingUnits - *out = new(int64) + *out = new(int32) **out = **in } if in.ResourceID != nil { diff --git a/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_spannerinstances.spanner.cnrm.cloud.google.com.yaml b/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_spannerinstances.spanner.cnrm.cloud.google.com.yaml index 38a2aefb58..6a6b36c375 100644 --- a/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_spannerinstances.spanner.cnrm.cloud.google.com.yaml +++ b/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_spannerinstances.spanner.cnrm.cloud.google.com.yaml @@ -77,10 +77,10 @@ spec: in length. type: string numNodes: - format: int64 + format: int32 type: integer processingUnits: - format: int64 + format: int32 type: integer resourceID: description: Immutable. The SpannerInstance name. If not given, the From 9811004486e80d38f9db4069ad5c39f4841464eb Mon Sep 17 00:00:00 2001 From: Anh Le Date: Tue, 17 Dec 2024 01:01:11 +0000 Subject: [PATCH 3/9] Spanner Instance Controller --- pkg/controller/direct/register/register.go | 1 + .../direct/spanner/instance_controller.go | 284 ++++++++++++++++++ .../direct/spanner/mapper.generated.go | 230 ++++++++++++++ .../direct/spanner/spannerinstace_mapper.go | 69 +++++ 4 files changed, 584 insertions(+) create mode 100644 pkg/controller/direct/spanner/instance_controller.go create mode 100644 pkg/controller/direct/spanner/mapper.generated.go create mode 100644 pkg/controller/direct/spanner/spannerinstace_mapper.go diff --git a/pkg/controller/direct/register/register.go b/pkg/controller/direct/register/register.go index 64af6b05f2..0551259634 100644 --- a/pkg/controller/direct/register/register.go +++ b/pkg/controller/direct/register/register.go @@ -42,6 +42,7 @@ import ( _ "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/resourcemanager" _ "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/secretmanager" _ "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/securesourcemanager" + _ "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/spanner" _ "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/sql" _ "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/workstations" ) diff --git a/pkg/controller/direct/spanner/instance_controller.go b/pkg/controller/direct/spanner/instance_controller.go new file mode 100644 index 0000000000..1e27c31786 --- /dev/null +++ b/pkg/controller/direct/spanner/instance_controller.go @@ -0,0 +1,284 @@ +// 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 spanner + +import ( + "context" + "fmt" + "reflect" + + krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/spanner/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" + "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry" + + gcp "cloud.google.com/go/spanner/admin/instance/apiv1" + + spannerpb "cloud.google.com/go/spanner/admin/instance/apiv1/instancepb" + "google.golang.org/api/option" + "google.golang.org/protobuf/types/known/fieldmaskpb" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func init() { + registry.RegisterModel(krm.SpannerInstanceGVK, NewSpannerInstanceModel) +} + +func NewSpannerInstanceModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) { + return &modelSpannerInstance{config: *config}, nil +} + +var _ directbase.Model = &modelSpannerInstance{} + +type modelSpannerInstance struct { + config config.ControllerConfig +} + +func (m *modelSpannerInstance) client(ctx context.Context) (*gcp.InstanceAdminClient, error) { + var opts []option.ClientOption + opts, err := m.config.RESTClientOptions() + if err != nil { + return nil, err + } + gcpClient, err := gcp.NewInstanceAdminRESTClient(ctx, opts...) + if err != nil { + return nil, fmt.Errorf("building Instance client: %w", err) + } + return gcpClient, err +} + +func (m *modelSpannerInstance) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) { + obj := &krm.SpannerInstance{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil { + return nil, fmt.Errorf("error converting to %T: %w", obj, err) + } + + id, err := krm.NewSpannerInstanceRef(ctx, reader, obj, u) + if err != nil { + return nil, err + } + + // Get spanner GCP client + gcpClient, err := m.client(ctx) + if err != nil { + return nil, err + } + resourceID := direct.ValueOf(obj.Spec.ResourceID) + if resourceID == "" { + resourceID = obj.GetName() + } + if resourceID == "" { + return nil, fmt.Errorf("cannot resolve resource ID") + } + return &SpannerInstanceAdapter{ + id: id, + gcpClient: gcpClient, + desired: obj, + resourceID: resourceID, + }, nil +} + +func (m *modelSpannerInstance) AdapterForURL(ctx context.Context, url string) (directbase.Adapter, error) { + // TODO: Support URLs + return nil, nil +} + +type SpannerInstanceAdapter struct { + id *krm.SpannerInstanceRef + gcpClient *gcp.InstanceAdminClient + desired *krm.SpannerInstance + actual *spannerpb.Instance + resourceID string +} + +var _ directbase.Adapter = &SpannerInstanceAdapter{} + +func (a *SpannerInstanceAdapter) Find(ctx context.Context) (bool, error) { + log := klog.FromContext(ctx) + log.V(2).Info("getting SpannerInstance", "name", a.id.External) + + req := &spannerpb.GetInstanceRequest{Name: a.id.External} + instancepb, err := a.gcpClient.GetInstance(ctx, req) + if err != nil { + if direct.IsNotFound(err) { + return false, nil + } + return false, fmt.Errorf("getting SpannerInstance %q: %w", a.id.External, err) + } + + a.actual = instancepb + return true, nil +} + +func (a *SpannerInstanceAdapter) Create(ctx context.Context, createOp *directbase.CreateOperation) error { + log := klog.FromContext(ctx) + log.V(2).Info("creating Instance", "name", a.id.External) + mapCtx := &direct.MapContext{} + + desired := a.desired.DeepCopy() + if err := a.SpecValidation(); err != nil { + return err + } + resource := SpannerInstanceSpec_ToProto(mapCtx, &desired.Spec, a.id.SpannerInstanceConfigPrefix()) + // If node count or processing unit is not specify, + // Default NodeCount to 1. + if resource.NodeCount == 0 && resource.ProcessingUnits == 0 { + resource.NodeCount = 1 + } + resource.Name = a.id.External + resource.Labels = desired.Labels + resource.Labels["managed-by-cnrm"] = "true" + resource.Name = a.id.External + if mapCtx.Err() != nil { + return mapCtx.Err() + } + parent, err := a.id.Parent() + if err != nil { + return err + } + + req := &spannerpb.CreateInstanceRequest{ + InstanceId: a.resourceID, + Instance: resource, + Parent: parent.String(), + } + op, err := a.gcpClient.CreateInstance(ctx, req) + if err != nil { + return fmt.Errorf("creating Instance %s: %w", a.id.External, err) + } + created, err := op.Wait(ctx) + if err != nil { + return fmt.Errorf("Instance %s waiting creation: %w", a.id.External, err) + } + log.V(2).Info("successfully created Instance", "name", a.id.External) + + status := &krm.SpannerInstanceStatus{} + status.State = State_FromProto(mapCtx, created) + if mapCtx.Err() != nil { + return mapCtx.Err() + } + status.ExternalRef = &a.id.External + return createOp.UpdateStatus(ctx, status, nil) +} + +func (a *SpannerInstanceAdapter) Update(ctx context.Context, updateOp *directbase.UpdateOperation) error { + log := klog.FromContext(ctx) + log.V(2).Info("updating Instance", "name", a.id.External) + mapCtx := &direct.MapContext{} + if err := a.SpecValidation(); err != nil { + return err + } + desired := a.desired.DeepCopy() + resource := SpannerInstanceSpec_ToProto(mapCtx, &desired.Spec, a.id.SpannerInstanceConfigPrefix()) + resource.Name = a.id.External + resource.Labels = desired.Labels + resource.Labels["managed-by-cnrm"] = "true" + resource.Name = a.id.External + if mapCtx.Err() != nil { + return mapCtx.Err() + } + + updateMask := &fieldmaskpb.FieldMask{} + if !reflect.DeepEqual(a.desired.Spec.DisplayName, a.actual.DisplayName) { + updateMask.Paths = append(updateMask.Paths, "display_name") + } + // If node count is unset, the field become unmanaged. + if a.desired.Spec.NumNodes != nil && !reflect.DeepEqual(a.desired.Spec.NumNodes, a.actual.NodeCount) { + updateMask.Paths = append(updateMask.Paths, "node_count") + } + // If processing unit is unset, the field become unmanaged. + if a.desired.Spec.ProcessingUnits != nil && !reflect.DeepEqual(a.desired.Spec.ProcessingUnits, a.actual.ProcessingUnits) { + updateMask.Paths = append(updateMask.Paths, "processing_units") + } + if !reflect.DeepEqual(a.desired.ObjectMeta.Labels, a.actual.Labels) { + updateMask.Paths = append(updateMask.Paths, "labels") + } + + if len(updateMask.Paths) == 0 { + log.V(2).Info("no field needs update", "name", a.id.External) + return nil + } + resource.Name = a.id.External + req := &spannerpb.UpdateInstanceRequest{ + FieldMask: updateMask, + Instance: resource, + } + op, err := a.gcpClient.UpdateInstance(ctx, req) + if err != nil { + return fmt.Errorf("updating Instance %s: %w", a.id.External, err) + } + updated, err := op.Wait(ctx) + if err != nil { + return fmt.Errorf("Instance %s waiting update: %w", a.id.External, err) + } + log.V(2).Info("successfully updated Instance", "name", a.id.External) + + status := &krm.SpannerInstanceStatus{} + status.State = State_FromProto(mapCtx, updated) + if mapCtx.Err() != nil { + return mapCtx.Err() + } + return updateOp.UpdateStatus(ctx, status, nil) +} + +func (a *SpannerInstanceAdapter) Export(ctx context.Context) (*unstructured.Unstructured, error) { + if a.actual == nil { + return nil, fmt.Errorf("Find() not called") + } + u := &unstructured.Unstructured{} + + obj := &krm.SpannerInstance{} + mapCtx := &direct.MapContext{} + obj.Spec = direct.ValueOf(SpannerInstanceSpec_FromProto(mapCtx, a.actual, a.id.SpannerInstanceConfigPrefix())) + if mapCtx.Err() != nil { + return nil, mapCtx.Err() + } + uObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + if err != nil { + return nil, err + } + + u.SetName(a.actual.Name) + u.SetGroupVersionKind(krm.SpannerInstanceGVK) + + u.Object = uObj + return u, nil +} + +// Delete implements the Adapter interface. +func (a *SpannerInstanceAdapter) Delete(ctx context.Context, deleteOp *directbase.DeleteOperation) (bool, error) { + log := klog.FromContext(ctx) + log.V(2).Info("deleting Instance", "name", a.id.External) + + req := &spannerpb.DeleteInstanceRequest{Name: a.id.External} + err := a.gcpClient.DeleteInstance(ctx, req) + if err != nil { + return false, fmt.Errorf("deleting Instance %s: %w", a.id.External, err) + } + log.V(2).Info("successfully deleted Instance", "name", a.id.External) + return true, nil +} + +func (a *SpannerInstanceAdapter) SpecValidation() error { + if a.desired.Spec.NumNodes != nil && a.desired.Spec.ProcessingUnits != nil { + return fmt.Errorf("Only one field can be set between numNodes and processingUnits.") + } + return nil +} diff --git a/pkg/controller/direct/spanner/mapper.generated.go b/pkg/controller/direct/spanner/mapper.generated.go new file mode 100644 index 0000000000..379d3bcd78 --- /dev/null +++ b/pkg/controller/direct/spanner/mapper.generated.go @@ -0,0 +1,230 @@ +// 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 spanner + +import ( + "strings" + + pb "cloud.google.com/go/spanner/admin/instance/apiv1/instancepb" + krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/spanner/v1beta1" + "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct" +) + +func AutoscalingConfig_FromProto(mapCtx *direct.MapContext, in *pb.AutoscalingConfig) *krm.AutoscalingConfig { + if in == nil { + return nil + } + out := &krm.AutoscalingConfig{} + out.AutoscalingLimits = AutoscalingConfig_AutoscalingLimits_FromProto(mapCtx, in.GetAutoscalingLimits()) + out.AutoscalingTargets = AutoscalingConfig_AutoscalingTargets_FromProto(mapCtx, in.GetAutoscalingTargets()) + out.AsymmetricAutoscalingOptions = direct.Slice_FromProto(mapCtx, in.AsymmetricAutoscalingOptions, AutoscalingConfig_AsymmetricAutoscalingOption_FromProto) + return out +} +func AutoscalingConfig_ToProto(mapCtx *direct.MapContext, in *krm.AutoscalingConfig) *pb.AutoscalingConfig { + if in == nil { + return nil + } + out := &pb.AutoscalingConfig{} + out.AutoscalingLimits = AutoscalingConfig_AutoscalingLimits_ToProto(mapCtx, in.AutoscalingLimits) + out.AutoscalingTargets = AutoscalingConfig_AutoscalingTargets_ToProto(mapCtx, in.AutoscalingTargets) + out.AsymmetricAutoscalingOptions = direct.Slice_ToProto(mapCtx, in.AsymmetricAutoscalingOptions, AutoscalingConfig_AsymmetricAutoscalingOption_ToProto) + return out +} +func AutoscalingConfig_AsymmetricAutoscalingOption_FromProto(mapCtx *direct.MapContext, in *pb.AutoscalingConfig_AsymmetricAutoscalingOption) *krm.AutoscalingConfig_AsymmetricAutoscalingOption { + if in == nil { + return nil + } + out := &krm.AutoscalingConfig_AsymmetricAutoscalingOption{} + out.ReplicaSelection = ReplicaSelection_FromProto(mapCtx, in.GetReplicaSelection()) + out.Overrides = AutoscalingConfig_AsymmetricAutoscalingOption_AutoscalingConfigOverrides_FromProto(mapCtx, in.GetOverrides()) + return out +} +func AutoscalingConfig_AsymmetricAutoscalingOption_ToProto(mapCtx *direct.MapContext, in *krm.AutoscalingConfig_AsymmetricAutoscalingOption) *pb.AutoscalingConfig_AsymmetricAutoscalingOption { + if in == nil { + return nil + } + out := &pb.AutoscalingConfig_AsymmetricAutoscalingOption{} + out.ReplicaSelection = ReplicaSelection_ToProto(mapCtx, in.ReplicaSelection) + out.Overrides = AutoscalingConfig_AsymmetricAutoscalingOption_AutoscalingConfigOverrides_ToProto(mapCtx, in.Overrides) + return out +} +func AutoscalingConfig_AsymmetricAutoscalingOption_AutoscalingConfigOverrides_FromProto(mapCtx *direct.MapContext, in *pb.AutoscalingConfig_AsymmetricAutoscalingOption_AutoscalingConfigOverrides) *krm.AutoscalingConfig_AsymmetricAutoscalingOption_AutoscalingConfigOverrides { + if in == nil { + return nil + } + out := &krm.AutoscalingConfig_AsymmetricAutoscalingOption_AutoscalingConfigOverrides{} + out.AutoscalingLimits = AutoscalingConfig_AutoscalingLimits_FromProto(mapCtx, in.GetAutoscalingLimits()) + out.AutoscalingTargetHighPriorityCpuUtilizationPercent = direct.LazyPtr(in.GetAutoscalingTargetHighPriorityCpuUtilizationPercent()) + return out +} +func AutoscalingConfig_AsymmetricAutoscalingOption_AutoscalingConfigOverrides_ToProto(mapCtx *direct.MapContext, in *krm.AutoscalingConfig_AsymmetricAutoscalingOption_AutoscalingConfigOverrides) *pb.AutoscalingConfig_AsymmetricAutoscalingOption_AutoscalingConfigOverrides { + if in == nil { + return nil + } + out := &pb.AutoscalingConfig_AsymmetricAutoscalingOption_AutoscalingConfigOverrides{} + out.AutoscalingLimits = AutoscalingConfig_AutoscalingLimits_ToProto(mapCtx, in.AutoscalingLimits) + out.AutoscalingTargetHighPriorityCpuUtilizationPercent = direct.ValueOf(in.AutoscalingTargetHighPriorityCpuUtilizationPercent) + return out +} +func AutoscalingConfig_AutoscalingLimits_FromProto(mapCtx *direct.MapContext, in *pb.AutoscalingConfig_AutoscalingLimits) *krm.AutoscalingConfig_AutoscalingLimits { + if in == nil { + return nil + } + out := &krm.AutoscalingConfig_AutoscalingLimits{} + out.MinNodes = direct.LazyPtr(in.GetMinNodes()) + out.MinProcessingUnits = direct.LazyPtr(in.GetMinProcessingUnits()) + out.MaxNodes = direct.LazyPtr(in.GetMaxNodes()) + out.MaxProcessingUnits = direct.LazyPtr(in.GetMaxProcessingUnits()) + return out +} +func AutoscalingConfig_AutoscalingLimits_ToProto(mapCtx *direct.MapContext, in *krm.AutoscalingConfig_AutoscalingLimits) *pb.AutoscalingConfig_AutoscalingLimits { + if in == nil { + return nil + } + out := &pb.AutoscalingConfig_AutoscalingLimits{} + if oneof := AutoscalingConfig_AutoscalingLimits_MinNodes_ToProto(mapCtx, in.MinNodes); oneof != nil { + out.MinLimit = oneof + } + if oneof := AutoscalingConfig_AutoscalingLimits_MinProcessingUnits_ToProto(mapCtx, in.MinProcessingUnits); oneof != nil { + out.MinLimit = oneof + } + if oneof := AutoscalingConfig_AutoscalingLimits_MaxNodes_ToProto(mapCtx, in.MaxNodes); oneof != nil { + out.MaxLimit = oneof + } + if oneof := AutoscalingConfig_AutoscalingLimits_MaxProcessingUnits_ToProto(mapCtx, in.MaxProcessingUnits); oneof != nil { + out.MaxLimit = oneof + } + return out +} +func AutoscalingConfig_AutoscalingTargets_FromProto(mapCtx *direct.MapContext, in *pb.AutoscalingConfig_AutoscalingTargets) *krm.AutoscalingConfig_AutoscalingTargets { + if in == nil { + return nil + } + out := &krm.AutoscalingConfig_AutoscalingTargets{} + out.HighPriorityCpuUtilizationPercent = direct.LazyPtr(in.GetHighPriorityCpuUtilizationPercent()) + out.StorageUtilizationPercent = direct.LazyPtr(in.GetStorageUtilizationPercent()) + return out +} +func AutoscalingConfig_AutoscalingTargets_ToProto(mapCtx *direct.MapContext, in *krm.AutoscalingConfig_AutoscalingTargets) *pb.AutoscalingConfig_AutoscalingTargets { + if in == nil { + return nil + } + out := &pb.AutoscalingConfig_AutoscalingTargets{} + out.HighPriorityCpuUtilizationPercent = direct.ValueOf(in.HighPriorityCpuUtilizationPercent) + out.StorageUtilizationPercent = direct.ValueOf(in.StorageUtilizationPercent) + return out +} +func Instance_FromProto(mapCtx *direct.MapContext, in *pb.Instance, parent *string) *krm.Instance { + if in == nil { + return nil + } + out := &krm.Instance{} + out.Name = direct.LazyPtr(in.GetName()) + out.Config = direct.LazyPtr(in.GetConfig()) + out.DisplayName = direct.LazyPtr(in.GetDisplayName()) + out.NodeCount = direct.LazyPtr(in.GetNodeCount()) + out.ProcessingUnits = direct.LazyPtr(in.GetProcessingUnits()) + out.ReplicaComputeCapacity = direct.Slice_FromProto(mapCtx, in.ReplicaComputeCapacity, ReplicaComputeCapacity_FromProto) + out.AutoscalingConfig = AutoscalingConfig_FromProto(mapCtx, in.GetAutoscalingConfig()) + out.State = State_FromProto(mapCtx, in) + out.Labels = in.Labels + out.EndpointUris = in.EndpointUris + out.CreateTime = direct.StringTimestamp_FromProto(mapCtx, in.GetCreateTime()) + out.UpdateTime = direct.StringTimestamp_FromProto(mapCtx, in.GetUpdateTime()) + out.Edition = direct.Enum_FromProto(mapCtx, in.GetEdition()) + return out +} +func Instance_ToProto(mapCtx *direct.MapContext, in *krm.Instance) *pb.Instance { + if in == nil { + return nil + } + out := &pb.Instance{} + out.Name = direct.ValueOf(in.Name) + out.Config = direct.ValueOf(in.Config) + out.DisplayName = direct.ValueOf(in.DisplayName) + out.NodeCount = direct.ValueOf(in.NodeCount) + out.ProcessingUnits = direct.ValueOf(in.ProcessingUnits) + out.ReplicaComputeCapacity = direct.Slice_ToProto(mapCtx, in.ReplicaComputeCapacity, ReplicaComputeCapacity_ToProto) + out.AutoscalingConfig = AutoscalingConfig_ToProto(mapCtx, in.AutoscalingConfig) + out.State = direct.Enum_ToProto[pb.Instance_State](mapCtx, in.State) + out.Labels = in.Labels + out.EndpointUris = in.EndpointUris + out.CreateTime = direct.StringTimestamp_ToProto(mapCtx, in.CreateTime) + out.UpdateTime = direct.StringTimestamp_ToProto(mapCtx, in.UpdateTime) + out.Edition = direct.Enum_ToProto[pb.Instance_Edition](mapCtx, in.Edition) + return out +} +func ReplicaComputeCapacity_FromProto(mapCtx *direct.MapContext, in *pb.ReplicaComputeCapacity) *krm.ReplicaComputeCapacity { + if in == nil { + return nil + } + out := &krm.ReplicaComputeCapacity{} + out.ReplicaSelection = ReplicaSelection_FromProto(mapCtx, in.GetReplicaSelection()) + out.NodeCount = direct.LazyPtr(in.GetNodeCount()) + out.ProcessingUnits = direct.LazyPtr(in.GetProcessingUnits()) + return out +} +func ReplicaComputeCapacity_ToProto(mapCtx *direct.MapContext, in *krm.ReplicaComputeCapacity) *pb.ReplicaComputeCapacity { + if in == nil { + return nil + } + out := &pb.ReplicaComputeCapacity{} + out.ReplicaSelection = ReplicaSelection_ToProto(mapCtx, in.ReplicaSelection) + if oneof := ReplicaComputeCapacity_NodeCount_ToProto(mapCtx, in.NodeCount); oneof != nil { + out.ComputeCapacity = oneof + } + if oneof := ReplicaComputeCapacity_ProcessingUnits_ToProto(mapCtx, in.ProcessingUnits); oneof != nil { + out.ComputeCapacity = oneof + } + return out +} +func ReplicaSelection_FromProto(mapCtx *direct.MapContext, in *pb.ReplicaSelection) *krm.ReplicaSelection { + if in == nil { + return nil + } + out := &krm.ReplicaSelection{} + out.Location = direct.LazyPtr(in.GetLocation()) + return out +} +func ReplicaSelection_ToProto(mapCtx *direct.MapContext, in *krm.ReplicaSelection) *pb.ReplicaSelection { + if in == nil { + return nil + } + out := &pb.ReplicaSelection{} + out.Location = direct.ValueOf(in.Location) + return out +} +func SpannerInstanceSpec_FromProto(mapCtx *direct.MapContext, in *pb.Instance, configPrefix string) *krm.SpannerInstanceSpec { + if in == nil { + return nil + } + out := &krm.SpannerInstanceSpec{} + out.Config = strings.TrimPrefix(in.GetConfig(), configPrefix) + out.DisplayName = in.GetDisplayName() + out.ProcessingUnits = direct.LazyPtr(in.GetProcessingUnits()) + out.NumNodes = direct.LazyPtr(in.GetNodeCount()) + return out +} +func SpannerInstanceSpec_ToProto(mapCtx *direct.MapContext, in *krm.SpannerInstanceSpec, configPrefix string) *pb.Instance { + if in == nil { + return nil + } + out := &pb.Instance{} + out.Config = configPrefix + in.Config + out.DisplayName = in.DisplayName + out.NodeCount = direct.ValueOf(in.NumNodes) + out.ProcessingUnits = direct.ValueOf(in.ProcessingUnits) + return out +} diff --git a/pkg/controller/direct/spanner/spannerinstace_mapper.go b/pkg/controller/direct/spanner/spannerinstace_mapper.go new file mode 100644 index 0000000000..cf6f53c738 --- /dev/null +++ b/pkg/controller/direct/spanner/spannerinstace_mapper.go @@ -0,0 +1,69 @@ +// 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 spanner + +import ( + pb "cloud.google.com/go/spanner/admin/instance/apiv1/instancepb" + // krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/spanner/v1beta1" + "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct" +) + +func AutoscalingConfig_AutoscalingLimits_MinNodes_ToProto(mapCtx *direct.MapContext, m *int32) *pb.AutoscalingConfig_AutoscalingLimits_MinNodes { + if m == nil { + return nil + } + return &pb.AutoscalingConfig_AutoscalingLimits_MinNodes{ + MinNodes: direct.ValueOf(m), + } +} +func AutoscalingConfig_AutoscalingLimits_MinProcessingUnits_ToProto(mapCtx *direct.MapContext, m *int32) *pb.AutoscalingConfig_AutoscalingLimits_MinProcessingUnits { + if m == nil { + return nil + } + return &pb.AutoscalingConfig_AutoscalingLimits_MinProcessingUnits{ + MinProcessingUnits: direct.ValueOf(m), + } +} +func AutoscalingConfig_AutoscalingLimits_MaxNodes_ToProto(mapCtx *direct.MapContext, m *int32) *pb.AutoscalingConfig_AutoscalingLimits_MaxNodes { + if m == nil { + return nil + } + return &pb.AutoscalingConfig_AutoscalingLimits_MaxNodes{ + MaxNodes: direct.ValueOf(m), + } +} +func AutoscalingConfig_AutoscalingLimits_MaxProcessingUnits_ToProto(mapCtx *direct.MapContext, m *int32) *pb.AutoscalingConfig_AutoscalingLimits_MaxProcessingUnits { + if m == nil { + return nil + } + return &pb.AutoscalingConfig_AutoscalingLimits_MaxProcessingUnits{ + MaxProcessingUnits: direct.ValueOf(m), + } +} +func ReplicaComputeCapacity_NodeCount_ToProto(mapCtx *direct.MapContext, m *int32) *pb.ReplicaComputeCapacity_NodeCount { + if m == nil { + return nil + } + return &pb.ReplicaComputeCapacity_NodeCount{NodeCount: direct.ValueOf(m)} +} +func ReplicaComputeCapacity_ProcessingUnits_ToProto(mapCtx *direct.MapContext, m *int32) *pb.ReplicaComputeCapacity_ProcessingUnits { + if m == nil { + return nil + } + return &pb.ReplicaComputeCapacity_ProcessingUnits{ProcessingUnits: direct.ValueOf(m)} +} +func State_FromProto(mapCtx *direct.MapContext, in *pb.Instance) *string { + return direct.Enum_FromProto(mapCtx, in.GetState()) +} From a397e3855b45faf1a03edd53bbca0d87eb62c429 Mon Sep 17 00:00:00 2001 From: Anh Le Date: Tue, 17 Dec 2024 01:02:27 +0000 Subject: [PATCH 4/9] Add spanner instance direct controller to test runner --- dev/tasks/run-e2e | 2 +- hack/record-gcp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/tasks/run-e2e b/dev/tasks/run-e2e index 8c526afcbc..d6624090bf 100755 --- a/dev/tasks/run-e2e +++ b/dev/tasks/run-e2e @@ -26,7 +26,7 @@ if [[ -z "${KUBEBUILDER_ASSETS:-}" ]]; then fi if [[ -z "${KCC_USE_DIRECT_RECONCILERS:-}" ]]; then - KCC_USE_DIRECT_RECONCILERS=ComputeForwardingRule,GKEHubFeatureMembership + KCC_USE_DIRECT_RECONCILERS=ComputeForwardingRule,GKEHubFeatureMembership,SpannerInstance fi echo "Using direct controllers: $KCC_USE_DIRECT_RECONCILERS" export KCC_USE_DIRECT_RECONCILERS diff --git a/hack/record-gcp b/hack/record-gcp index 87c5889a84..b43cca2c15 100755 --- a/hack/record-gcp +++ b/hack/record-gcp @@ -27,7 +27,7 @@ echo "TEST_BILLING_ACCOUNT_ID=${TEST_BILLING_ACCOUNT_ID}" export TEST_BILLING_ACCOUNT_ID if [[ -z "${KCC_USE_DIRECT_RECONCILERS:-}" ]]; then - KCC_USE_DIRECT_RECONCILERS="ComputeForwardingRule" + KCC_USE_DIRECT_RECONCILERS="ComputeForwardingRule,SpannerInstance" fi export KCC_USE_DIRECT_RECONCILERS echo "KCC_USE_DIRECT_RECONCILERS=$KCC_USE_DIRECT_RECONCILERS" From 4568d49c0dfd0d34ee0071bfdd333ea830128b8a Mon Sep 17 00:00:00 2001 From: Anh Le Date: Tue, 17 Dec 2024 01:02:58 +0000 Subject: [PATCH 5/9] Update test log and golden obj for direct controller --- ...d_object_spannerinstance-basic.golden.yaml | 7 +- .../v1beta1/spannerinstance-basic/_http.log | 67 ++++++++++--------- ...ed_object_spannerinstance-full.golden.yaml | 7 +- .../v1beta1/spannerinstance-full/_http.log | 60 ++++++++++------- ...nerated_object_spannerinstance.golden.yaml | 7 +- .../spanner/v1beta1/spannerinstance/_http.log | 60 ++++++++++------- 6 files changed, 113 insertions(+), 95 deletions(-) diff --git a/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerinstance-basic/_generated_object_spannerinstance-basic.golden.yaml b/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerinstance-basic/_generated_object_spannerinstance-basic.golden.yaml index da8c1f8bc5..ce958bdfe5 100644 --- a/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerinstance-basic/_generated_object_spannerinstance-basic.golden.yaml +++ b/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerinstance-basic/_generated_object_spannerinstance-basic.golden.yaml @@ -4,11 +4,10 @@ metadata: annotations: cnrm.cloud.google.com/management-conflict-prevention-policy: none cnrm.cloud.google.com/project-id: ${projectId} - cnrm.cloud.google.com/state-into-spec: absent finalizers: - cnrm.cloud.google.com/finalizer - cnrm.cloud.google.com/deletion-defender - generation: 3 + generation: 2 labels: cnrm-test: "true" label-one: value-one @@ -18,7 +17,6 @@ metadata: spec: config: regional-us-west1 displayName: New Spanner Instance Sample - resourceID: spannerinstance-sample-${uniqueId} status: conditions: - lastTransitionTime: "1970-01-01T00:00:00Z" @@ -26,5 +24,6 @@ status: reason: UpToDate status: "True" type: Ready - observedGeneration: 3 + externalRef: projects/${projectId}/instances/spannerinstance-sample-${uniqueId} + observedGeneration: 2 state: READY diff --git a/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerinstance-basic/_http.log b/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerinstance-basic/_http.log index 2f3c52123e..82c241dc8d 100644 --- a/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerinstance-basic/_http.log +++ b/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerinstance-basic/_http.log @@ -1,6 +1,7 @@ -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?alt=json +GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-sample-${uniqueId} 404 Not Found Cache-Control: private @@ -22,9 +23,10 @@ X-Xss-Protection: 0 --- -POST https://spanner.googleapis.com/v1/projects/${projectId}/instances?alt=json +POST https://spanner.googleapis.com/v1/projects/${projectId}/instances?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: parent=projects%2F${projectId} { "instance": { @@ -35,9 +37,11 @@ User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 t "label-one": "value-one", "managed-by-cnrm": "true" }, + "name": "projects/${projectId}/instances/spannerinstance-sample-${uniqueId}", "nodeCount": 1 }, - "instanceId": "spannerinstance-sample-${uniqueId}" + "instanceId": "spannerinstance-sample-${uniqueId}", + "parent": "projects/${projectId}" } 200 OK @@ -72,7 +76,7 @@ X-Xss-Protection: 0 } } ], - "state": "READY" + "state": 2 }, "startTime": "2024-04-01T12:34:56.123456Z" }, @@ -81,9 +85,10 @@ X-Xss-Protection: 0 --- -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}/operations/${operationID}?alt=json +GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}/operations/${operationID} Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-sample-${uniqueId}%2Foperations%2F${operationID} 200 OK Cache-Control: private @@ -138,9 +143,10 @@ X-Xss-Protection: 0 --- -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?alt=json +GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-sample-${uniqueId} 200 OK Cache-Control: private @@ -164,19 +170,21 @@ X-Xss-Protection: 0 "name": "projects/${projectId}/instances/spannerinstance-sample-${uniqueId}", "nodeCount": 1, "processingUnits": 1000, - "state": "READY", + "state": 2, "updateTime": "2024-04-01T12:34:56.123456Z" } --- -PATCH https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?alt=json +PATCH https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: instance.name=projects%2F${projectId}%2Finstances%2Fspannerinstance-sample-${uniqueId} { "fieldMask": "displayName,labels", "instance": { + "config": "projects/${projectId}/instanceConfigs/regional-us-west1", "displayName": "New Spanner Instance Sample", "labels": { "cnrm-test": "true", @@ -184,9 +192,7 @@ User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 t "label-two": "value-two", "managed-by-cnrm": "true" }, - "name": "projects/${projectId}/instances/%!s(\u003cnil\u003e)", - "nodeCount": 1, - "processingUnits": 1000 + "name": "projects/${projectId}/instances/spannerinstance-sample-${uniqueId}" } } @@ -213,17 +219,15 @@ X-Xss-Protection: 0 "managed-by-cnrm": "true" }, "name": "projects/${projectId}/instances/spannerinstance-sample-${uniqueId}", - "nodeCount": 1, - "processingUnits": 1000, "replicaComputeCapacity": [ { - "nodeCount": 1, + "nodeCount": 0, "replicaSelection": { "location": "us-west1" } } ], - "state": "READY", + "state": 2, "updateTime": "2024-04-01T12:34:56.123456Z" }, "startTime": "2024-04-01T12:34:56.123456Z" @@ -233,9 +237,10 @@ X-Xss-Protection: 0 --- -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}/operations/${operationID}?alt=json +GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}/operations/${operationID} Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-sample-${uniqueId}%2Foperations%2F${operationID} 200 OK Cache-Control: private @@ -263,8 +268,6 @@ X-Xss-Protection: 0 "managed-by-cnrm": "true" }, "name": "projects/${projectId}/instances/spannerinstance-sample-${uniqueId}", - "nodeCount": 1, - "processingUnits": 1000, "state": "READY", "updateTime": "2024-04-01T12:34:56.123456Z" }, @@ -282,8 +285,6 @@ X-Xss-Protection: 0 "managed-by-cnrm": "true" }, "name": "projects/${projectId}/instances/spannerinstance-sample-${uniqueId}", - "nodeCount": 1, - "processingUnits": 1000, "state": "READY", "updateTime": "2024-04-01T12:34:56.123456Z" } @@ -291,9 +292,10 @@ X-Xss-Protection: 0 --- -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?alt=json +GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-sample-${uniqueId} 200 OK Cache-Control: private @@ -315,17 +317,16 @@ X-Xss-Protection: 0 "managed-by-cnrm": "true" }, "name": "projects/${projectId}/instances/spannerinstance-sample-${uniqueId}", - "nodeCount": 1, - "processingUnits": 1000, - "state": "READY", + "state": 2, "updateTime": "2024-04-01T12:34:56.123456Z" } --- -DELETE https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?alt=json +DELETE https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-sample-${uniqueId} 200 OK Cache-Control: private diff --git a/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerinstance-full/_generated_object_spannerinstance-full.golden.yaml b/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerinstance-full/_generated_object_spannerinstance-full.golden.yaml index 7252a40cf9..d379be9e87 100644 --- a/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerinstance-full/_generated_object_spannerinstance-full.golden.yaml +++ b/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerinstance-full/_generated_object_spannerinstance-full.golden.yaml @@ -4,11 +4,10 @@ metadata: annotations: cnrm.cloud.google.com/management-conflict-prevention-policy: none cnrm.cloud.google.com/project-id: ${projectId} - cnrm.cloud.google.com/state-into-spec: absent finalizers: - cnrm.cloud.google.com/finalizer - cnrm.cloud.google.com/deletion-defender - generation: 3 + generation: 2 labels: cnrm-test: "true" label-one: value-one @@ -18,7 +17,6 @@ spec: config: regional-us-west1 displayName: New spanner Instance Sample numNodes: 3 - resourceID: spannerinstance-sample-${uniqueId} status: conditions: - lastTransitionTime: "1970-01-01T00:00:00Z" @@ -26,5 +24,6 @@ status: reason: UpToDate status: "True" type: Ready - observedGeneration: 3 + externalRef: projects/${projectId}/instances/spannerinstance-sample-${uniqueId} + observedGeneration: 2 state: READY diff --git a/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerinstance-full/_http.log b/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerinstance-full/_http.log index 808e143fee..aabc21e93f 100644 --- a/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerinstance-full/_http.log +++ b/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerinstance-full/_http.log @@ -1,6 +1,7 @@ -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?alt=json +GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-sample-${uniqueId} 404 Not Found Cache-Control: private @@ -22,9 +23,10 @@ X-Xss-Protection: 0 --- -POST https://spanner.googleapis.com/v1/projects/${projectId}/instances?alt=json +POST https://spanner.googleapis.com/v1/projects/${projectId}/instances?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: parent=projects%2F${projectId} { "instance": { @@ -35,9 +37,11 @@ User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 t "label-one": "value-one", "managed-by-cnrm": "true" }, + "name": "projects/${projectId}/instances/spannerinstance-sample-${uniqueId}", "nodeCount": 2 }, - "instanceId": "spannerinstance-sample-${uniqueId}" + "instanceId": "spannerinstance-sample-${uniqueId}", + "parent": "projects/${projectId}" } 200 OK @@ -72,7 +76,7 @@ X-Xss-Protection: 0 } } ], - "state": "READY" + "state": 2 }, "startTime": "2024-04-01T12:34:56.123456Z" }, @@ -81,9 +85,10 @@ X-Xss-Protection: 0 --- -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}/operations/${operationID}?alt=json +GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}/operations/${operationID} Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-sample-${uniqueId}%2Foperations%2F${operationID} 200 OK Cache-Control: private @@ -138,9 +143,10 @@ X-Xss-Protection: 0 --- -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?alt=json +GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-sample-${uniqueId} 200 OK Cache-Control: private @@ -164,28 +170,29 @@ X-Xss-Protection: 0 "name": "projects/${projectId}/instances/spannerinstance-sample-${uniqueId}", "nodeCount": 2, "processingUnits": 2000, - "state": "READY", + "state": 2, "updateTime": "2024-04-01T12:34:56.123456Z" } --- -PATCH https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?alt=json +PATCH https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: instance.name=projects%2F${projectId}%2Finstances%2Fspannerinstance-sample-${uniqueId} { - "fieldMask": "nodeCount,displayName", + "fieldMask": "displayName,nodeCount,labels", "instance": { + "config": "projects/${projectId}/instanceConfigs/regional-us-west1", "displayName": "New spanner Instance Sample", "labels": { "cnrm-test": "true", "label-one": "value-one", "managed-by-cnrm": "true" }, - "name": "projects/${projectId}/instances/%!s(\u003cnil\u003e)", - "nodeCount": 3, - "processingUnits": 2000 + "name": "projects/${projectId}/instances/spannerinstance-sample-${uniqueId}", + "nodeCount": 3 } } @@ -222,7 +229,7 @@ X-Xss-Protection: 0 } } ], - "state": "READY", + "state": 2, "updateTime": "2024-04-01T12:34:56.123456Z" }, "startTime": "2024-04-01T12:34:56.123456Z" @@ -232,9 +239,10 @@ X-Xss-Protection: 0 --- -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}/operations/${operationID}?alt=json +GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}/operations/${operationID} Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-sample-${uniqueId}%2Foperations%2F${operationID} 200 OK Cache-Control: private @@ -290,9 +298,10 @@ X-Xss-Protection: 0 --- -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?alt=json +GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-sample-${uniqueId} 200 OK Cache-Control: private @@ -316,15 +325,16 @@ X-Xss-Protection: 0 "name": "projects/${projectId}/instances/spannerinstance-sample-${uniqueId}", "nodeCount": 3, "processingUnits": 3000, - "state": "READY", + "state": 2, "updateTime": "2024-04-01T12:34:56.123456Z" } --- -DELETE https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?alt=json +DELETE https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-sample-${uniqueId} 200 OK Cache-Control: private diff --git a/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerinstance/_generated_object_spannerinstance.golden.yaml b/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerinstance/_generated_object_spannerinstance.golden.yaml index 556ad3e9a2..ede313249b 100644 --- a/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerinstance/_generated_object_spannerinstance.golden.yaml +++ b/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerinstance/_generated_object_spannerinstance.golden.yaml @@ -4,11 +4,10 @@ metadata: annotations: cnrm.cloud.google.com/management-conflict-prevention-policy: none cnrm.cloud.google.com/project-id: ${projectId} - cnrm.cloud.google.com/state-into-spec: absent finalizers: - cnrm.cloud.google.com/finalizer - cnrm.cloud.google.com/deletion-defender - generation: 3 + generation: 2 labels: cnrm-test: "true" label-one: value-one @@ -18,7 +17,6 @@ spec: config: regional-us-west1 displayName: Spanner Instance Sample numNodes: 3 - resourceID: spannerinstance-sample-${uniqueId} status: conditions: - lastTransitionTime: "1970-01-01T00:00:00Z" @@ -26,5 +24,6 @@ status: reason: UpToDate status: "True" type: Ready - observedGeneration: 3 + externalRef: projects/${projectId}/instances/spannerinstance-sample-${uniqueId} + observedGeneration: 2 state: READY diff --git a/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerinstance/_http.log b/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerinstance/_http.log index 04de9d676b..57e308c02d 100644 --- a/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerinstance/_http.log +++ b/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerinstance/_http.log @@ -1,6 +1,7 @@ -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?alt=json +GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-sample-${uniqueId} 404 Not Found Cache-Control: private @@ -22,9 +23,10 @@ X-Xss-Protection: 0 --- -POST https://spanner.googleapis.com/v1/projects/${projectId}/instances?alt=json +POST https://spanner.googleapis.com/v1/projects/${projectId}/instances?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: parent=projects%2F${projectId} { "instance": { @@ -35,9 +37,11 @@ User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 t "label-one": "value-one", "managed-by-cnrm": "true" }, + "name": "projects/${projectId}/instances/spannerinstance-sample-${uniqueId}", "nodeCount": 2 }, - "instanceId": "spannerinstance-sample-${uniqueId}" + "instanceId": "spannerinstance-sample-${uniqueId}", + "parent": "projects/${projectId}" } 200 OK @@ -72,7 +76,7 @@ X-Xss-Protection: 0 } } ], - "state": "READY" + "state": 2 }, "startTime": "2024-04-01T12:34:56.123456Z" }, @@ -81,9 +85,10 @@ X-Xss-Protection: 0 --- -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}/operations/${operationID}?alt=json +GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}/operations/${operationID} Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-sample-${uniqueId}%2Foperations%2F${operationID} 200 OK Cache-Control: private @@ -138,9 +143,10 @@ X-Xss-Protection: 0 --- -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?alt=json +GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-sample-${uniqueId} 200 OK Cache-Control: private @@ -164,28 +170,29 @@ X-Xss-Protection: 0 "name": "projects/${projectId}/instances/spannerinstance-sample-${uniqueId}", "nodeCount": 2, "processingUnits": 2000, - "state": "READY", + "state": 2, "updateTime": "2024-04-01T12:34:56.123456Z" } --- -PATCH https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?alt=json +PATCH https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: instance.name=projects%2F${projectId}%2Finstances%2Fspannerinstance-sample-${uniqueId} { - "fieldMask": "nodeCount", + "fieldMask": "nodeCount,labels", "instance": { + "config": "projects/${projectId}/instanceConfigs/regional-us-west1", "displayName": "Spanner Instance Sample", "labels": { "cnrm-test": "true", "label-one": "value-one", "managed-by-cnrm": "true" }, - "name": "projects/${projectId}/instances/%!s(\u003cnil\u003e)", - "nodeCount": 3, - "processingUnits": 2000 + "name": "projects/${projectId}/instances/spannerinstance-sample-${uniqueId}", + "nodeCount": 3 } } @@ -222,7 +229,7 @@ X-Xss-Protection: 0 } } ], - "state": "READY", + "state": 2, "updateTime": "2024-04-01T12:34:56.123456Z" }, "startTime": "2024-04-01T12:34:56.123456Z" @@ -232,9 +239,10 @@ X-Xss-Protection: 0 --- -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}/operations/${operationID}?alt=json +GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}/operations/${operationID} Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-sample-${uniqueId}%2Foperations%2F${operationID} 200 OK Cache-Control: private @@ -290,9 +298,10 @@ X-Xss-Protection: 0 --- -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?alt=json +GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-sample-${uniqueId} 200 OK Cache-Control: private @@ -316,15 +325,16 @@ X-Xss-Protection: 0 "name": "projects/${projectId}/instances/spannerinstance-sample-${uniqueId}", "nodeCount": 3, "processingUnits": 3000, - "state": "READY", + "state": 2, "updateTime": "2024-04-01T12:34:56.123456Z" } --- -DELETE https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?alt=json +DELETE https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-sample-${uniqueId}?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-sample-${uniqueId} 200 OK Cache-Control: private From 97ecdc61f026a42315cf6f28587c5ef049860e19 Mon Sep 17 00:00:00 2001 From: Anh Le Date: Tue, 17 Dec 2024 01:18:20 +0000 Subject: [PATCH 6/9] make ready-pr --- go.mod | 1 + go.sum | 2 ++ 2 files changed, 3 insertions(+) diff --git a/go.mod b/go.mod index b9c8eec6d9..dde617db5b 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( cloud.google.com/go/secretmanager v1.14.2 cloud.google.com/go/securesourcemanager v1.1.1 cloud.google.com/go/security v1.18.2 + cloud.google.com/go/spanner v1.73.0 cloud.google.com/go/workstations v1.1.1 contrib.go.opencensus.io/exporter/prometheus v0.1.0 github.com/GoogleCloudPlatform/declarative-resource-client-library v1.62.0 diff --git a/go.sum b/go.sum index 9fc6d299cf..f65037cd37 100644 --- a/go.sum +++ b/go.sum @@ -87,6 +87,8 @@ cloud.google.com/go/securesourcemanager v1.1.1 h1:raOxRsR507ALLlc5yG79LK7O2TuDbD cloud.google.com/go/securesourcemanager v1.1.1/go.mod h1:qMVTY4bzBRhcrUe0B8KvDCK5mhGQD6PyrHbzqOn3TLQ= cloud.google.com/go/security v1.18.2 h1:9Nzp9LGjiDvHqy7X7Q9GrS5lIHN0bI8RvDjkrl4ILO0= cloud.google.com/go/security v1.18.2/go.mod h1:3EwTcYw8554iEtgK8VxAjZaq2unFehcsgFIF9nOvQmU= +cloud.google.com/go/spanner v1.73.0 h1:0bab8QDn6MNj9lNK6XyGAVFhMlhMU2waePPa6GZNoi8= +cloud.google.com/go/spanner v1.73.0/go.mod h1:mw98ua5ggQXVWwp83yjwggqEmW9t8rjs9Po1ohcUGW4= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= From bd593a6db821d92c53d460a7785e622114ea7651 Mon Sep 17 00:00:00 2001 From: Anh Le Date: Tue, 17 Dec 2024 18:50:03 +0000 Subject: [PATCH 7/9] Update dependent tests --- apis/spanner/v1beta1/instance_types.go | 10 +++ .../cloudspannerconnectionbasic/_http.log | 64 ++++++------------- .../spanner/v1beta1/spannerdatabase/_http.log | 64 ++++++------------- .../externalwithsubresource/_http.log | 64 ++++++------------- 4 files changed, 70 insertions(+), 132 deletions(-) diff --git a/apis/spanner/v1beta1/instance_types.go b/apis/spanner/v1beta1/instance_types.go index 501de9686c..f719b8d00a 100644 --- a/apis/spanner/v1beta1/instance_types.go +++ b/apis/spanner/v1beta1/instance_types.go @@ -66,10 +66,20 @@ type SpannerInstanceStatus struct { /* Instance status: 'CREATING' or 'READY'. */ // +optional State *string `json:"state,omitempty"` + + /* ObservedState is the state of the resource as most recently observed in GCP. */ + // +optional + ObservedState *SpannerInstanceObservedState `json:"observedState,omitempty"` } // SpannerInstanceObservedState is the state of the SpannerInstance resource as most recently observed in GCP. type SpannerInstanceObservedState struct { + /* NOTYET + // NumNodes and ProcessUnits is output fields with AutoScaler is set. + NumNodes *int32 `json:"numNodes,omitempty"` + + ProcessingUnits *int32 `json:"processingUnits,omitempty"` + */ } // +genclient diff --git a/pkg/test/resourcefixture/testdata/basic/bigqueryconnection/v1beta1/bigqueryconnectionconnection/cloudspannerconnectionbasic/_http.log b/pkg/test/resourcefixture/testdata/basic/bigqueryconnection/v1beta1/bigqueryconnectionconnection/cloudspannerconnectionbasic/_http.log index 25ff831a9a..ac6b11c03c 100644 --- a/pkg/test/resourcefixture/testdata/basic/bigqueryconnection/v1beta1/bigqueryconnectionconnection/cloudspannerconnectionbasic/_http.log +++ b/pkg/test/resourcefixture/testdata/basic/bigqueryconnection/v1beta1/bigqueryconnectionconnection/cloudspannerconnectionbasic/_http.log @@ -1,6 +1,7 @@ -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}?alt=json +GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-${uniqueId} 404 Not Found Cache-Control: private @@ -22,9 +23,10 @@ X-Xss-Protection: 0 --- -POST https://spanner.googleapis.com/v1/projects/${projectId}/instances?alt=json +POST https://spanner.googleapis.com/v1/projects/${projectId}/instances?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: parent=projects%2F${projectId} { "instance": { @@ -34,9 +36,11 @@ User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 t "cnrm-test": "true", "managed-by-cnrm": "true" }, + "name": "projects/${projectId}/instances/spannerinstance-${uniqueId}", "nodeCount": 1 }, - "instanceId": "spannerinstance-${uniqueId}" + "instanceId": "spannerinstance-${uniqueId}", + "parent": "projects/${projectId}" } 200 OK @@ -70,7 +74,7 @@ X-Xss-Protection: 0 } } ], - "state": "READY" + "state": 2 }, "startTime": "2024-04-01T12:34:56.123456Z" }, @@ -79,9 +83,10 @@ X-Xss-Protection: 0 --- -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}/operations/${operationID}?alt=json +GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}/operations/${operationID} Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-${uniqueId}%2Foperations%2F${operationID} 200 OK Cache-Control: private @@ -134,37 +139,6 @@ X-Xss-Protection: 0 --- -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}?alt=json -Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager - -200 OK -Cache-Control: private -Content-Type: application/json; charset=UTF-8 -Server: ESF -Vary: Origin -Vary: X-Origin -Vary: Referer -X-Frame-Options: SAMEORIGIN -X-Xss-Protection: 0 - -{ - "config": "projects/${projectId}/instanceConfigs/regional-us-west1", - "createTime": "2024-04-01T12:34:56.123456Z", - "displayName": "Spanner Database Dependency", - "labels": { - "cnrm-test": "true", - "managed-by-cnrm": "true" - }, - "name": "projects/${projectId}/instances/spannerinstance-${uniqueId}", - "nodeCount": 1, - "processingUnits": 1000, - "state": "READY", - "updateTime": "2024-04-01T12:34:56.123456Z" -} - ---- - GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}/databases/spannerdb-${uniqueId}?alt=json Content-Type: application/json User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager @@ -550,9 +524,10 @@ X-Xss-Protection: 0 --- -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}?alt=json +GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-${uniqueId} 200 OK Cache-Control: private @@ -575,15 +550,16 @@ X-Xss-Protection: 0 "name": "projects/${projectId}/instances/spannerinstance-${uniqueId}", "nodeCount": 1, "processingUnits": 1000, - "state": "READY", + "state": 2, "updateTime": "2024-04-01T12:34:56.123456Z" } --- -DELETE https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}?alt=json +DELETE https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-${uniqueId} 200 OK Cache-Control: private diff --git a/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerdatabase/_http.log b/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerdatabase/_http.log index 73344bab66..0d87f843df 100644 --- a/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerdatabase/_http.log +++ b/pkg/test/resourcefixture/testdata/basic/spanner/v1beta1/spannerdatabase/_http.log @@ -1,6 +1,7 @@ -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}?alt=json +GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-${uniqueId} 404 Not Found Cache-Control: private @@ -22,9 +23,10 @@ X-Xss-Protection: 0 --- -POST https://spanner.googleapis.com/v1/projects/${projectId}/instances?alt=json +POST https://spanner.googleapis.com/v1/projects/${projectId}/instances?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: parent=projects%2F${projectId} { "instance": { @@ -34,9 +36,11 @@ User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 t "cnrm-test": "true", "managed-by-cnrm": "true" }, + "name": "projects/${projectId}/instances/spannerinstance-${uniqueId}", "nodeCount": 1 }, - "instanceId": "spannerinstance-${uniqueId}" + "instanceId": "spannerinstance-${uniqueId}", + "parent": "projects/${projectId}" } 200 OK @@ -70,7 +74,7 @@ X-Xss-Protection: 0 } } ], - "state": "READY" + "state": 2 }, "startTime": "2024-04-01T12:34:56.123456Z" }, @@ -79,9 +83,10 @@ X-Xss-Protection: 0 --- -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}/operations/${operationID}?alt=json +GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}/operations/${operationID} Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-${uniqueId}%2Foperations%2F${operationID} 200 OK Cache-Control: private @@ -134,37 +139,6 @@ X-Xss-Protection: 0 --- -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}?alt=json -Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager - -200 OK -Cache-Control: private -Content-Type: application/json; charset=UTF-8 -Server: ESF -Vary: Origin -Vary: X-Origin -Vary: Referer -X-Frame-Options: SAMEORIGIN -X-Xss-Protection: 0 - -{ - "config": "projects/${projectId}/instanceConfigs/regional-us-west1", - "createTime": "2024-04-01T12:34:56.123456Z", - "displayName": "Spanner Database Dependency", - "labels": { - "cnrm-test": "true", - "managed-by-cnrm": "true" - }, - "name": "projects/${projectId}/instances/spannerinstance-${uniqueId}", - "nodeCount": 1, - "processingUnits": 1000, - "state": "READY", - "updateTime": "2024-04-01T12:34:56.123456Z" -} - ---- - GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}/databases/spannerdb-${uniqueId}?alt=json Content-Type: application/json User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager @@ -361,9 +335,10 @@ X-Xss-Protection: 0 --- -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}?alt=json +GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-${uniqueId} 200 OK Cache-Control: private @@ -386,15 +361,16 @@ X-Xss-Protection: 0 "name": "projects/${projectId}/instances/spannerinstance-${uniqueId}", "nodeCount": 1, "processingUnits": 1000, - "state": "READY", + "state": 2, "updateTime": "2024-04-01T12:34:56.123456Z" } --- -DELETE https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}?alt=json +DELETE https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-${uniqueId} 200 OK Cache-Control: private diff --git a/pkg/test/resourcefixture/testdata/externalref/externalwithsubresource/_http.log b/pkg/test/resourcefixture/testdata/externalref/externalwithsubresource/_http.log index 43f0a92fe7..3f15e4ef7b 100644 --- a/pkg/test/resourcefixture/testdata/externalref/externalwithsubresource/_http.log +++ b/pkg/test/resourcefixture/testdata/externalref/externalwithsubresource/_http.log @@ -1,6 +1,7 @@ -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}?alt=json +GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-${uniqueId} 404 Not Found Cache-Control: private @@ -22,9 +23,10 @@ X-Xss-Protection: 0 --- -POST https://spanner.googleapis.com/v1/projects/${projectId}/instances?alt=json +POST https://spanner.googleapis.com/v1/projects/${projectId}/instances?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: parent=projects%2F${projectId} { "instance": { @@ -34,9 +36,11 @@ User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 t "cnrm-test": "true", "managed-by-cnrm": "true" }, + "name": "projects/${projectId}/instances/spannerinstance-${uniqueId}", "nodeCount": 1 }, - "instanceId": "spannerinstance-${uniqueId}" + "instanceId": "spannerinstance-${uniqueId}", + "parent": "projects/${projectId}" } 200 OK @@ -70,7 +74,7 @@ X-Xss-Protection: 0 } } ], - "state": "READY" + "state": 2 }, "startTime": "2024-04-01T12:34:56.123456Z" }, @@ -79,9 +83,10 @@ X-Xss-Protection: 0 --- -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}/operations/${operationID}?alt=json +GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}/operations/${operationID} Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-${uniqueId}%2Foperations%2F${operationID} 200 OK Cache-Control: private @@ -134,37 +139,6 @@ X-Xss-Protection: 0 --- -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}?alt=json -Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager - -200 OK -Cache-Control: private -Content-Type: application/json; charset=UTF-8 -Server: ESF -Vary: Origin -Vary: X-Origin -Vary: Referer -X-Frame-Options: SAMEORIGIN -X-Xss-Protection: 0 - -{ - "config": "projects/${projectId}/instanceConfigs/regional-us-west1", - "createTime": "2024-04-01T12:34:56.123456Z", - "displayName": "Spanner Database Dependency", - "labels": { - "cnrm-test": "true", - "managed-by-cnrm": "true" - }, - "name": "projects/${projectId}/instances/spannerinstance-${uniqueId}", - "nodeCount": 1, - "processingUnits": 1000, - "state": "READY", - "updateTime": "2024-04-01T12:34:56.123456Z" -} - ---- - GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}/databases/spannerdatabase-test?alt=json Content-Type: application/json User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager @@ -361,9 +335,10 @@ X-Xss-Protection: 0 --- -GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}?alt=json +GET https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-${uniqueId} 200 OK Cache-Control: private @@ -386,15 +361,16 @@ X-Xss-Protection: 0 "name": "projects/${projectId}/instances/spannerinstance-${uniqueId}", "nodeCount": 1, "processingUnits": 1000, - "state": "READY", + "state": 2, "updateTime": "2024-04-01T12:34:56.123456Z" } --- -DELETE https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}?alt=json +DELETE https://spanner.googleapis.com/v1/projects/${projectId}/instances/spannerinstance-${uniqueId}?%24alt=json%3Benum-encoding%3Dint Content-Type: application/json -User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Finstances%2Fspannerinstance-${uniqueId} 200 OK Cache-Control: private From c0f4723fe3874fe97e42c8fb7fcba808ec8d6ffb Mon Sep 17 00:00:00 2001 From: Anh Le Date: Thu, 19 Dec 2024 00:58:21 +0000 Subject: [PATCH 8/9] Address comment and split ref and identity --- apis/spanner/v1beta1/instance_identity.go | 90 ++++++++++++++ apis/spanner/v1beta1/instance_reference.go | 111 ++++-------------- apis/spanner/v1beta1/zz_generated.deepcopy.go | 30 ++++- ...stances.spanner.cnrm.cloud.google.com.yaml | 4 + .../direct/spanner/instance_controller.go | 83 +++++++------ .../resource-docs/spanner/spannerinstance.md | 8 ++ 6 files changed, 187 insertions(+), 139 deletions(-) create mode 100644 apis/spanner/v1beta1/instance_identity.go diff --git a/apis/spanner/v1beta1/instance_identity.go b/apis/spanner/v1beta1/instance_identity.go new file mode 100644 index 0000000000..98d48df32c --- /dev/null +++ b/apis/spanner/v1beta1/instance_identity.go @@ -0,0 +1,90 @@ +// 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" + + "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 SpannerInstanceIdentity struct { + id string + parent *SpannerInstanceParent +} + +func (i *SpannerInstanceIdentity) String() string { + return i.parent.String() + "/instances/" + i.id +} + +func (r *SpannerInstanceIdentity) Parent() *SpannerInstanceParent { + return r.parent +} + +func (r *SpannerInstanceIdentity) ID() string { + return r.id +} + +type SpannerInstanceParent struct { + ProjectID string +} + +func (p *SpannerInstanceParent) String() string { + return "projects/" + p.ProjectID +} + +func NewSpannerInstanceIdentity(ctx context.Context, reader client.Reader, obj *SpannerInstance, u *unstructured.Unstructured) (*SpannerInstanceIdentity, error) { + // Get Parent + projectID, err := refsv1beta1.ResolveProjectID(ctx, reader, u) + if err != nil { + return nil, err + } + // 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 := ParseSpannerInstanceExternal(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 &SpannerInstanceIdentity{ + parent: &SpannerInstanceParent{ProjectID: projectID}, + id: resourceID, + }, nil +} + +func (r *SpannerInstanceIdentity) SpannerInstanceConfigPrefix() string { + return fmt.Sprintf("projects/%s/instanceConfigs/", r.parent.ProjectID) +} diff --git a/apis/spanner/v1beta1/instance_reference.go b/apis/spanner/v1beta1/instance_reference.go index 35c6cb6b76..f7b84b4344 100644 --- a/apis/spanner/v1beta1/instance_reference.go +++ b/apis/spanner/v1beta1/instance_reference.go @@ -16,6 +16,7 @@ package v1beta1 import ( "context" + "errors" "fmt" "strings" @@ -41,8 +42,6 @@ type SpannerInstanceRef struct { // The namespace of a SpannerInstance resource. Namespace string `json:"namespace,omitempty"` - - parent *SpannerInstanceParent } // NormalizedExternal provision the "External" value for other resource that depends on SpannerInstance. @@ -54,7 +53,7 @@ func (r *SpannerInstanceRef) NormalizedExternal(ctx context.Context, reader clie } // From given External if r.External != "" { - if _, _, err := parseSpannerInstanceExternal(r.External); err != nil { + if _, err := ParseSpannerInstanceExternal(r.External); err != nil { return "", err } return r.External, nil @@ -74,9 +73,15 @@ func (r *SpannerInstanceRef) NormalizedExternal(ctx context.Context, reader clie return "", fmt.Errorf("reading referenced %s %s: %w", SpannerInstanceGVK, 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("SecretManagerSecret `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) @@ -85,97 +90,21 @@ func (r *SpannerInstanceRef) NormalizedExternal(ctx context.Context, reader clie return r.External, nil } -// New builds a SpannerInstanceRef from the Config Connector SpannerInstance object. -func NewSpannerInstanceRef(ctx context.Context, reader client.Reader, obj *SpannerInstance, u *unstructured.Unstructured) (*SpannerInstanceRef, error) { - id := &SpannerInstanceRef{} - - projectID, err := refsv1beta1.ResolveProjectID(ctx, reader, u) - if err != nil { - return nil, err - } - - id.parent = &SpannerInstanceParent{ProjectID: projectID} - - // 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 = asSpannerInstanceExternal(id.parent, resourceID) - return id, nil - } - - // Validate desired with actual - actualParent, actualResourceID, err := parseSpannerInstanceExternal(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 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 = &SpannerInstanceParent{ProjectID: projectID} - return id, nil -} - -func (r *SpannerInstanceRef) Parent() (*SpannerInstanceParent, error) { - if r.parent != nil { - return r.parent, nil - } - if r.External != "" { - parent, _, err := parseSpannerInstanceExternal(r.External) - if err != nil { - return nil, err - } - return parent, nil - } - return nil, fmt.Errorf("SpannerInstanceRef not initialized from `NewSpannerInstanceRef` or `NormalizedExternal`") -} - -type SpannerInstanceParent struct { - ProjectID string -} - -func (p *SpannerInstanceParent) String() string { - return "projects/" + p.ProjectID -} - func asSpannerInstanceExternal(parent *SpannerInstanceParent, resourceID string) (external string) { return parent.String() + "/instances/" + resourceID } -func parseSpannerInstanceExternal(external string) (parent *SpannerInstanceParent, resourceID string, err error) { +func ParseSpannerInstanceExternal(external string) (*SpannerInstanceIdentity, error) { + if external == "" { + return nil, fmt.Errorf("missing external value") + } external = strings.TrimPrefix(external, "/") tokens := strings.Split(external, "/") if len(tokens) != 4 || tokens[0] != "projects" || tokens[2] != "instances" { - return nil, "", fmt.Errorf("format of SpannerInstance external=%q was not known (use projects/{{projectId}}/instances/{{instanceID}})", external) - } - parent = &SpannerInstanceParent{ - ProjectID: tokens[1], - } - resourceID = tokens[3] - return parent, resourceID, nil -} - -func (r *SpannerInstanceRef) SpannerInstanceConfigPrefix() string { - return fmt.Sprintf("projects/%s/instanceConfigs/", r.parent.ProjectID) -} - -func valueOf[T any](t *T) T { - var zeroVal T - if t == nil { - return zeroVal + return nil, fmt.Errorf("format of SpannerInstance external=%q was not known (use projects/{{projectId}}/instances/{{instanceID}})", external) } - return *t + return &SpannerInstanceIdentity{ + parent: &SpannerInstanceParent{ProjectID: tokens[1]}, + id: tokens[3], + }, nil } diff --git a/apis/spanner/v1beta1/zz_generated.deepcopy.go b/apis/spanner/v1beta1/zz_generated.deepcopy.go index 7c027ee58b..e7c542f733 100644 --- a/apis/spanner/v1beta1/zz_generated.deepcopy.go +++ b/apis/spanner/v1beta1/zz_generated.deepcopy.go @@ -331,6 +331,26 @@ func (in *SpannerInstance) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SpannerInstanceIdentity) DeepCopyInto(out *SpannerInstanceIdentity) { + *out = *in + if in.parent != nil { + in, out := &in.parent, &out.parent + *out = new(SpannerInstanceParent) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpannerInstanceIdentity. +func (in *SpannerInstanceIdentity) DeepCopy() *SpannerInstanceIdentity { + if in == nil { + return nil + } + out := new(SpannerInstanceIdentity) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SpannerInstanceList) DeepCopyInto(out *SpannerInstanceList) { *out = *in @@ -396,11 +416,6 @@ func (in *SpannerInstanceParent) DeepCopy() *SpannerInstanceParent { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SpannerInstanceRef) DeepCopyInto(out *SpannerInstanceRef) { *out = *in - if in.parent != nil { - in, out := &in.parent, &out.parent - *out = new(SpannerInstanceParent) - **out = **in - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpannerInstanceRef. @@ -466,6 +481,11 @@ func (in *SpannerInstanceStatus) DeepCopyInto(out *SpannerInstanceStatus) { *out = new(string) **out = **in } + if in.ObservedState != nil { + in, out := &in.ObservedState, &out.ObservedState + *out = new(SpannerInstanceObservedState) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpannerInstanceStatus. diff --git a/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_spannerinstances.spanner.cnrm.cloud.google.com.yaml b/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_spannerinstances.spanner.cnrm.cloud.google.com.yaml index 6a6b36c375..29863500fc 100644 --- a/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_spannerinstances.spanner.cnrm.cloud.google.com.yaml +++ b/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_spannerinstances.spanner.cnrm.cloud.google.com.yaml @@ -135,6 +135,10 @@ spec: the resource. format: int64 type: integer + observedState: + description: ObservedState is the state of the resource as most recently + observed in GCP. + type: object state: description: 'Instance status: ''CREATING'' or ''READY''.' type: string diff --git a/pkg/controller/direct/spanner/instance_controller.go b/pkg/controller/direct/spanner/instance_controller.go index 1e27c31786..7d4ac5f076 100644 --- a/pkg/controller/direct/spanner/instance_controller.go +++ b/pkg/controller/direct/spanner/instance_controller.go @@ -37,6 +37,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) +const ( + ctrlName = "spanner-controller" +) + func init() { registry.RegisterModel(krm.SpannerInstanceGVK, NewSpannerInstanceModel) } @@ -70,7 +74,7 @@ func (m *modelSpannerInstance) AdapterForObject(ctx context.Context, reader clie return nil, fmt.Errorf("error converting to %T: %w", obj, err) } - id, err := krm.NewSpannerInstanceRef(ctx, reader, obj, u) + id, err := krm.NewSpannerInstanceIdentity(ctx, reader, obj, u) if err != nil { return nil, err } @@ -88,10 +92,9 @@ func (m *modelSpannerInstance) AdapterForObject(ctx context.Context, reader clie return nil, fmt.Errorf("cannot resolve resource ID") } return &SpannerInstanceAdapter{ - id: id, - gcpClient: gcpClient, - desired: obj, - resourceID: resourceID, + id: id, + gcpClient: gcpClient, + desired: obj, }, nil } @@ -101,26 +104,25 @@ func (m *modelSpannerInstance) AdapterForURL(ctx context.Context, url string) (d } type SpannerInstanceAdapter struct { - id *krm.SpannerInstanceRef - gcpClient *gcp.InstanceAdminClient - desired *krm.SpannerInstance - actual *spannerpb.Instance - resourceID string + id *krm.SpannerInstanceIdentity + gcpClient *gcp.InstanceAdminClient + desired *krm.SpannerInstance + actual *spannerpb.Instance } var _ directbase.Adapter = &SpannerInstanceAdapter{} func (a *SpannerInstanceAdapter) Find(ctx context.Context) (bool, error) { - log := klog.FromContext(ctx) - log.V(2).Info("getting SpannerInstance", "name", a.id.External) + log := klog.FromContext(ctx).WithName(ctrlName) + log.V(2).Info("getting SpannerInstance", "name", a.id) - req := &spannerpb.GetInstanceRequest{Name: a.id.External} + req := &spannerpb.GetInstanceRequest{Name: a.id.String()} instancepb, err := a.gcpClient.GetInstance(ctx, req) if err != nil { if direct.IsNotFound(err) { return false, nil } - return false, fmt.Errorf("getting SpannerInstance %q: %w", a.id.External, err) + return false, fmt.Errorf("getting SpannerInstance %q: %w", a.id, err) } a.actual = instancepb @@ -128,8 +130,8 @@ func (a *SpannerInstanceAdapter) Find(ctx context.Context) (bool, error) { } func (a *SpannerInstanceAdapter) Create(ctx context.Context, createOp *directbase.CreateOperation) error { - log := klog.FromContext(ctx) - log.V(2).Info("creating Instance", "name", a.id.External) + log := klog.FromContext(ctx).WithName(ctrlName) + log.V(2).Info("creating Instance", "name", a.id) mapCtx := &direct.MapContext{} desired := a.desired.DeepCopy() @@ -142,55 +144,50 @@ func (a *SpannerInstanceAdapter) Create(ctx context.Context, createOp *directbas if resource.NodeCount == 0 && resource.ProcessingUnits == 0 { resource.NodeCount = 1 } - resource.Name = a.id.External + resource.Name = a.id.String() resource.Labels = desired.Labels resource.Labels["managed-by-cnrm"] = "true" - resource.Name = a.id.External if mapCtx.Err() != nil { return mapCtx.Err() } - parent, err := a.id.Parent() - if err != nil { - return err - } - req := &spannerpb.CreateInstanceRequest{ - InstanceId: a.resourceID, + InstanceId: a.id.ID(), Instance: resource, - Parent: parent.String(), + Parent: a.id.Parent().String(), } op, err := a.gcpClient.CreateInstance(ctx, req) if err != nil { - return fmt.Errorf("creating Instance %s: %w", a.id.External, err) + return fmt.Errorf("creating Instance %s: %w", a.id, err) } + created, err := op.Wait(ctx) if err != nil { - return fmt.Errorf("Instance %s waiting creation: %w", a.id.External, err) + return fmt.Errorf("Instance %s waiting creation: %w", a.id, err) } - log.V(2).Info("successfully created Instance", "name", a.id.External) + log.V(2).Info("successfully created Instance", "name", a.id) status := &krm.SpannerInstanceStatus{} status.State = State_FromProto(mapCtx, created) if mapCtx.Err() != nil { return mapCtx.Err() } - status.ExternalRef = &a.id.External + external := a.id.String() + status.ExternalRef = &external return createOp.UpdateStatus(ctx, status, nil) } func (a *SpannerInstanceAdapter) Update(ctx context.Context, updateOp *directbase.UpdateOperation) error { - log := klog.FromContext(ctx) - log.V(2).Info("updating Instance", "name", a.id.External) + log := klog.FromContext(ctx).WithName(ctrlName) + log.V(2).Info("updating Instance", "name", a.id) mapCtx := &direct.MapContext{} if err := a.SpecValidation(); err != nil { return err } desired := a.desired.DeepCopy() resource := SpannerInstanceSpec_ToProto(mapCtx, &desired.Spec, a.id.SpannerInstanceConfigPrefix()) - resource.Name = a.id.External + resource.Name = a.id.String() resource.Labels = desired.Labels resource.Labels["managed-by-cnrm"] = "true" - resource.Name = a.id.External if mapCtx.Err() != nil { return mapCtx.Err() } @@ -212,23 +209,23 @@ func (a *SpannerInstanceAdapter) Update(ctx context.Context, updateOp *directbas } if len(updateMask.Paths) == 0 { - log.V(2).Info("no field needs update", "name", a.id.External) + log.V(2).Info("no field needs update", "name", a.id) return nil } - resource.Name = a.id.External + req := &spannerpb.UpdateInstanceRequest{ FieldMask: updateMask, Instance: resource, } op, err := a.gcpClient.UpdateInstance(ctx, req) if err != nil { - return fmt.Errorf("updating Instance %s: %w", a.id.External, err) + return fmt.Errorf("updating Instance %s: %w", a.id, err) } updated, err := op.Wait(ctx) if err != nil { - return fmt.Errorf("Instance %s waiting update: %w", a.id.External, err) + return fmt.Errorf("Instance %s waiting update: %w", a.id, err) } - log.V(2).Info("successfully updated Instance", "name", a.id.External) + log.V(2).Info("successfully updated Instance", "name", a.id) status := &krm.SpannerInstanceStatus{} status.State = State_FromProto(mapCtx, updated) @@ -264,15 +261,15 @@ func (a *SpannerInstanceAdapter) Export(ctx context.Context) (*unstructured.Unst // Delete implements the Adapter interface. func (a *SpannerInstanceAdapter) Delete(ctx context.Context, deleteOp *directbase.DeleteOperation) (bool, error) { - log := klog.FromContext(ctx) - log.V(2).Info("deleting Instance", "name", a.id.External) + log := klog.FromContext(ctx).WithName(ctrlName) + log.V(2).Info("deleting Instance", "name", a.id) - req := &spannerpb.DeleteInstanceRequest{Name: a.id.External} + req := &spannerpb.DeleteInstanceRequest{Name: a.id.String()} err := a.gcpClient.DeleteInstance(ctx, req) if err != nil { - return false, fmt.Errorf("deleting Instance %s: %w", a.id.External, err) + return false, fmt.Errorf("deleting Instance %s: %w", a.id, err) } - log.V(2).Info("successfully deleted Instance", "name", a.id.External) + log.V(2).Info("successfully deleted Instance", "name", a.id) return true, nil } diff --git a/scripts/generate-google3-docs/resource-reference/generated/resource-docs/spanner/spannerinstance.md b/scripts/generate-google3-docs/resource-reference/generated/resource-docs/spanner/spannerinstance.md index 8bf99f0cd6..1acaec36cf 100644 --- a/scripts/generate-google3-docs/resource-reference/generated/resource-docs/spanner/spannerinstance.md +++ b/scripts/generate-google3-docs/resource-reference/generated/resource-docs/spanner/spannerinstance.md @@ -178,6 +178,7 @@ conditions: type: string externalRef: string observedGeneration: integer +observedState: {} state: string ``` @@ -251,6 +252,13 @@ state: string

{% verbatim %}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.{% endverbatim %}

+ + observedState + +

object

+

{% verbatim %}ObservedState is the state of the resource as most recently observed in GCP.{% endverbatim %}

+ + state From a20379fec6e982a51e525b412ee498064c1bd1f5 Mon Sep 17 00:00:00 2001 From: Anh Le Date: Thu, 19 Dec 2024 00:59:55 +0000 Subject: [PATCH 9/9] remove spanner instance from real gcp test --- hack/record-gcp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/record-gcp b/hack/record-gcp index b43cca2c15..87c5889a84 100755 --- a/hack/record-gcp +++ b/hack/record-gcp @@ -27,7 +27,7 @@ echo "TEST_BILLING_ACCOUNT_ID=${TEST_BILLING_ACCOUNT_ID}" export TEST_BILLING_ACCOUNT_ID if [[ -z "${KCC_USE_DIRECT_RECONCILERS:-}" ]]; then - KCC_USE_DIRECT_RECONCILERS="ComputeForwardingRule,SpannerInstance" + KCC_USE_DIRECT_RECONCILERS="ComputeForwardingRule" fi export KCC_USE_DIRECT_RECONCILERS echo "KCC_USE_DIRECT_RECONCILERS=$KCC_USE_DIRECT_RECONCILERS"