diff --git a/apis/cloudbuild/v1alpha1/conversion.go b/apis/cloudbuild/v1alpha1/conversion.go deleted file mode 100644 index 13badf11a4..0000000000 --- a/apis/cloudbuild/v1alpha1/conversion.go +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package v1alpha1 - -import ( - "fmt" - - cloudbuildpb "cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb" - refv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1" -) - -func Convert_WorkerPool_API_v1_To_KRM_status(in *cloudbuildpb.WorkerPool, out *CloudBuildWorkerPoolStatus) error { - if in == nil { - return nil - } - out.ObservedState = &CloudBuildWorkerPoolObservedState{} - - out.ObservedState.ETag = &in.Etag - if err := Convert_PrivatePoolV1Config_API_v1_To_KRM(in.GetPrivatePoolV1Config(), out.ObservedState); err != nil { - return err - } - return nil -} - -func Convert_PrivatePoolV1Config_API_v1_To_KRM(in *cloudbuildpb.PrivatePoolV1Config, out *CloudBuildWorkerPoolObservedState) error { - if in == nil { - return nil - } - out.NetworkConfig = &PrivatePoolV1Config_NetworkConfig{} - if err := Convert_NetworkConfig_API_v1_To_KRM(in.NetworkConfig, out.NetworkConfig); err != nil { - return err - } - out.WorkerConfig = &PrivatePoolV1Config_WorkerConfig{} - if err := Convert_WorkerConfig_API_v1_To_KRM(in.WorkerConfig, out.WorkerConfig); err != nil { - return err - } - return nil -} - -func Convert_NetworkConfig_API_v1_To_KRM(in *cloudbuildpb.PrivatePoolV1Config_NetworkConfig, out *PrivatePoolV1Config_NetworkConfig) error { - if in == nil { - return nil - } - - switch in.EgressOption { - case cloudbuildpb.PrivatePoolV1Config_NetworkConfig_EGRESS_OPTION_UNSPECIFIED: - out.EgressOption = LazyPtr("EGRESS_OPTION_UNSPECIFIED") - case cloudbuildpb.PrivatePoolV1Config_NetworkConfig_NO_PUBLIC_EGRESS: - out.EgressOption = LazyPtr("NO_PUBLIC_EGRESS") - case cloudbuildpb.PrivatePoolV1Config_NetworkConfig_PUBLIC_EGRESS: - out.EgressOption = LazyPtr("PUBLIC_EGRESS") - default: - return fmt.Errorf("unknown egressoption %s", in.EgressOption) - } - - out.PeeredNetworkIPRange = PtrTo(in.GetPeeredNetworkIpRange()) - out.PeeredNetworkRef = refv1beta1.ComputeNetworkRef{ - External: in.GetPeeredNetwork(), - } - - return nil -} - -func Convert_WorkerConfig_API_v1_To_KRM(in *cloudbuildpb.PrivatePoolV1Config_WorkerConfig, out *PrivatePoolV1Config_WorkerConfig) error { - if in == nil { - return nil - } - out.DiskSizeGb = LazyPtr(in.GetDiskSizeGb()) - out.MachineType = LazyPtr(in.GetMachineType()) - return nil -} - -func Convert_WorkerPool_KRM_To_API_v1(in *CloudBuildWorkerPool, out *cloudbuildpb.WorkerPool) error { - if in == nil { - return nil - } - // CloudBuildWorkerPool API has "Name" as output only field. - // The "Name" is of the form "projects//locations//workerpools/" - // out.Name = in.Name - out.DisplayName = in.Spec.DisplayName - - // Custom - outConfig := &cloudbuildpb.PrivatePoolV1Config{} - if err := Convert_PrivatePoolV1Config_KRM_To_API_v1(in.Spec.PrivatePoolConfig, outConfig); err != nil { - return err - } - out.Config = &cloudbuildpb.WorkerPool_PrivatePoolV1Config{ - PrivatePoolV1Config: outConfig, - } - return nil -} - -func Convert_PrivatePoolV1Config_KRM_To_API_v1(in *PrivatePoolV1Config, out *cloudbuildpb.PrivatePoolV1Config) error { - if in == nil { - return nil - } - networkconfig := &cloudbuildpb.PrivatePoolV1Config_NetworkConfig{} - if err := Convert_PrivatePoolV1Config_NetworkConfig_KRM_To_API_v1(in.NetworkConfig, networkconfig); err != nil { - return err - } - out.NetworkConfig = networkconfig - - workerconfig := &cloudbuildpb.PrivatePoolV1Config_WorkerConfig{} - if err := Convert_PrivatePoolV1Config_WorkerConfig_KRM_To_API_v1(in.WorkerConfig, workerconfig); err != nil { - return err - } - out.WorkerConfig = workerconfig - return nil -} - -func Convert_PrivatePoolV1Config_NetworkConfig_KRM_To_API_v1(in *PrivatePoolV1Config_NetworkConfig, out *cloudbuildpb.PrivatePoolV1Config_NetworkConfig) error { - if in == nil { - return nil - } - obj := in.DeepCopy() - out.PeeredNetworkIpRange = ValueOf(obj.PeeredNetworkIPRange) - - // custom - switch ValueOf(obj.EgressOption) { - case "EGRESS_OPTION_UNSPECIFIED": - out.EgressOption = 0 - case "NO_PUBLIC_EGRESS": - out.EgressOption = 1 - case "PUBLIC_EGRESS": - out.EgressOption = 2 - default: - return fmt.Errorf("unknown egressoption %s", ValueOf(obj.EgressOption)) - } - - if obj.PeeredNetworkRef.External != "" { - out.PeeredNetwork = obj.PeeredNetworkRef.External - } - return nil -} - -func Convert_PrivatePoolV1Config_WorkerConfig_KRM_To_API_v1(in *PrivatePoolV1Config_WorkerConfig, out *cloudbuildpb.PrivatePoolV1Config_WorkerConfig) error { - if in == nil { - return nil - } - obj := in.DeepCopy() - out.DiskSizeGb = ValueOf(obj.DiskSizeGb) - out.MachineType = ValueOf(obj.MachineType) - return nil -} - -func PtrTo[T any](t T) *T { - return &t -} - -func ValueOf[T any](t *T) T { - var zeroVal T - if t == nil { - return zeroVal - } - return *t -} - -func LazyPtr[T comparable](v T) *T { - var defaultValue T - if v == defaultValue { - return nil - } - return &v -} diff --git a/pkg/controller/direct/cloudbuild/maputils.go b/pkg/controller/direct/cloudbuild/maputils.go new file mode 100644 index 0000000000..87dc31dcc2 --- /dev/null +++ b/pkg/controller/direct/cloudbuild/maputils.go @@ -0,0 +1,100 @@ +// 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 cloudbuild + +import ( + "errors" + "fmt" + "strings" + "time" + + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type MapContext struct { + errs []error +} + +func (c *MapContext) Errorf(msg string, args ...interface{}) { + c.errs = append(c.errs, fmt.Errorf(msg, args...)) +} + +func (c *MapContext) Err() error { + return errors.Join(c.errs...) +} + +type ProtoEnum interface { + ~int32 + Descriptor() protoreflect.EnumDescriptor +} + +func Enum_ToProto[U ProtoEnum](mapCtx *MapContext, in *string) U { + var defaultU U + descriptor := defaultU.Descriptor() + + inValue := ValueOf(in) + if inValue == "" { + unspecifiedValue := U(0) + return unspecifiedValue + } + + n := descriptor.Values().Len() + for i := 0; i < n; i++ { + value := descriptor.Values().Get(i) + if string(value.Name()) == inValue { + v := U(value.Number()) + return v + } + } + + var validValues []string + for i := 0; i < n; i++ { + value := descriptor.Values().Get(i) + validValues = append(validValues, string(value.Name())) + } + + mapCtx.Errorf("unknown enum value %q for %v (valid values are %v)", inValue, descriptor.FullName(), strings.Join(validValues, ", ")) + return 0 +} + +func Enum_FromProto[U ProtoEnum](mapCtx *MapContext, v U) *string { + descriptor := v.Descriptor() + + if v == 0 { + return nil + } + + val := descriptor.Values().ByNumber(protoreflect.EnumNumber(v)) + if val == nil { + mapCtx.Errorf("unknown enum value %d", v) + return nil + } + s := string(val.Name()) + return &s +} + +func LazyPtr[V comparable](v V) *V { + var defaultV V + if v == defaultV { + return nil + } + return &v +} + +func ToOpenAPIDateTime(ts *timestamppb.Timestamp) *string { + formatted := ts.AsTime().Format(time.RFC3339) + return &formatted +} diff --git a/pkg/controller/direct/cloudbuild/workerpool_controller.go b/pkg/controller/direct/cloudbuild/workerpool_controller.go index e882328be6..e471c3de6f 100644 --- a/pkg/controller/direct/cloudbuild/workerpool_controller.go +++ b/pkg/controller/direct/cloudbuild/workerpool_controller.go @@ -22,7 +22,6 @@ import ( "fmt" "reflect" "strings" - "time" gcp "cloud.google.com/go/cloudbuild/apiv1/v2" cloudbuildpb "cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb" @@ -35,7 +34,6 @@ import ( "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry" "github.com/googleapis/gax-go/v2/apierror" "google.golang.org/protobuf/types/known/fieldmaskpb" - "google.golang.org/protobuf/types/known/timestamppb" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/klog/v2" @@ -171,13 +169,13 @@ func (a *Adapter) Create(ctx context.Context, u *unstructured.Unstructured) erro } desired := a.desired.DeepCopy() - wp := &cloudbuildpb.WorkerPool{ - Name: a.fullyQualifiedName(), - } - err := krm.Convert_WorkerPool_KRM_To_API_v1(desired, wp) - if err != nil { - return fmt.Errorf("converting workerpool spec to api: %w", err) + + mapCtx := &MapContext{} + wp := CloudBuildWorkerPoolSpec_ToProto(mapCtx, &desired.Spec) + if mapCtx.Err() != nil { + return mapCtx.Err() } + wp.Name = a.fullyQualifiedName() req := &cloudbuildpb.CreateWorkerPoolRequest{ Parent: a.getParent(), WorkerPoolId: a.resourceID, @@ -191,12 +189,12 @@ func (a *Adapter) Create(ctx context.Context, u *unstructured.Unstructured) erro if err != nil { return fmt.Errorf("cloudbuildworkerpool %s waiting creation failed: %w", wp.Name, err) } + status := &krm.CloudBuildWorkerPoolStatus{} - if err := krm.Convert_WorkerPool_API_v1_To_KRM_status(created, status); err != nil { - return fmt.Errorf("update workerpool status %w", err) + status.ObservedState = CloudBuildWorkerPoolObservedState_FromProto(mapCtx, created) + if mapCtx.Err() != nil { + return mapCtx.Err() } - status.ObservedState.CreateTime = ToOpenAPIDateTime(created.GetCreateTime()) - status.ObservedState.UpdateTime = ToOpenAPIDateTime(created.GetUpdateTime()) resRef, err := NewResourceRef(created) if err != nil { return err @@ -265,15 +263,14 @@ func (a *Adapter) Update(ctx context.Context, u *unstructured.Unstructured) erro return nil } - wp := &cloudbuildpb.WorkerPool{ - Name: a.fullyQualifiedName(), - Etag: a.actual.Etag, - } desired := a.desired.DeepCopy() - err := krm.Convert_WorkerPool_KRM_To_API_v1(desired, wp) - if err != nil { - return fmt.Errorf("converting workerpool spec to api: %w", err) + mapCtx := &MapContext{} + wp := CloudBuildWorkerPoolSpec_ToProto(mapCtx, &desired.Spec) + if mapCtx.Err() != nil { + return mapCtx.Err() } + wp.Name = a.fullyQualifiedName() + wp.Etag = a.actual.Etag req := &cloudbuildpb.UpdateWorkerPoolRequest{ WorkerPool: wp, UpdateMask: updateMask, @@ -287,11 +284,10 @@ func (a *Adapter) Update(ctx context.Context, u *unstructured.Unstructured) erro return fmt.Errorf("cloudbuildworkerpool %s waiting update failed: %w", wp.Name, err) } status := &krm.CloudBuildWorkerPoolStatus{} - if err := krm.Convert_WorkerPool_API_v1_To_KRM_status(updated, status); err != nil { - return fmt.Errorf("update workerpool status %w", err) + status.ObservedState = CloudBuildWorkerPoolObservedState_FromProto(mapCtx, updated) + if mapCtx.Err() != nil { + return fmt.Errorf("update workerpool status %w", mapCtx.Err()) } - status.ObservedState.CreateTime = ToOpenAPIDateTime(updated.GetCreateTime()) - status.ObservedState.UpdateTime = ToOpenAPIDateTime(updated.GetUpdateTime()) // This value should not be updated. Just in case. resRef, err := NewResourceRef(updated) if err != nil { @@ -416,19 +412,3 @@ func HasHTTPCode(err error, code int) bool { } return false } - -// LazyPtr returns a pointer to v, unless it is the empty value, in which case it returns nil. -// It is essentially the inverse of ValueOf, though it is lossy -// because we can't tell nil and empty apart without a pointer. -func LazyPtr[T comparable](v T) *T { - var defaultValue T - if v == defaultValue { - return nil - } - return &v -} - -func ToOpenAPIDateTime(ts *timestamppb.Timestamp) *string { - formatted := ts.AsTime().Format(time.RFC3339) - return &formatted -} diff --git a/pkg/controller/direct/cloudbuild/workerpool_mappings.go b/pkg/controller/direct/cloudbuild/workerpool_mappings.go new file mode 100644 index 0000000000..9fb1bb4b51 --- /dev/null +++ b/pkg/controller/direct/cloudbuild/workerpool_mappings.go @@ -0,0 +1,109 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cloudbuild + +import ( + pb "cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb" + + krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/cloudbuild/v1alpha1" + refv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1" +) + +func CloudBuildWorkerPoolObservedState_FromProto(mapCtx *MapContext, in *pb.WorkerPool) *krm.CloudBuildWorkerPoolObservedState { + if in == nil { + return nil + } + out := &krm.CloudBuildWorkerPoolObservedState{} + out.ETag = LazyPtr(in.Etag) + privateConfig := PrivatePoolV1Config_FromProto(mapCtx, in.GetPrivatePoolV1Config()) + out.NetworkConfig = privateConfig.NetworkConfig + out.WorkerConfig = privateConfig.WorkerConfig + out.CreateTime = ToOpenAPIDateTime(in.GetCreateTime()) + out.UpdateTime = ToOpenAPIDateTime(in.GetUpdateTime()) + return out +} + +func CloudBuildWorkerPoolSpec_ToProto(mapCtx *MapContext, in *krm.CloudBuildWorkerPoolSpec) *pb.WorkerPool { + if in == nil { + return nil + } + out := &pb.WorkerPool{} + out.DisplayName = in.DisplayName + out.Config = &pb.WorkerPool_PrivatePoolV1Config{ + PrivatePoolV1Config: PrivatePoolV1Config_ToProto(mapCtx, in.PrivatePoolConfig), + } + return out +} + +func PrivatePoolV1Config_FromProto(mapCtx *MapContext, in *pb.PrivatePoolV1Config) *krm.PrivatePoolV1Config { + if in == nil { + return nil + } + out := &krm.PrivatePoolV1Config{} + out.WorkerConfig = PrivatePoolV1Config_WorkerConfig_FromProto(mapCtx, in.GetWorkerConfig()) + out.NetworkConfig = PrivatePoolV1Config_NetworkConfig_FromProto(mapCtx, in.GetNetworkConfig()) + return out +} +func PrivatePoolV1Config_ToProto(mapCtx *MapContext, in *krm.PrivatePoolV1Config) *pb.PrivatePoolV1Config { + if in == nil { + return nil + } + out := &pb.PrivatePoolV1Config{} + out.WorkerConfig = PrivatePoolV1Config_WorkerConfig_ToProto(mapCtx, in.WorkerConfig) + out.NetworkConfig = PrivatePoolV1Config_NetworkConfig_ToProto(mapCtx, in.NetworkConfig) + return out +} +func PrivatePoolV1Config_NetworkConfig_FromProto(mapCtx *MapContext, in *pb.PrivatePoolV1Config_NetworkConfig) *krm.PrivatePoolV1Config_NetworkConfig { + if in == nil { + return nil + } + out := &krm.PrivatePoolV1Config_NetworkConfig{} + out.PeeredNetworkRef = refv1beta1.ComputeNetworkRef{ + External: in.GetPeeredNetwork(), + } + out.EgressOption = Enum_FromProto(mapCtx, in.EgressOption) + out.PeeredNetworkIPRange = LazyPtr(in.GetPeeredNetworkIpRange()) + return out +} +func PrivatePoolV1Config_NetworkConfig_ToProto(mapCtx *MapContext, in *krm.PrivatePoolV1Config_NetworkConfig) *pb.PrivatePoolV1Config_NetworkConfig { + if in == nil { + return nil + } + out := &pb.PrivatePoolV1Config_NetworkConfig{} + out.PeeredNetwork = in.PeeredNetworkRef.External + out.EgressOption = Enum_ToProto[pb.PrivatePoolV1Config_NetworkConfig_EgressOption](mapCtx, in.EgressOption) + out.PeeredNetworkIpRange = ValueOf(in.PeeredNetworkIPRange) + return out +} +func PrivatePoolV1Config_WorkerConfig_FromProto(mapCtx *MapContext, in *pb.PrivatePoolV1Config_WorkerConfig) *krm.PrivatePoolV1Config_WorkerConfig { + if in == nil { + return nil + } + out := &krm.PrivatePoolV1Config_WorkerConfig{} + out.MachineType = LazyPtr(in.GetMachineType()) + out.DiskSizeGb = LazyPtr(in.GetDiskSizeGb()) + return out +} +func PrivatePoolV1Config_WorkerConfig_ToProto(mapCtx *MapContext, in *krm.PrivatePoolV1Config_WorkerConfig) *pb.PrivatePoolV1Config_WorkerConfig { + if in == nil { + return nil + } + out := &pb.PrivatePoolV1Config_WorkerConfig{} + out.MachineType = ValueOf(in.MachineType) + out.DiskSizeGb = ValueOf(in.DiskSizeGb) + return out +}