Skip to content

Commit 9bc708d

Browse files
committed
Add api, fuzzer and mapper for TagsTagBinding
1 parent e364c69 commit 9bc708d

13 files changed

Lines changed: 747 additions & 16 deletions

apis/tags/v1beta1/doc.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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+
// +kcc:proto=google.cloud.resourcemanager.v3
16+
package v1beta1

apis/tags/v1beta1/generate.sh

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/bin/bash
2+
# Copyright 2025 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
17+
set -o errexit
18+
set -o nounset
19+
set -o pipefail
20+
21+
REPO_ROOT="$(git rev-parse --show-toplevel)"
22+
cd ${REPO_ROOT}/dev/tools/controllerbuilder
23+
24+
go run . generate-types \
25+
--service google.cloud.resourcemanager.v3 \
26+
--api-version tags.cnrm.cloud.google.com/v1beta1 \
27+
--resource TagsTagBinding:TagBinding
28+
29+
go run . generate-mapper --service google.cloud.resourcemanager.v3 --api-version tags.cnrm.cloud.google.com/v1beta1
30+
31+
cd ${REPO_ROOT}
32+
dev/tasks/generate-crds
33+
34+
go run -mod=readonly golang.org/x/tools/cmd/goimports@latest -w pkg/controller/direct/tags/
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
// +kubebuilder:object:generate=true
16+
// +groupName=tags.cnrm.cloud.google.com
17+
package v1beta1
18+
19+
import (
20+
"k8s.io/apimachinery/pkg/runtime/schema"
21+
"sigs.k8s.io/controller-runtime/pkg/scheme"
22+
)
23+
24+
var (
25+
// GroupVersion is group version used to register these objects
26+
GroupVersion = schema.GroupVersion{Group: "tags.cnrm.cloud.google.com", Version: "v1beta1"}
27+
28+
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
29+
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
30+
31+
// AddToScheme adds the types in this group-version to the given scheme.
32+
AddToScheme = SchemeBuilder.AddToScheme
33+
)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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 v1beta1
16+
17+
import (
18+
"context"
19+
"fmt"
20+
21+
"sigs.k8s.io/controller-runtime/pkg/client"
22+
)
23+
24+
// TagBindingIdentity defines the resource reference to TagsTagBinding, which "External" field
25+
// holds the GCP identifier for the KRM object.
26+
type TagBindingIdentity struct {
27+
parent string
28+
tagValue string
29+
}
30+
31+
func (i *TagBindingIdentity) String() string {
32+
return fmt.Sprintf("tagBindings/%s/%s", i.parent, i.tagValue)
33+
}
34+
35+
func (i *TagBindingIdentity) Parent() string {
36+
return i.parent
37+
}
38+
39+
func (i *TagBindingIdentity) TagValue() string {
40+
return i.tagValue
41+
}
42+
43+
// New builds a TagBindingIdentity from the Config Connector TagBinding object.
44+
func NewTagBindingIdentity(ctx context.Context, reader client.Reader, obj *TagsTagBinding) (*TagBindingIdentity, error) {
45+
parent, err := resolveParent(ctx, reader, obj)
46+
if err != nil {
47+
return nil, err
48+
}
49+
50+
tagValue, err := resolveTagValue(ctx, reader, obj)
51+
if err != nil {
52+
return nil, err
53+
}
54+
55+
return &TagBindingIdentity{
56+
parent: parent,
57+
tagValue: tagValue,
58+
}, nil
59+
}
60+
61+
func resolveParent(ctx context.Context, reader client.Reader, obj *TagsTagBinding) (string, error) {
62+
if obj.Spec.ParentRef.External != "" {
63+
return obj.Spec.ParentRef.External, nil
64+
}
65+
if obj.Spec.ParentRef.Name != "" {
66+
// TODO: This is not quite right, we need to resolve the project number.
67+
// For now, we will just use the name.
68+
return fmt.Sprintf("//cloudresourcemanager.googleapis.com/projects/%s", obj.Spec.ParentRef.Name), nil
69+
}
70+
return "", fmt.Errorf("parentRef is required")
71+
}
72+
73+
func resolveTagValue(ctx context.Context, reader client.Reader, obj *TagsTagBinding) (string, error) {
74+
if obj.Spec.TagValueRef.External != "" {
75+
return obj.Spec.TagValueRef.External, nil
76+
}
77+
if obj.Spec.TagValueRef.Name != "" {
78+
// TODO: This is not quite right, we need to resolve the tag value name.
79+
// For now, we will just use the name.
80+
return fmt.Sprintf("tagValues/%s", obj.Spec.TagValueRef.Name), nil
81+
}
82+
return "", fmt.Errorf("tagValueRef is required")
83+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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 v1beta1
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 = &TagsTagValueRef{}
30+
31+
// NormalizedExternal provision the "External" value for other resource that depends on TagsTagValue.
32+
// If the "External" is given in the other resource's spec.TagsTagValueRef, the given value will be used.
33+
// Otherwise, the "Name" and "Namespace" will be used to query the actual TagsTagValue object from the cluster.
34+
func (r *TagsTagValueRef) NormalizedExternal(ctx context.Context, reader client.Reader, otherNamespace string) (string, error) {
35+
if r.External != "" && r.Name != "" {
36+
return "", fmt.Errorf("cannot specify both name and external on %s reference", "TagsTagValue")
37+
}
38+
// From given External
39+
if r.External != "" {
40+
return r.External, nil
41+
}
42+
43+
// From the Config Connector object
44+
if r.Namespace == "" {
45+
r.Namespace = otherNamespace
46+
}
47+
key := types.NamespacedName{Name: r.Name, Namespace: r.Namespace}
48+
u := &unstructured.Unstructured{}
49+
u.SetGroupVersionKind(GroupVersion.WithKind("TagsTagValue"))
50+
if err := reader.Get(ctx, key, u); err != nil {
51+
if apierrors.IsNotFound(err) {
52+
return "", k8s.NewReferenceNotFoundError(u.GroupVersionKind(), key)
53+
}
54+
return "", fmt.Errorf("reading referenced %s %s: %w", "TagsTagValue", key, err)
55+
}
56+
// Get external from status.selfLink. This is the most trustworthy place.
57+
actualExternalRef, _, err := unstructured.NestedString(u.Object, "status", "selfLink")
58+
if err != nil {
59+
return "", fmt.Errorf("reading status.selfLink: %w", err)
60+
}
61+
if actualExternalRef == "" {
62+
return "", k8s.NewReferenceNotReadyError(u.GroupVersionKind(), key)
63+
}
64+
r.External = actualExternalRef
65+
return r.External, nil
66+
}
Lines changed: 111 additions & 0 deletions
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 v1beta1
16+
17+
import (
18+
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
19+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
20+
)
21+
22+
var TagsTagBindingGVK = GroupVersion.WithKind("TagsTagBinding")
23+
24+
// TagsTagBindingSpec defines the desired state of TagsTagBinding
25+
// +kcc:spec:proto=google.cloud.resourcemanager.v3.TagBinding
26+
type TagsTagBindingSpec struct {
27+
// +kcc:ref=Project
28+
ParentRef *ParentRef `json:"parentRef"`
29+
30+
// +kcc:ref=TagsTagValue
31+
TagValueRef *TagsTagValueRef `json:"tagValueRef"`
32+
33+
// The service-generated name of the resource. Used for acquisition only. Leave unset to create a new
34+
// resource.
35+
ResourceID *string `json:"resourceID,omitempty"`
36+
}
37+
38+
// ParentRef is a reference to a parent resource.
39+
// +kcc:ref=Project
40+
type ParentRef struct {
41+
// Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
42+
Name string `json:"name,omitempty"`
43+
44+
// Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
45+
Namespace string `json:"namespace,omitempty"`
46+
47+
// Allowed value: string of the format `//cloudresourcemanager.googleapis.com/projects/{{value}}`,
48+
// where {{value}} is the `number` field of a `Project` resource.
49+
External string `json:"external,omitempty"`
50+
}
51+
52+
// TagsTagBindingStatus defines the config connector machine state of TagsTagBinding
53+
type TagsTagBindingStatus struct {
54+
/* Conditions represent the latest available observations of the
55+
object's current state. */
56+
Conditions []v1alpha1.Condition `json:"conditions,omitempty"`
57+
58+
// 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.
59+
ObservedGeneration *int64 `json:"observedGeneration,omitempty"`
60+
61+
// The generated id for the TagBinding. This is a string of the form: tagBindings/{full-resource-name}/{tag-value-name}.
62+
Name *string `json:"name,omitempty"`
63+
64+
// A unique specifier for the TagsTagBinding resource in GCP.
65+
ExternalRef *string `json:"externalRef,omitempty"`
66+
67+
// ObservedState is the state of the resource as most recently observed in GCP.
68+
// ObservedState *TagsTagBindingObservedState `json:"observedState,omitempty"`
69+
}
70+
71+
// TagsTagBindingObservedState is the state of the TagsTagBinding resource as most recently observed in GCP.
72+
// +kcc:observedstate:proto=google.cloud.resourcemanager.v3.TagBinding
73+
type TagsTagBindingObservedState struct {
74+
// Output only. The name of the TagBinding. This is a String of the form:
75+
// `tagBindings/{full-resource-name}/{tag-value-name}`
76+
// (e.g. `tagBindings/%2F%2Fcloudresourcemanager.googleapis.com%2Fprojects%2F123/tagValues/456`).
77+
Name *string `json:"name,omitempty"`
78+
}
79+
80+
// +genclient
81+
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
82+
// +kubebuilder:resource:categories=gcp,shortName=gcptagstagbinding;gcptagstagbindings
83+
// +kubebuilder:subresource:status
84+
// +kubebuilder:metadata:labels="cnrm.cloud.google.com/managed-by-kcc=true";"cnrm.cloud.google.com/stability-level=alpha";"cnrm.cloud.google.com/system=true";"cnrm.cloud.google.com/tf2crd=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+
// TagsTagBinding is the Schema for the TagsTagBinding API
91+
// +k8s:openapi-gen=true
92+
type TagsTagBinding struct {
93+
metav1.TypeMeta `json:",inline"`
94+
metav1.ObjectMeta `json:"metadata,omitempty"`
95+
96+
// +required
97+
Spec TagsTagBindingSpec `json:"spec,omitempty"`
98+
Status TagsTagBindingStatus `json:"status,omitempty"`
99+
}
100+
101+
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
102+
// TagsTagBindingList contains a list of TagsTagBinding
103+
type TagsTagBindingList struct {
104+
metav1.TypeMeta `json:",inline"`
105+
metav1.ListMeta `json:"metadata,omitempty"`
106+
Items []TagsTagBinding `json:"items"`
107+
}
108+
109+
func init() {
110+
SchemeBuilder.Register(&TagsTagBinding{}, &TagsTagBindingList{})
111+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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 v1beta1
16+
17+
// TagsTagValueRef is a reference to a TagsTagValue resource.
18+
// +kcc:ref=TagsTagValue
19+
type TagsTagValueRef struct {
20+
// Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
21+
Name string `json:"name,omitempty"`
22+
23+
// Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
24+
Namespace string `json:"namespace,omitempty"`
25+
26+
// Allowed value: string of the format `tagValues/{{value}}`,
27+
// where {{value}} is the `name` field of a `TagsTagValue` resource.
28+
External string `json:"external,omitempty"`
29+
}

0 commit comments

Comments
 (0)