Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add types, mappers and fuzzer for VMwareEngineExternalAddress #4275

105 changes: 105 additions & 0 deletions apis/vmwareengine/v1alpha1/externaladdress_identity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright 2025 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 (
"context"
"fmt"
"strings"

"github.com/GoogleCloudPlatform/k8s-config-connector/apis/common"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// ExternalAddressIdentity defines the resource reference to VMwareEngineExternalAddress, which "External" field
// holds the GCP identifier for the KRM object.
type ExternalAddressIdentity struct {
parent *ExternalAddressParent
id string
}

func (i *ExternalAddressIdentity) String() string {
return i.parent.String() + "/externalAddresses/" + i.id
}

func (i *ExternalAddressIdentity) ID() string {
return i.id
}

func (i *ExternalAddressIdentity) Parent() *ExternalAddressParent {
return i.parent
}

type ExternalAddressParent struct {
PrivateCloud string
}

func (p *ExternalAddressParent) String() string {
return p.PrivateCloud
}

// New builds a ExternalAddressIdentity from the Config Connector ExternalAddress object.
func NewExternalAddressIdentity(ctx context.Context, reader client.Reader, obj *VMwareEngineExternalAddress) (*ExternalAddressIdentity, error) {
// Get Parent
privateCloud, err := obj.Spec.PrivateCloudRef.NormalizedExternal(ctx, reader, obj.GetNamespace())
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 != "" {
// Validate desired with actual
actualParent, actualResourceID, err := ParseExternalAddressExternal(externalRef)
if err != nil {
return nil, err
}
if actualParent.PrivateCloud != privateCloud {
return nil, fmt.Errorf("spec.privateCloudRef changed, expect %s, got %s", actualParent.PrivateCloud, privateCloud)
}
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)
}
}
return &ExternalAddressIdentity{
parent: &ExternalAddressParent{
PrivateCloud: privateCloud,
},
id: resourceID,
}, nil
}

func ParseExternalAddressExternal(external string) (parent *ExternalAddressParent, resourceID string, err error) {
tokens := strings.Split(external, "/")
if len(tokens) != 8 || tokens[0] != "projects" || tokens[2] != "locations" || tokens[4] != "privateClouds" || tokens[6] != "externalAddresses" {
return nil, "", fmt.Errorf("format of VMwareEngineExternalAddress external=%q was not known (use projects/{{projectID}}/locations/{{location}}/privateClouds/{{privatecloudID}}/externalAddresses/{{externaladdressID}})", external)
}
privateCloud := strings.Join(tokens[:len(tokens)-2], "/")
parent = &ExternalAddressParent{
PrivateCloud: privateCloud,
}
resourceID = tokens[7]
return parent, resourceID, nil
}
58 changes: 55 additions & 3 deletions apis/vmwareengine/v1alpha1/externaladdress_reference.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,19 @@

package v1alpha1

// TODO(jingyih): properly implement ExternalAddressRef after adding PrivateCloud and ExternalAddress resources
import (
"context"
"fmt"

refsv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

var _ refsv1beta1.ExternalNormalizer = &ExternalAddressRef{}

// ExternalAddressRef defines the resource reference to VMwareEngineExternalAddress, which "External" field
// holds the GCP identifier for the KRM object.
Expand All @@ -24,8 +36,48 @@ type ExternalAddressRef struct {
External string `json:"external,omitempty"`

// The name of a VMwareEngineExternalAddress resource.
// Name string `json:"name,omitempty"`
Name string `json:"name,omitempty"`

// The namespace of a VMwareEngineExternalAddress resource.
// Namespace string `json:"namespace,omitempty"`
Namespace string `json:"namespace,omitempty"`
}

// NormalizedExternal provision the "External" value for other resource that depends on VMwareEngineExternalAddress.
// If the "External" is given in the other resource's spec.VMwareEngineExternalAddressRef, the given value will be used.
// Otherwise, the "Name" and "Namespace" will be used to query the actual VMwareEngineExternalAddress object from the cluster.
func (r *ExternalAddressRef) NormalizedExternal(ctx context.Context, reader client.Reader, otherNamespace string) (string, error) {
if r.External != "" && r.Name != "" {
return "", fmt.Errorf("cannot specify both name and external on %s reference", VMwareEngineExternalAddressGVK.Kind)
}
// From given External
if r.External != "" {
if _, _, err := ParseExternalAddressExternal(r.External); err != nil {
return "", err
}
return r.External, nil
}

// From the Config Connector object
if r.Namespace == "" {
r.Namespace = otherNamespace
}
key := types.NamespacedName{Name: r.Name, Namespace: r.Namespace}
u := &unstructured.Unstructured{}
u.SetGroupVersionKind(VMwareEngineExternalAddressGVK)
if err := reader.Get(ctx, key, u); err != nil {
if apierrors.IsNotFound(err) {
return "", k8s.NewReferenceNotFoundError(u.GroupVersionKind(), key)
}
return "", fmt.Errorf("reading referenced %s %s: %w", VMwareEngineExternalAddressGVK, 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)
}
if actualExternalRef == "" {
return "", k8s.NewReferenceNotReadyError(u.GroupVersionKind(), key)
}
r.External = actualExternalRef
return r.External, nil
}
123 changes: 123 additions & 0 deletions apis/vmwareengine/v1alpha1/externaladdress_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright 2025 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 (
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var VMwareEngineExternalAddressGVK = GroupVersion.WithKind("VMwareEngineExternalAddress")

// VMwareEngineExternalAddressSpec defines the desired state of VMwareEngineExternalAddress
// +kcc:proto=google.cloud.vmwareengine.v1.ExternalAddress
type VMwareEngineExternalAddressSpec struct {
// The VMwareEngineExternalAddress name. If not given, the metadata.name will be used.
ResourceID *string `json:"resourceID,omitempty"`

// Required. The resource name of the private cloud to create a new external IP address in.
// +required
PrivateCloudRef *PrivateCloudRef `json:"privateCloudRef,omitempty"`

// The internal IP address of a workload VM.
// +kcc:proto:field=google.cloud.vmwareengine.v1.ExternalAddress.internal_ip
InternalIP *string `json:"internalIP,omitempty"`

// User-provided description for this resource.
// +kcc:proto:field=google.cloud.vmwareengine.v1.ExternalAddress.description
Description *string `json:"description,omitempty"`
}

// VMwareEngineExternalAddressStatus defines the config connector machine state of VMwareEngineExternalAddress
type VMwareEngineExternalAddressStatus struct {
/* Conditions represent the latest available observations of the
object's current state. */
Conditions []v1alpha1.Condition `json:"conditions,omitempty"`

// ObservedGeneration is the generation of the resource that was most recently observed by the Config Connector controller. If this is equal to metadata.generation, then that means that the current reported status reflects the most recent desired state of the resource.
ObservedGeneration *int64 `json:"observedGeneration,omitempty"`

// A unique specifier for the VMwareEngineExternalAddress resource in GCP.
ExternalRef *string `json:"externalRef,omitempty"`

// ObservedState is the state of the resource as most recently observed in GCP.
ObservedState *VMwareEngineExternalAddressObservedState `json:"observedState,omitempty"`
}

// VMwareEngineExternalAddressObservedState is the state of the VMwareEngineExternalAddress resource as most recently observed in GCP.
// +kcc:proto=google.cloud.vmwareengine.v1.ExternalAddress
type VMwareEngineExternalAddressObservedState struct {
// Output only. The resource name of this external IP address.
// Resource names are schemeless URIs that follow the conventions in
// https://cloud.google.com/apis/design/resource_names.
// For example:
// `projects/my-project/locations/us-central1-a/privateClouds/my-cloud/externalAddresses/my-address`
// +kcc:proto:field=google.cloud.vmwareengine.v1.ExternalAddress.name
// NOTYET: this field serves the same purpose as externalRef
// Name *string `json:"name,omitempty"`

// Output only. Creation time of this resource.
// +kcc:proto:field=google.cloud.vmwareengine.v1.ExternalAddress.create_time
CreateTime *string `json:"createTime,omitempty"`

// Output only. Last update time of this resource.
// +kcc:proto:field=google.cloud.vmwareengine.v1.ExternalAddress.update_time
UpdateTime *string `json:"updateTime,omitempty"`

// Output only. The external IP address of a workload VM.
// +kcc:proto:field=google.cloud.vmwareengine.v1.ExternalAddress.external_ip
ExternalIP *string `json:"externalIP,omitempty"`

// Output only. The state of the resource.
// +kcc:proto:field=google.cloud.vmwareengine.v1.ExternalAddress.state
State *string `json:"state,omitempty"`

// Output only. System-generated unique identifier for the resource.
// +kcc:proto:field=google.cloud.vmwareengine.v1.ExternalAddress.uid
UID *string `json:"uid,omitempty"`
}

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=gcp,shortName=gcpvmwareengineexternaladdress;gcpvmwareengineexternaladdresses
// +kubebuilder:subresource:status
// +kubebuilder:metadata:labels="cnrm.cloud.google.com/managed-by-kcc=true";"cnrm.cloud.google.com/system=true"
// +kubebuilder:printcolumn:name="Age",JSONPath=".metadata.creationTimestamp",type="date"
// +kubebuilder:printcolumn:name="Ready",JSONPath=".status.conditions[?(@.type=='Ready')].status",type="string",description="When 'True', the most recent reconcile of the resource succeeded"
// +kubebuilder:printcolumn:name="Status",JSONPath=".status.conditions[?(@.type=='Ready')].reason",type="string",description="The reason for the value in 'Ready'"
// +kubebuilder:printcolumn:name="Status Age",JSONPath=".status.conditions[?(@.type=='Ready')].lastTransitionTime",type="date",description="The last transition time for the value in 'Status'"

// VMwareEngineExternalAddress is the Schema for the VMwareEngineExternalAddress API
// +k8s:openapi-gen=true
type VMwareEngineExternalAddress struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

// +required
Spec VMwareEngineExternalAddressSpec `json:"spec,omitempty"`
Status VMwareEngineExternalAddressStatus `json:"status,omitempty"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// VMwareEngineExternalAddressList contains a list of VMwareEngineExternalAddress
type VMwareEngineExternalAddressList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []VMwareEngineExternalAddress `json:"items"`
}

func init() {
SchemeBuilder.Register(&VMwareEngineExternalAddress{}, &VMwareEngineExternalAddressList{})
}
3 changes: 2 additions & 1 deletion apis/vmwareengine/v1alpha1/generate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ go run . generate-types \
--resource VMwareEngineNetworkPeering:NetworkPeering \
--resource VMwareEngineNetworkPolicy:NetworkPolicy \
--resource VMwareEngineExternalAccessRule:ExternalAccessRule \
--resource VMwareEnginePrivateCloud:PrivateCloud
--resource VMwareEnginePrivateCloud:PrivateCloud \
--resource VMwareEngineExternalAddress:ExternalAddress

go run . generate-mapper \
--service google.cloud.vmwareengine.v1 \
Expand Down
1 change: 1 addition & 0 deletions apis/vmwareengine/v1alpha1/types.generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading