Skip to content

Commit 94663d7

Browse files
Merge pull request GoogleCloudPlatform#3605 from acpana/acpana/aega-types
apis: ApigeeEnvgroupAttachment
2 parents 8e419c2 + f28af05 commit 94663d7

8 files changed

+698
-30
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
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+
"github.com/GoogleCloudPlatform/k8s-config-connector/apis/common"
23+
refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
24+
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
25+
"sigs.k8s.io/controller-runtime/pkg/client"
26+
)
27+
28+
type EnvgroupAttachmentIdentity struct {
29+
parent *EnvgroupAttachmentParent
30+
id string
31+
}
32+
33+
func (i *EnvgroupAttachmentIdentity) String() string {
34+
return fmt.Sprintf("%s/attachments/%s", i.parent, i.id)
35+
}
36+
37+
func (i *EnvgroupAttachmentIdentity) ID() string {
38+
return i.id
39+
}
40+
41+
func (i *EnvgroupAttachmentIdentity) Parent() *EnvgroupAttachmentParent {
42+
return i.parent
43+
}
44+
45+
type EnvgroupAttachmentParent struct {
46+
Organization string
47+
Envgroup string
48+
}
49+
50+
func (p *EnvgroupAttachmentParent) String() string {
51+
return "organizations/" + p.Organization + "/envgroups/" + p.Envgroup
52+
}
53+
54+
func NewEnvgroupAttachmentIdentity(ctx context.Context, reader client.Reader, obj *ApigeeEnvgroupAttachment) (*EnvgroupAttachmentIdentity, error) {
55+
if obj == nil {
56+
return nil, fmt.Errorf("object cannot be nil")
57+
}
58+
59+
// Get Parent
60+
orgRef := obj.Spec.OrganizationRef
61+
if orgRef == nil {
62+
return nil, fmt.Errorf("no parent organization")
63+
}
64+
orgExternal, err := orgRef.NormalizedExternal(ctx, reader, obj.Namespace)
65+
if err != nil {
66+
return nil, fmt.Errorf("cannot resolve organization: %w", err)
67+
}
68+
69+
org, err := refs.ParseApigeeOrganizationExternal(orgExternal)
70+
if err != nil {
71+
return nil, fmt.Errorf("cannot parse external organization: %w", err)
72+
}
73+
74+
envgroupRef := obj.Spec.EnvgroupRef
75+
if envgroupRef == nil {
76+
return nil, fmt.Errorf("no envgroup reference")
77+
}
78+
envgroupExternal, err := envgroupRef.NormalizedExternal(ctx, reader, obj.Namespace)
79+
if err != nil {
80+
return nil, fmt.Errorf("cannot resolve envgroup: %w", err)
81+
}
82+
_, envgroupID, err := ParseEnvironmentGroupExternal(envgroupExternal)
83+
if err != nil {
84+
return nil, fmt.Errorf("cannot parse envgroup external: %w", err)
85+
}
86+
87+
resourceID := direct.ValueOf(obj.Spec.ResourceID)
88+
if resourceID == "" {
89+
resourceID = obj.GetName()
90+
}
91+
if resourceID == "" {
92+
return nil, fmt.Errorf("cannot resolve resource ID")
93+
}
94+
95+
externalRef := common.ValueOf(obj.Status.ExternalRef)
96+
if externalRef != "" {
97+
actualParent, actualResourceID, err := ParseEnvgroupAttachmentExternalRef(externalRef)
98+
if err != nil {
99+
return nil, err
100+
}
101+
102+
if actualParent.Organization != org {
103+
return nil, fmt.Errorf("spec.organizationRef changed, expect %s, got %s", actualParent.Organization, org)
104+
}
105+
if actualParent.Envgroup != envgroupID {
106+
return nil, fmt.Errorf("spec.envgroup changed, expect %s, got %s", actualParent.Envgroup, obj.Spec.EnvgroupRef)
107+
}
108+
if actualResourceID != actualResourceID {
109+
return nil, fmt.Errorf("spec.resourceID changed, expect %s, got %s", actualResourceID, resourceID)
110+
}
111+
}
112+
113+
return &EnvgroupAttachmentIdentity{
114+
parent: &EnvgroupAttachmentParent{
115+
Organization: org,
116+
Envgroup: envgroupID,
117+
},
118+
id: resourceID,
119+
}, nil
120+
}
121+
122+
func ParseEnvgroupAttachmentExternalRef(external string) (parent *EnvgroupAttachmentParent, resourceID string, err error) {
123+
tokens := strings.Split(external, "/")
124+
if len(tokens) != 6 {
125+
return nil, "", fmt.Errorf("invalid external format: %s, expecting organizations/{{organizationID}}/envgroups/{{envgroupID}}/attachments/{{attachmentID}} ", external)
126+
}
127+
128+
parent = &EnvgroupAttachmentParent{
129+
Organization: tokens[1],
130+
Envgroup: tokens[3],
131+
}
132+
resourceID = tokens[5]
133+
return parent, resourceID, nil
134+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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+
21+
refsv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
22+
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
23+
apierrors "k8s.io/apimachinery/pkg/api/errors"
24+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
25+
"k8s.io/apimachinery/pkg/types"
26+
"sigs.k8s.io/controller-runtime/pkg/client"
27+
)
28+
29+
var _ refsv1beta1.ExternalNormalizer = &EnvgroupAttachmentRef{}
30+
31+
// EnvgroupAttachmentRef defines the resource reference to EnvgroupAttachment, which "External" field
32+
// holds the GCP identifier for the KRM object.
33+
type EnvgroupAttachmentRef struct {
34+
// A reference to an externally managed EnvgroupAttachment resource.
35+
// Should be in the format "organizations/{{organizationID}}/envgroups/{{envgroupID}}/attachments/{{attachmentID}}".
36+
External string `json:"external,omitempty"`
37+
38+
// The name of a EnvgroupAttachment resource.
39+
Name string `json:"name,omitempty"`
40+
41+
// The namespace of a EnvgroupAttachment resource.
42+
Namespace string `json:"namespace,omitempty"`
43+
}
44+
45+
// NormalizedExternal provision the "External" value for other resource that depends on EnvgroupAttachment.
46+
// If the "External" is given in the other resource's spec.EnvgroupAttachmentRef, the given value will be used.
47+
// Otherwise, the "Name" and "Namespace" will be used to query the actual EnvgroupAttachment object from the cluster.
48+
func (r *EnvgroupAttachmentRef) NormalizedExternal(ctx context.Context, reader client.Reader, otherNamespace string) (string, error) {
49+
if r.External != "" && r.Name != "" {
50+
return "", fmt.Errorf("cannot specify both name and external on %s reference", EnvgroupAttachmentGVK.Kind)
51+
}
52+
// From given External
53+
if r.External != "" {
54+
if _, _, err := ParseEnvgroupAttachmentExternalRef(r.External); err != nil {
55+
return "", err
56+
}
57+
return r.External, nil
58+
}
59+
60+
// From the Config Connector object
61+
if r.Namespace == "" {
62+
r.Namespace = otherNamespace
63+
}
64+
key := types.NamespacedName{Name: r.Name, Namespace: r.Namespace}
65+
u := &unstructured.Unstructured{}
66+
u.SetGroupVersionKind(EnvgroupAttachmentGVK)
67+
if err := reader.Get(ctx, key, u); err != nil {
68+
if apierrors.IsNotFound(err) {
69+
return "", k8s.NewReferenceNotFoundError(u.GroupVersionKind(), key)
70+
}
71+
return "", fmt.Errorf("reading referenced %s %s: %w", EnvgroupAttachmentGVK, key, err)
72+
}
73+
// Get external from status.externalRef. This is the most trustworthy place.
74+
actualExternalRef, found, err := unstructured.NestedString(u.Object, "status", "externalRef")
75+
if err != nil {
76+
return "", fmt.Errorf("reading status.externalRef: %w", err)
77+
}
78+
if !found {
79+
return "", fmt.Errorf("status.externalRef is not found")
80+
}
81+
if actualExternalRef == "" {
82+
return "", k8s.NewReferenceNotReadyError(u.GroupVersionKind(), key)
83+
}
84+
r.External = actualExternalRef
85+
return r.External, nil
86+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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+
refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
19+
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
20+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
)
22+
23+
var EnvgroupAttachmentGVK = GroupVersion.WithKind("ApigeeEnvgroupAttachment")
24+
25+
// ApigeeEnvgroupAttachmentSpec defines the desired state of EnvgroupAttachment
26+
type ApigeeEnvgroupAttachmentSpec struct {
27+
// +required
28+
OrganizationRef *refs.ApigeeOrganizationRef `json:"organizationRef"`
29+
30+
// Immutable. The Apigee environment group which will host the environment.
31+
EnvgroupRef *EnvironmentGroupRef `json:"envgroup"`
32+
33+
// Immutable. The Apigee environment to attach to.
34+
EnvironmentRef *EnvironmentRef `json:"environment"`
35+
36+
// The EnvgroupAttachment name. If not given, the metadata.name will be used.
37+
ResourceID *string `json:"resourceID,omitempty"`
38+
}
39+
40+
// ApigeeEnvgroupAttachmentStatus defines the observed state of EnvgroupAttachment
41+
type ApigeeEnvgroupAttachmentStatus struct {
42+
// Conditions represent the latest available observations of the
43+
// EnvgroupAttachment's current state.
44+
Conditions []v1alpha1.Condition `json:"conditions,omitempty"`
45+
46+
// ObservedGeneration is the generation of the resource that was most recently observed by the Config Connector controller.
47+
// If this is equal to metadata.generation, then that means that the current reported status reflects the most recent desired state of the resource.
48+
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
49+
50+
// A unique specifier for the EnvgroupAttachment resource.
51+
// +optional
52+
ExternalRef *string `json:"externalRef,omitempty"`
53+
}
54+
55+
// +genclient
56+
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
57+
58+
// ApigeeEnvgroupAttachment is the Schema for the EnvgroupAttachments API
59+
// +genclient
60+
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
61+
// +kubebuilder:resource:categories=gcp,shortName=gcpapigeeenvgroupattachment;gcpapigeeenvgroupattachments
62+
// +kubebuilder:subresource:status
63+
// +kubebuilder:metadata:labels="cnrm.cloud.google.com/managed-by-kcc=true";"cnrm.cloud.google.com/system=true";"cnrm.cloud.google.com/stability-level=alpha"
64+
// +kubebuilder:printcolumn:name="Age",JSONPath=".metadata.creationTimestamp",type="date"
65+
// +kubebuilder:printcolumn:name="Ready",JSONPath=".status.conditions[?(@.type=='Ready')].status",type="string",description="When 'True', the most recent reconcile of the resource succeeded"
66+
// +kubebuilder:printcolumn:name="Status",JSONPath=".status.conditions[?(@.type=='Ready')].reason",type="string",description="The reason for the value in 'Ready'"
67+
// +kubebuilder:printcolumn:name="Status Age",JSONPath=".status.conditions[?(@.type=='Ready')].lastTransitionTime",type="date",description="The last transition time for the value in 'Status'"
68+
type ApigeeEnvgroupAttachment struct {
69+
metav1.TypeMeta `json:",inline"`
70+
metav1.ObjectMeta `json:"metadata,omitempty"`
71+
72+
Spec ApigeeEnvgroupAttachmentSpec `json:"spec,omitempty"`
73+
Status ApigeeEnvgroupAttachmentStatus `json:"status,omitempty"`
74+
}
75+
76+
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
77+
78+
// ApigeeEnvgroupAttachmentList contains a list of EnvgroupAttachment
79+
type ApigeeEnvgroupAttachmentList struct {
80+
metav1.TypeMeta `json:",inline"`
81+
metav1.ListMeta `json:"metadata,omitempty"`
82+
Items []ApigeeEnvgroupAttachment `json:"items"`
83+
}
84+
85+
func init() {
86+
SchemeBuilder.Register(&ApigeeEnvgroupAttachment{}, &ApigeeEnvgroupAttachmentList{})
87+
}

0 commit comments

Comments
 (0)