Skip to content

Commit e433b2a

Browse files
Merge pull request #3795 from jasonvigil/apigeeendpointattachment
feat: Add ApigeeEndpointAttachment v1alpha1 reconciler
2 parents 07682dc + 0281e93 commit e433b2a

22 files changed

+3035
-47
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package v1alpha1
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"strings"
21+
22+
apigeev1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/apigee/v1beta1"
23+
"github.com/GoogleCloudPlatform/k8s-config-connector/apis/common"
24+
"github.com/GoogleCloudPlatform/k8s-config-connector/apis/common/identity"
25+
"sigs.k8s.io/controller-runtime/pkg/client"
26+
)
27+
28+
const (
29+
ApigeeEndpointAttachmentIDToken = "endpointAttachments"
30+
ApigeeEndpointAttachmentIDFormat = apigeev1beta1.ApigeeOrganizationIDFormat + "/" + ApigeeEndpointAttachmentIDToken + "/{{attachmentID}}"
31+
)
32+
33+
var _ identity.Identity = &ApigeeEndpointAttachmentIdentity{}
34+
35+
type ApigeeEndpointAttachmentIdentity struct {
36+
ParentID *apigeev1beta1.ApigeeOrganizationIdentity
37+
ResourceID string
38+
}
39+
40+
func (i *ApigeeEndpointAttachmentIdentity) String() string {
41+
return i.ParentID.String() + "/" + ApigeeEndpointAttachmentIDToken + "/" + i.ResourceID
42+
}
43+
44+
func (i *ApigeeEndpointAttachmentIdentity) FromExternal(ref string) error {
45+
requiredTokens := len(strings.Split(ApigeeEndpointAttachmentIDFormat, "/"))
46+
47+
tokens := strings.Split(ref, "/")
48+
if len(tokens) != requiredTokens || tokens[len(tokens)-2] != ApigeeEndpointAttachmentIDToken {
49+
return fmt.Errorf("format of ApigeeEndpointAttachment ref=%q was not known (use %q)", ref, ApigeeEndpointAttachmentIDFormat)
50+
}
51+
52+
parentID := &apigeev1beta1.ApigeeOrganizationIdentity{}
53+
if err := parentID.FromExternal(strings.Join(tokens[:len(tokens)-2], "/")); err != nil {
54+
return fmt.Errorf("format of ApigeeEndpointAttachment ref=%q was not known (use %q)", ref, ApigeeEndpointAttachmentIDFormat)
55+
}
56+
57+
resourceID := tokens[len(tokens)-1]
58+
59+
i.ParentID = parentID
60+
i.ResourceID = resourceID
61+
62+
return nil
63+
}
64+
65+
var _ identity.Resource = &ApigeeEndpointAttachment{}
66+
67+
func (obj *ApigeeEndpointAttachment) GetIdentity(ctx context.Context, reader client.Reader) (identity.Identity, error) {
68+
// Get parent ID
69+
parentID, err := obj.GetParentIdentity(ctx, reader)
70+
if err != nil {
71+
return nil, err
72+
}
73+
74+
// Get resource ID
75+
resourceID := common.ValueOf(obj.Spec.ResourceID)
76+
if resourceID == "" {
77+
resourceID = obj.GetName()
78+
}
79+
if resourceID == "" {
80+
return nil, fmt.Errorf("cannot resolve resource ID")
81+
}
82+
83+
id := &ApigeeEndpointAttachmentIdentity{
84+
ParentID: parentID.(*apigeev1beta1.ApigeeOrganizationIdentity),
85+
ResourceID: resourceID,
86+
}
87+
88+
// Attempt to ensure ID is immutable, by verifying against previously-set `status.externalRef`.
89+
externalRef := common.ValueOf(obj.Status.ExternalRef)
90+
if externalRef != "" {
91+
previousID := &ApigeeEndpointAttachmentIdentity{}
92+
if err := previousID.FromExternal(externalRef); err != nil {
93+
return nil, err
94+
}
95+
if id.String() != previousID.String() {
96+
return nil, fmt.Errorf("cannot update ApigeeEndpointAttachment identity (old=%q, new=%q): identity is immutable", previousID.String(), id.String())
97+
}
98+
}
99+
100+
return id, nil
101+
}
102+
103+
func (obj *ApigeeEndpointAttachment) GetParentIdentity(ctx context.Context, reader client.Reader) (identity.Identity, error) {
104+
// Normalize parent reference
105+
if err := obj.Spec.OrganizationRef.Normalize(ctx, reader, obj.GetNamespace()); err != nil {
106+
return nil, err
107+
}
108+
// Get parent identity
109+
parentID := &apigeev1beta1.ApigeeOrganizationIdentity{}
110+
if err := parentID.FromExternal(obj.Spec.OrganizationRef.External); err != nil {
111+
return nil, err
112+
}
113+
return parentID, nil
114+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package v1alpha1
16+
17+
import (
18+
"context"
19+
20+
refsv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
21+
22+
"k8s.io/apimachinery/pkg/runtime/schema"
23+
"k8s.io/apimachinery/pkg/types"
24+
"sigs.k8s.io/controller-runtime/pkg/client"
25+
)
26+
27+
var _ refsv1beta1.Ref = &ApigeeEndpointAttachmentRef{}
28+
29+
// ApigeeEndpointAttachmentRef is a reference to a ApigeeEndpointAttachment resource.
30+
type ApigeeEndpointAttachmentRef struct {
31+
// A reference to an externally managed ApigeeEndpointAttachment resource.
32+
// Should be in the format "organizations/{{organizationID}}/endpointAttachments/{{attachmentID}}".
33+
External string `json:"external,omitempty"`
34+
35+
// The name of a ApigeeEndpointAttachment resource.
36+
Name string `json:"name,omitempty"`
37+
38+
// The namespace of a ApigeeEndpointAttachment resource.
39+
Namespace string `json:"namespace,omitempty"`
40+
}
41+
42+
func (r *ApigeeEndpointAttachmentRef) GetGVK() schema.GroupVersionKind {
43+
return ApigeeEndpointAttachmentGVK
44+
}
45+
46+
func (r *ApigeeEndpointAttachmentRef) GetNamespacedName() types.NamespacedName {
47+
return types.NamespacedName{
48+
Name: r.Name,
49+
Namespace: r.Namespace,
50+
}
51+
}
52+
53+
func (r *ApigeeEndpointAttachmentRef) GetExternal() string {
54+
return r.External
55+
}
56+
57+
func (r *ApigeeEndpointAttachmentRef) SetExternal(ref string) {
58+
r.External = ref
59+
}
60+
61+
func (r *ApigeeEndpointAttachmentRef) ValidateExternal(ref string) error {
62+
id := &ApigeeEndpointAttachmentIdentity{}
63+
if err := id.FromExternal(r.GetExternal()); err != nil {
64+
return err
65+
}
66+
return nil
67+
}
68+
69+
func (r *ApigeeEndpointAttachmentRef) Normalize(ctx context.Context, reader client.Reader, defaultNamespace string) error {
70+
return refsv1beta1.Normalize(ctx, reader, r, defaultNamespace)
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package v1alpha1
16+
17+
import (
18+
apigeev1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/apigee/v1beta1"
19+
refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
20+
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
21+
22+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
)
24+
25+
var ApigeeEndpointAttachmentGVK = GroupVersion.WithKind("ApigeeEndpointAttachment")
26+
27+
// ApigeeEndpointAttachmentSpec defines the desired state of ApigeeEndpointAttachment
28+
// +kcc:proto=mockgcp.cloud.apigee.v1.GoogleCloudApigeeV1EndpointAttachment
29+
type ApigeeEndpointAttachmentSpec struct {
30+
// Reference to parent Apigee Organization.
31+
// +required
32+
OrganizationRef *apigeev1beta1.ApigeeOrganizationRef `json:"organizationRef"`
33+
34+
// Required. Location of the endpoint attachment.
35+
// +kcc:proto:field=mockgcp.cloud.apigee.v1.GoogleCloudApigeeV1EndpointAttachment.location
36+
// +rquired
37+
Location *string `json:"location,omitempty"`
38+
39+
// Reference to the ServiceAttachment for the EndpointAttachment.
40+
// +kcc:proto:field=mockgcp.cloud.apigee.v1.GoogleCloudApigeeV1EndpointAttachment.service_attachment
41+
ServiceAttachmentRef *refs.ComputeServiceAttachmentRef `json:"serviceAttachmentRef,omitempty"`
42+
43+
// The ApigeeEndpointAttachment name. If not given, the metadata.name will be used.
44+
ResourceID *string `json:"resourceID,omitempty"`
45+
}
46+
47+
// ApigeeEndpointAttachmentStatus defines the config connector machine state of ApigeeEndpointAttachment
48+
type ApigeeEndpointAttachmentStatus struct {
49+
/* Conditions represent the latest available observations of the
50+
object's current state. */
51+
Conditions []v1alpha1.Condition `json:"conditions,omitempty"`
52+
53+
// 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.
54+
ObservedGeneration *int64 `json:"observedGeneration,omitempty"`
55+
56+
// A unique specifier for the ApigeeEndpointAttachment resource in GCP.
57+
ExternalRef *string `json:"externalRef,omitempty"`
58+
59+
// ObservedState is the state of the resource as most recently observed in GCP.
60+
ObservedState *ApigeeEndpointAttachmentObservedState `json:"observedState,omitempty"`
61+
}
62+
63+
// ApigeeEndpointAttachmentObservedState is the state of the ApigeeEndpointAttachment resource as most recently observed in GCP.
64+
// +kcc:proto=mockgcp.cloud.apigee.v1.GoogleCloudApigeeV1EndpointAttachment
65+
type ApigeeEndpointAttachmentObservedState struct {
66+
// Output only. State of the endpoint attachment connection to the service attachment.
67+
// +kcc:proto:field=mockgcp.cloud.apigee.v1.GoogleCloudApigeeV1EndpointAttachment.connection_state
68+
ConnectionState *string `json:"connectionState,omitempty"`
69+
70+
// Output only. Host that can be used in either the HTTP target endpoint directly or as the host in target server.
71+
// +kcc:proto:field=mockgcp.cloud.apigee.v1.GoogleCloudApigeeV1EndpointAttachment.host
72+
Host *string `json:"host,omitempty"`
73+
74+
// Output only. State of the endpoint attachment. Values other than `ACTIVE` mean the resource is not ready to use.
75+
// +kcc:proto:field=mockgcp.cloud.apigee.v1.GoogleCloudApigeeV1EndpointAttachment.state
76+
State *string `json:"state,omitempty"`
77+
}
78+
79+
// +genclient
80+
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
81+
// TODO(user): make sure the pluralizaiton below is correct
82+
// +kubebuilder:resource:categories=gcp,shortName=gcpapigeeendpointattachment;gcpapigeeendpointattachments
83+
// +kubebuilder:subresource:status
84+
// +kubebuilder:metadata:labels="cnrm.cloud.google.com/managed-by-kcc=true";"cnrm.cloud.google.com/system=true"
85+
// +kubebuilder:printcolumn:name="Age",JSONPath=".metadata.creationTimestamp",type="date"
86+
// +kubebuilder:printcolumn:name="Ready",JSONPath=".status.conditions[?(@.type=='Ready')].status",type="string",description="When 'True', the most recent reconcile of the resource succeeded"
87+
// +kubebuilder:printcolumn:name="Status",JSONPath=".status.conditions[?(@.type=='Ready')].reason",type="string",description="The reason for the value in 'Ready'"
88+
// +kubebuilder:printcolumn:name="Status Age",JSONPath=".status.conditions[?(@.type=='Ready')].lastTransitionTime",type="date",description="The last transition time for the value in 'Status'"
89+
90+
// ApigeeEndpointAttachment is the Schema for the ApigeeEndpointAttachment API
91+
// +k8s:openapi-gen=true
92+
type ApigeeEndpointAttachment struct {
93+
metav1.TypeMeta `json:",inline"`
94+
metav1.ObjectMeta `json:"metadata,omitempty"`
95+
96+
// +required
97+
Spec ApigeeEndpointAttachmentSpec `json:"spec,omitempty"`
98+
Status ApigeeEndpointAttachmentStatus `json:"status,omitempty"`
99+
}
100+
101+
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
102+
// ApigeeEndpointAttachmentList contains a list of ApigeeEndpointAttachment
103+
type ApigeeEndpointAttachmentList struct {
104+
metav1.TypeMeta `json:",inline"`
105+
metav1.ListMeta `json:"metadata,omitempty"`
106+
Items []ApigeeEndpointAttachment `json:"items"`
107+
}
108+
109+
func init() {
110+
SchemeBuilder.Register(&ApigeeEndpointAttachment{}, &ApigeeEndpointAttachmentList{})
111+
}

0 commit comments

Comments
 (0)