Skip to content

Commit ffb5472

Browse files
Merge pull request GoogleCloudPlatform#2865 from acpana/acpana/dataexchangelisting-2
feat: types for direct BigQueryAnalyticsHubListing
2 parents 3ef9098 + d0d3329 commit ffb5472

12 files changed

+1648
-125
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// Copyright 2024 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+
refsv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
23+
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
24+
apierrors "k8s.io/apimachinery/pkg/api/errors"
25+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
26+
"k8s.io/apimachinery/pkg/types"
27+
"sigs.k8s.io/controller-runtime/pkg/client"
28+
)
29+
30+
var _ refsv1beta1.ExternalNormalizer = &BigQueryAnalyticsHubListingRef{}
31+
32+
// BigQueryAnalyticsHubListingRef defines the resource reference to BigQueryAnalyticsHubListing, which "External" field
33+
// holds the GCP identifier for the KRM object.
34+
type BigQueryAnalyticsHubListingRef struct {
35+
// A reference to an externally managed BigQueryAnalyticsHubListing resource.
36+
// Should be in the format "projects/<projectID>/locations/<location>/listings/<listingID>".
37+
External string `json:"external,omitempty"`
38+
39+
// The name of a BigQueryAnalyticsHubListing resource.
40+
Name string `json:"name,omitempty"`
41+
42+
// The namespace of a BigQueryAnalyticsHubListing resource.
43+
Namespace string `json:"namespace,omitempty"`
44+
45+
parent *BigQueryAnalyticsHubListingParent
46+
}
47+
48+
// NormalizedExternal provision the "External" value for other resource that depends on BigQueryAnalyticsHubListing.
49+
// If the "External" is given in the other resource's spec.BigQueryAnalyticsHubListingRef, the given value will be used.
50+
// Otherwise, the "Name" and "Namespace" will be used to query the actual BigQueryAnalyticsHubListing object from the cluster.
51+
func (r *BigQueryAnalyticsHubListingRef) NormalizedExternal(ctx context.Context, reader client.Reader, otherNamespace string) (string, error) {
52+
if r.External != "" && r.Name != "" {
53+
return "", fmt.Errorf("cannot specify both name and external on %s reference", BigQueryAnalyticsHubListingGVK.Kind)
54+
}
55+
// From given External
56+
if r.External != "" {
57+
if _, _, err := parseBigQueryAnalyticsHubListingExternal(r.External); err != nil {
58+
return "", err
59+
}
60+
return r.External, nil
61+
}
62+
63+
// From the Config Connector object
64+
if r.Namespace == "" {
65+
r.Namespace = otherNamespace
66+
}
67+
key := types.NamespacedName{Name: r.Name, Namespace: r.Namespace}
68+
u := &unstructured.Unstructured{}
69+
u.SetGroupVersionKind(BigQueryAnalyticsHubListingGVK)
70+
if err := reader.Get(ctx, key, u); err != nil {
71+
if apierrors.IsNotFound(err) {
72+
return "", k8s.NewReferenceNotFoundError(u.GroupVersionKind(), key)
73+
}
74+
return "", fmt.Errorf("reading referenced %s %s: %w", BigQueryAnalyticsHubListingGVK, key, err)
75+
}
76+
// Get external from status.externalRef. This is the most trustworthy place.
77+
actualExternalRef, _, err := unstructured.NestedString(u.Object, "status", "externalRef")
78+
if err != nil {
79+
return "", fmt.Errorf("reading status.externalRef: %w", err)
80+
}
81+
if actualExternalRef == "" {
82+
return "", fmt.Errorf("BigQueryAnalyticsHubListing is not ready yet")
83+
}
84+
r.External = actualExternalRef
85+
return r.External, nil
86+
}
87+
88+
// New builds a BigQueryAnalyticsHubListingRef from the Config Connector BigQueryAnalyticsHubListing object.
89+
func NewBigQueryAnalyticsHubListingRef(ctx context.Context, reader client.Reader, obj *BigQueryAnalyticsHubListing) (*BigQueryAnalyticsHubListingRef, error) {
90+
id := &BigQueryAnalyticsHubListingRef{}
91+
92+
// Get Parent
93+
projectRef, err := refsv1beta1.ResolveProject(ctx, reader, obj, obj.Spec.ProjectRef)
94+
if err != nil {
95+
return nil, err
96+
}
97+
projectID := projectRef.ProjectID
98+
if projectID == "" {
99+
return nil, fmt.Errorf("cannot resolve project")
100+
}
101+
location := obj.Spec.Location
102+
id.parent = &BigQueryAnalyticsHubListingParent{ProjectID: projectID, Location: location}
103+
104+
// Get desired ID
105+
resourceID := valueOf(obj.Spec.ResourceID)
106+
if resourceID == "" {
107+
resourceID = obj.GetName()
108+
}
109+
if resourceID == "" {
110+
return nil, fmt.Errorf("cannot resolve resource ID")
111+
}
112+
113+
// Use approved External
114+
externalRef := valueOf(obj.Status.ExternalRef)
115+
if externalRef == "" {
116+
id.External = asBigQueryAnalyticsHubListingExternal(id.parent, resourceID)
117+
return id, nil
118+
}
119+
120+
// Validate desired with actual
121+
actualParent, actualResourceID, err := parseBigQueryAnalyticsHubListingExternal(externalRef)
122+
if err != nil {
123+
return nil, err
124+
}
125+
if actualParent.ProjectID != projectID {
126+
return nil, fmt.Errorf("spec.projectRef changed, expect %s, got %s", actualParent.ProjectID, projectID)
127+
}
128+
if actualParent.Location != location {
129+
return nil, fmt.Errorf("spec.location changed, expect %s, got %s", actualParent.Location, location)
130+
}
131+
if actualResourceID != resourceID {
132+
return nil, fmt.Errorf("cannot reset `metadata.name` or `spec.resourceID` to %s, since it has already assigned to %s",
133+
resourceID, actualResourceID)
134+
}
135+
id.External = externalRef
136+
id.parent = &BigQueryAnalyticsHubListingParent{ProjectID: projectID, Location: location}
137+
return id, nil
138+
}
139+
140+
func (r *BigQueryAnalyticsHubListingRef) Parent() (*BigQueryAnalyticsHubListingParent, error) {
141+
if r.parent != nil {
142+
return r.parent, nil
143+
}
144+
if r.External != "" {
145+
parent, _, err := parseBigQueryAnalyticsHubListingExternal(r.External)
146+
if err != nil {
147+
return nil, err
148+
}
149+
return parent, nil
150+
}
151+
return nil, fmt.Errorf("BigQueryAnalyticsHubListingRef not initialized from `NewBigQueryAnalyticsHubListingRef` or `NormalizedExternal`")
152+
}
153+
154+
type BigQueryAnalyticsHubListingParent struct {
155+
ProjectID string
156+
Location string
157+
}
158+
159+
func (p *BigQueryAnalyticsHubListingParent) String() string {
160+
return "projects/" + p.ProjectID + "/locations/" + p.Location
161+
}
162+
163+
func asBigQueryAnalyticsHubListingExternal(parent *BigQueryAnalyticsHubListingParent, resourceID string) (external string) {
164+
return parent.String() + "/listings/" + resourceID
165+
}
166+
167+
func parseBigQueryAnalyticsHubListingExternal(external string) (parent *BigQueryAnalyticsHubListingParent, resourceID string, err error) {
168+
external = strings.TrimPrefix(external, "/")
169+
tokens := strings.Split(external, "/")
170+
if len(tokens) != 6 || tokens[0] != "projects" || tokens[2] != "locations" || tokens[4] != "listing" {
171+
return nil, "", fmt.Errorf("format of BigQueryAnalyticsHubListing external=%q was not known (use projects/<projectId>/locations/<location>/listings/<listingID>)", external)
172+
}
173+
parent = &BigQueryAnalyticsHubListingParent{
174+
ProjectID: tokens[1],
175+
Location: tokens[3],
176+
}
177+
resourceID = tokens[5]
178+
return parent, resourceID, nil
179+
}
180+
181+
func valueOf[T any](t *T) T {
182+
var zeroVal T
183+
if t == nil {
184+
return zeroVal
185+
}
186+
return *t
187+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
// Copyright 2024 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+
refv1beta1 "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 BigQueryAnalyticsHubListingGVK = GroupVersion.WithKind("BigQueryAnalyticsHubDataExchangeListing")
24+
25+
type BigQueryDatasetSource struct {
26+
// +required
27+
// Resource name of the dataset source for this listing.
28+
// e.g. `projects/myproject/datasets/123`
29+
Dataset *refv1beta1.BigQueryDatasetRef `json:"datasetRef,omitempty"`
30+
31+
// Optional. Resources in this dataset that are selectively shared.
32+
// If this field is empty, then the entire dataset (all resources) are
33+
// shared. This field is only valid for data clean room exchanges.
34+
SelectedResources []Listing_BigQueryDatasetSource_SelectedResource `json:"selectedResources,omitempty"`
35+
36+
// Optional. If set, restricted export policy will be propagated and
37+
// enforced on the linked dataset.
38+
RestrictedExportPolicy *Listing_BigQueryDatasetSource_RestrictedExportPolicy `json:"restrictedExportPolicy,omitempty"`
39+
}
40+
type Source struct {
41+
// One of the following fields must be set.
42+
BigQueryDatasetSource *BigQueryDatasetSource `json:"bigQueryDatasetSource,omitempty"`
43+
44+
// NOT YET
45+
// PubsubTopicSource *PubsubTopicSource `json:"pubsubTopicSource,omitempty"`
46+
}
47+
48+
// BigQueryAnalyticsHubListingSpec defines the desired state of BigQueryAnalyticsHubDataExchangeListing
49+
// +kcc:proto=google.cloud.bigquery.analyticshub.v1.Listing
50+
type BigQueryAnalyticsHubListingSpec struct {
51+
// +required
52+
Source *Source `json:"source,omitempty"`
53+
54+
// +required
55+
// Required. Human-readable display name of the listing. The display name must
56+
// contain only Unicode letters, numbers (0-9), underscores (_), dashes (-),
57+
// spaces ( ), ampersands (&) and can't start or end with spaces. Default
58+
// value is an empty string. Max length: 63 bytes.
59+
DisplayName *string `json:"displayName,omitempty"`
60+
61+
// Optional. Short description of the listing. The description must contain only
62+
// Unicode characters or tabs (HT), new lines (LF), carriage returns (CR), and
63+
// page breaks (FF). Default value is an empty string. Max length: 2000 bytes.
64+
Description *string `json:"description,omitempty"`
65+
66+
// Optional. Email or URL of the primary point of contact of the listing.
67+
// Max Length: 1000 bytes.
68+
PrimaryContact *string `json:"primaryContact,omitempty"`
69+
70+
// Optional. Documentation describing the listing.
71+
Documentation *string `json:"documentation,omitempty"`
72+
73+
// Optional. Details of the data provider who owns the source data.
74+
DataProvider *DataProvider `json:"dataProvider,omitempty"`
75+
76+
// Optional. Categories of the listing. Up to two categories are allowed.
77+
Categories []string `json:"categories,omitempty"`
78+
79+
// Optional. Details of the publisher who owns the listing and who can share
80+
// the source data.
81+
Publisher *Publisher `json:"publisher,omitempty"`
82+
83+
// Optional. Email or URL of the request access of the listing.
84+
// Subscribers can use this reference to request access.
85+
// Max Length: 1000 bytes.
86+
RequestAccess *string `json:"requestAccess,omitempty"`
87+
88+
// Not yet
89+
// // Optional. If set, restricted export configuration will be propagated and
90+
// // enforced on the linked dataset.
91+
// RestrictedExportConfig *Listing_RestrictedExportConfig `json:"restrictedExportConfig,omitempty"`
92+
93+
// Optional. Type of discovery of the listing on the discovery page.
94+
DiscoveryType *string `json:"discoveryType,omitempty"`
95+
96+
// Not yet
97+
// // Optional. Base64 encoded image representing the listing. Max Size: 3.0MiB
98+
// // Expected image dimensions are 512x512 pixels, however the API only
99+
// // performs validation on size of the encoded data.
100+
// // Note: For byte fields, the contents of the field are base64-encoded (which
101+
// // increases the size of the data by 33-36%) when using JSON on the wire.
102+
// Icon []byte `json:"icon,omitempty"`
103+
104+
// Not yet
105+
// Source *Listing_Source `json:"source,omitempty"`
106+
107+
/* Immutable. The name of the location this data exchange. */
108+
// +required
109+
Location string `json:"location"`
110+
111+
// +required
112+
ProjectRef *refv1beta1.ProjectRef `json:"projectRef"`
113+
114+
// +required
115+
DataExchangeRef *refv1beta1.DataExchangeRef `json:"dataExchangeRef"`
116+
117+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable"
118+
// Immutable.
119+
// The BigQueryAnalyticsHubDataExchangeListing name. If not given, the metadata.name will be used.
120+
// + optional
121+
ResourceID *string `json:"resourceID,omitempty"`
122+
}
123+
124+
// BigQueryAnalyticsHubListingStatus defines the config connector machine state of BigQueryAnalyticsHubDataExchangeListing
125+
type BigQueryAnalyticsHubListingStatus struct {
126+
/* Conditions represent the latest available observations of the
127+
object's current state. */
128+
Conditions []v1alpha1.Condition `json:"conditions,omitempty"`
129+
130+
// 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.
131+
ObservedGeneration *int64 `json:"observedGeneration,omitempty"`
132+
133+
// A unique specifier for the BigQueryAnalyticsHubDataExchangeListing resource in GCP.
134+
ExternalRef *string `json:"externalRef,omitempty"`
135+
136+
// ObservedState is the state of the resource as most recently observed in GCP.
137+
ObservedState *BigQueryAnalyticsHubListingObservedState `json:"observedState,omitempty"`
138+
}
139+
140+
// BigQueryAnalyticsHubDataExchangeListingSpec defines the desired state of BigQueryAnalyticsHubDataExchangeListing
141+
// +kcc:proto=google.cloud.bigquery.analyticshub.v1.Listing
142+
type BigQueryAnalyticsHubListingObservedState struct {
143+
// This field is in the same format as our externalRef! So it's redundant.
144+
// // Output only. The resource name of the data exchange.
145+
// // e.g. `projects/myproject/locations/US/dataExchanges/123/listing/456`.
146+
// Name *string `json:"name,omitempty"`
147+
148+
// Output only. Current state of the listing.
149+
State *string `json:"state,omitempty"`
150+
}
151+
152+
// +genclient
153+
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
154+
// +kubebuilder:resource:categories=gcp
155+
// +kubebuilder:subresource:status
156+
// +kubebuilder:metadata:labels="cnrm.cloud.google.com/managed-by-kcc=true";"cnrm.cloud.google.com/system=true"
157+
// +kubebuilder:printcolumn:name="Age",JSONPath=".metadata.creationTimestamp",type="date"
158+
// +kubebuilder:printcolumn:name="Ready",JSONPath=".status.conditions[?(@.type=='Ready')].status",type="string",description="When 'True', the most recent reconcile of the resource succeeded"
159+
// +kubebuilder:printcolumn:name="Status",JSONPath=".status.conditions[?(@.type=='Ready')].reason",type="string",description="The reason for the value in 'Ready'"
160+
// +kubebuilder:printcolumn:name="Status Age",JSONPath=".status.conditions[?(@.type=='Ready')].lastTransitionTime",type="date",description="The last transition time for the value in 'Status'"
161+
162+
// BigQueryAnalyticsHubListing is the Schema for the BigQueryAnalyticsHubListing API
163+
// +k8s:openapi-gen=true
164+
type BigQueryAnalyticsHubListing struct {
165+
metav1.TypeMeta `json:",inline"`
166+
metav1.ObjectMeta `json:"metadata,omitempty"`
167+
168+
Spec BigQueryAnalyticsHubListingSpec `json:"spec,omitempty"`
169+
Status BigQueryAnalyticsHubListingStatus `json:"status,omitempty"`
170+
}
171+
172+
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
173+
// BigQueryAnalyticsHubListingList contains a list of BigQueryAnalyticsHubDataExchangeListing
174+
type BigQueryAnalyticsHubListingList struct {
175+
metav1.TypeMeta `json:",inline"`
176+
metav1.ListMeta `json:"metadata,omitempty"`
177+
Items []BigQueryAnalyticsHubListing `json:"items"`
178+
}
179+
180+
func init() {
181+
SchemeBuilder.Register(&BigQueryAnalyticsHubListing{}, &BigQueryAnalyticsHubListingList{})
182+
}

0 commit comments

Comments
 (0)