From 95955bdb7067ae15a4a23c9232d7f5e933aa77c7 Mon Sep 17 00:00:00 2001 From: Alex Pana <8968914+acpana@users.noreply.github.com> Date: Wed, 23 Oct 2024 20:52:55 +0000 Subject: [PATCH 1/7] api: rework listing types Signed-off-by: Alex Pana <8968914+acpana@users.noreply.github.com> --- .../v1alpha1/listing_reference.go | 2 +- .../v1alpha1/listing_types.go | 30 ++- .../v1alpha1/types.generated.go | 27 +-- .../v1alpha1/zz_generated.deepcopy.go | 72 +++--- ...eryanalyticshub.cnrm.cloud.google.com.yaml | 49 ++-- .../bigqueryanalyticshublisting_types.go | 28 +-- .../v1alpha1/zz_generated.deepcopy.go | 77 +------ .../direct/bigqueryanalyticshub/mapper.go | 214 +++++++++++++++++- 8 files changed, 318 insertions(+), 181 deletions(-) diff --git a/apis/bigqueryanalyticshub/v1alpha1/listing_reference.go b/apis/bigqueryanalyticshub/v1alpha1/listing_reference.go index 5692d9d72b..f5e12b3c37 100644 --- a/apis/bigqueryanalyticshub/v1alpha1/listing_reference.go +++ b/apis/bigqueryanalyticshub/v1alpha1/listing_reference.go @@ -167,7 +167,7 @@ func asBigQueryAnalyticsHubListingExternal(parent *BigQueryAnalyticsHubListingPa func parseBigQueryAnalyticsHubListingExternal(external string) (parent *BigQueryAnalyticsHubListingParent, resourceID string, err error) { external = strings.TrimPrefix(external, "/") tokens := strings.Split(external, "/") - if len(tokens) != 6 || tokens[0] != "projects" || tokens[2] != "locations" || tokens[4] != "listing" { + if len(tokens) != 6 || tokens[0] != "projects" || tokens[2] != "locations" || tokens[4] != "listings" { return nil, "", fmt.Errorf("format of BigQueryAnalyticsHubListing external=%q was not known (use projects//locations//listings/)", external) } parent = &BigQueryAnalyticsHubListingParent{ diff --git a/apis/bigqueryanalyticshub/v1alpha1/listing_types.go b/apis/bigqueryanalyticshub/v1alpha1/listing_types.go index 3b01cd2895..c34ffcf067 100644 --- a/apis/bigqueryanalyticshub/v1alpha1/listing_types.go +++ b/apis/bigqueryanalyticshub/v1alpha1/listing_types.go @@ -20,7 +20,16 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -var BigQueryAnalyticsHubListingGVK = GroupVersion.WithKind("BigQueryAnalyticsHubDataExchangeListing") +var BigQueryAnalyticsHubListingGVK = GroupVersion.WithKind("BigQueryAnalyticsHubListing") + +// +kcc:proto=google.cloud.bigquery.analyticshub.v1.Listing.BigQueryDatasetSource.SelectedResource +type SelectedResource struct { + // Optional. A reference to a BigQueryTable. + // Format: + // `projects/{projectId}/datasets/{datasetId}/tables/{tableId}` + // Example:"projects/test_project/datasets/test_dataset/tables/test_table" + TableRef *refv1beta1.BigQueryTableRef `json:"table,omitempty"` +} type BigQueryDatasetSource struct { // +required @@ -31,12 +40,27 @@ type BigQueryDatasetSource struct { // Optional. Resources in this dataset that are selectively shared. // If this field is empty, then the entire dataset (all resources) are // shared. This field is only valid for data clean room exchanges. - SelectedResources []Listing_BigQueryDatasetSource_SelectedResource `json:"selectedResources,omitempty"` + SelectedResources []SelectedResource `json:"selectedResources,omitempty"` // Optional. If set, restricted export policy will be propagated and // enforced on the linked dataset. - RestrictedExportPolicy *Listing_BigQueryDatasetSource_RestrictedExportPolicy `json:"restrictedExportPolicy,omitempty"` + RestrictedExportPolicy *RestrictedExportPolicy `json:"restrictedExportPolicy,omitempty"` +} + +// +kcc:proto=google.cloud.bigquery.analyticshub.v1.Listing.BigQueryDatasetSource.RestrictedExportPolicy +type RestrictedExportPolicy struct { + // Optional. If true, enable restricted export. + Enabled *bool `json:"enabled,omitempty"` + + // Optional. If true, restrict direct table access (read + // api/tabledata.list) on linked table. + RestrictDirectTableAccess *bool `json:"restrictDirectTableAccess,omitempty"` + + // Optional. If true, restrict export of query result derived from + // restricted linked dataset table. + RestrictQueryResult *bool `json:"restrictQueryResult,omitempty"` } + type Source struct { // One of the following fields must be set. BigQueryDatasetSource *BigQueryDatasetSource `json:"bigQueryDatasetSource,omitempty"` diff --git a/apis/bigqueryanalyticshub/v1alpha1/types.generated.go b/apis/bigqueryanalyticshub/v1alpha1/types.generated.go index 31c0285d6a..a12f4bbfc2 100644 --- a/apis/bigqueryanalyticshub/v1alpha1/types.generated.go +++ b/apis/bigqueryanalyticshub/v1alpha1/types.generated.go @@ -167,34 +167,11 @@ type Listing_BigQueryDatasetSource struct { // Optional. Resources in this dataset that are selectively shared. // If this field is empty, then the entire dataset (all resources) are // shared. This field is only valid for data clean room exchanges. - SelectedResources []Listing_BigQueryDatasetSource_SelectedResource `json:"selectedResources,omitempty"` + SelectedResources []SelectedResource `json:"selectedResources,omitempty"` // Optional. If set, restricted export policy will be propagated and // enforced on the linked dataset. - RestrictedExportPolicy *Listing_BigQueryDatasetSource_RestrictedExportPolicy `json:"restrictedExportPolicy,omitempty"` -} - -// +kcc:proto=google.cloud.bigquery.analyticshub.v1.Listing.BigQueryDatasetSource.RestrictedExportPolicy -type Listing_BigQueryDatasetSource_RestrictedExportPolicy struct { - // Optional. If true, enable restricted export. - Enabled *BoolValue `json:"enabled,omitempty"` - - // Optional. If true, restrict direct table access (read - // api/tabledata.list) on linked table. - RestrictDirectTableAccess *BoolValue `json:"restrictDirectTableAccess,omitempty"` - - // Optional. If true, restrict export of query result derived from - // restricted linked dataset table. - RestrictQueryResult *BoolValue `json:"restrictQueryResult,omitempty"` -} - -// +kcc:proto=google.cloud.bigquery.analyticshub.v1.Listing.BigQueryDatasetSource.SelectedResource -type Listing_BigQueryDatasetSource_SelectedResource struct { - // Optional. Format: - // For table: - // `projects/{projectId}/datasets/{datasetId}/tables/{tableId}` - // Example:"projects/test_project/datasets/test_dataset/tables/test_table" - Table *string `json:"table,omitempty"` + RestrictedExportPolicy *RestrictedExportPolicy `json:"restrictedExportPolicy,omitempty"` } // +kcc:proto=google.cloud.bigquery.analyticshub.v1.Listing.RestrictedExportConfig diff --git a/apis/bigqueryanalyticshub/v1alpha1/zz_generated.deepcopy.go b/apis/bigqueryanalyticshub/v1alpha1/zz_generated.deepcopy.go index 0895aeb71f..542ccab122 100644 --- a/apis/bigqueryanalyticshub/v1alpha1/zz_generated.deepcopy.go +++ b/apis/bigqueryanalyticshub/v1alpha1/zz_generated.deepcopy.go @@ -427,14 +427,14 @@ func (in *BigQueryDatasetSource) DeepCopyInto(out *BigQueryDatasetSource) { } if in.SelectedResources != nil { in, out := &in.SelectedResources, &out.SelectedResources - *out = make([]Listing_BigQueryDatasetSource_SelectedResource, len(*in)) + *out = make([]SelectedResource, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } if in.RestrictedExportPolicy != nil { in, out := &in.RestrictedExportPolicy, &out.RestrictedExportPolicy - *out = new(Listing_BigQueryDatasetSource_RestrictedExportPolicy) + *out = new(RestrictedExportPolicy) (*in).DeepCopyInto(*out) } } @@ -644,14 +644,14 @@ func (in *Listing_BigQueryDatasetSource) DeepCopyInto(out *Listing_BigQueryDatas } if in.SelectedResources != nil { in, out := &in.SelectedResources, &out.SelectedResources - *out = make([]Listing_BigQueryDatasetSource_SelectedResource, len(*in)) + *out = make([]SelectedResource, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } if in.RestrictedExportPolicy != nil { in, out := &in.RestrictedExportPolicy, &out.RestrictedExportPolicy - *out = new(Listing_BigQueryDatasetSource_RestrictedExportPolicy) + *out = new(RestrictedExportPolicy) (*in).DeepCopyInto(*out) } } @@ -667,57 +667,62 @@ func (in *Listing_BigQueryDatasetSource) DeepCopy() *Listing_BigQueryDatasetSour } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Listing_BigQueryDatasetSource_RestrictedExportPolicy) DeepCopyInto(out *Listing_BigQueryDatasetSource_RestrictedExportPolicy) { +func (in *Listing_RestrictedExportConfig) DeepCopyInto(out *Listing_RestrictedExportConfig) { *out = *in if in.Enabled != nil { in, out := &in.Enabled, &out.Enabled - *out = new(BoolValue) - (*in).DeepCopyInto(*out) + *out = new(bool) + **out = **in } if in.RestrictDirectTableAccess != nil { in, out := &in.RestrictDirectTableAccess, &out.RestrictDirectTableAccess - *out = new(BoolValue) - (*in).DeepCopyInto(*out) + *out = new(bool) + **out = **in } if in.RestrictQueryResult != nil { in, out := &in.RestrictQueryResult, &out.RestrictQueryResult - *out = new(BoolValue) - (*in).DeepCopyInto(*out) + *out = new(bool) + **out = **in } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Listing_BigQueryDatasetSource_RestrictedExportPolicy. -func (in *Listing_BigQueryDatasetSource_RestrictedExportPolicy) DeepCopy() *Listing_BigQueryDatasetSource_RestrictedExportPolicy { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Listing_RestrictedExportConfig. +func (in *Listing_RestrictedExportConfig) DeepCopy() *Listing_RestrictedExportConfig { if in == nil { return nil } - out := new(Listing_BigQueryDatasetSource_RestrictedExportPolicy) + out := new(Listing_RestrictedExportConfig) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Listing_BigQueryDatasetSource_SelectedResource) DeepCopyInto(out *Listing_BigQueryDatasetSource_SelectedResource) { +func (in *Publisher) DeepCopyInto(out *Publisher) { *out = *in - if in.Table != nil { - in, out := &in.Table, &out.Table + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(string) + **out = **in + } + if in.PrimaryContact != nil { + in, out := &in.PrimaryContact, &out.PrimaryContact *out = new(string) **out = **in } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Listing_BigQueryDatasetSource_SelectedResource. -func (in *Listing_BigQueryDatasetSource_SelectedResource) DeepCopy() *Listing_BigQueryDatasetSource_SelectedResource { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Publisher. +func (in *Publisher) DeepCopy() *Publisher { if in == nil { return nil } - out := new(Listing_BigQueryDatasetSource_SelectedResource) + out := new(Publisher) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Listing_RestrictedExportConfig) DeepCopyInto(out *Listing_RestrictedExportConfig) { +func (in *RestrictedExportPolicy) DeepCopyInto(out *RestrictedExportPolicy) { *out = *in if in.Enabled != nil { in, out := &in.Enabled, &out.Enabled @@ -736,37 +741,32 @@ func (in *Listing_RestrictedExportConfig) DeepCopyInto(out *Listing_RestrictedEx } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Listing_RestrictedExportConfig. -func (in *Listing_RestrictedExportConfig) DeepCopy() *Listing_RestrictedExportConfig { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestrictedExportPolicy. +func (in *RestrictedExportPolicy) DeepCopy() *RestrictedExportPolicy { if in == nil { return nil } - out := new(Listing_RestrictedExportConfig) + out := new(RestrictedExportPolicy) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Publisher) DeepCopyInto(out *Publisher) { +func (in *SelectedResource) DeepCopyInto(out *SelectedResource) { *out = *in - if in.Name != nil { - in, out := &in.Name, &out.Name - *out = new(string) - **out = **in - } - if in.PrimaryContact != nil { - in, out := &in.PrimaryContact, &out.PrimaryContact - *out = new(string) + if in.TableRef != nil { + in, out := &in.TableRef, &out.TableRef + *out = new(v1beta1.BigQueryDatasetTableRef) **out = **in } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Publisher. -func (in *Publisher) DeepCopy() *Publisher { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SelectedResource. +func (in *SelectedResource) DeepCopy() *SelectedResource { if in == nil { return nil } - out := new(Publisher) + out := new(SelectedResource) in.DeepCopyInto(out) return out } diff --git a/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_bigqueryanalyticshublistings.bigqueryanalyticshub.cnrm.cloud.google.com.yaml b/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_bigqueryanalyticshublistings.bigqueryanalyticshub.cnrm.cloud.google.com.yaml index a7f1bac561..055adc95ac 100644 --- a/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_bigqueryanalyticshublistings.bigqueryanalyticshub.cnrm.cloud.google.com.yaml +++ b/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_bigqueryanalyticshublistings.bigqueryanalyticshub.cnrm.cloud.google.com.yaml @@ -227,27 +227,15 @@ spec: properties: enabled: description: Optional. If true, enable restricted export. - properties: - value: - description: The bool value. - type: boolean - type: object + type: boolean restrictDirectTableAccess: description: Optional. If true, restrict direct table access (read api/tabledata.list) on linked table. - properties: - value: - description: The bool value. - type: boolean - type: object + type: boolean restrictQueryResult: description: Optional. If true, restrict export of query result derived from restricted linked dataset table. - properties: - value: - description: The bool value. - type: boolean - type: object + type: boolean type: object selectedResources: description: Optional. Resources in this dataset that are @@ -257,9 +245,36 @@ spec: items: properties: table: - description: 'Optional. Format: For table: `projects/{projectId}/datasets/{datasetId}/tables/{tableId}` + description: 'Optional. A reference to a BigQueryTable. + Format: `projects/{projectId}/datasets/{datasetId}/tables/{tableId}` Example:"projects/test_project/datasets/test_dataset/tables/test_table"' - type: string + oneOf: + - not: + required: + - external + required: + - name + - not: + anyOf: + - required: + - name + - required: + - namespace + required: + - external + properties: + external: + description: If provided must be in the format `projects/{projectId}/datasets/{datasetId}/tables/{tableId}`. + type: string + name: + description: The `metadata.name` field of a `BigQueryTable` + resource. + type: string + namespace: + description: The `metadata.namespace` field of a + `BigQueryTable` resource. + type: string + type: object type: object type: array required: diff --git a/pkg/clients/generated/apis/bigqueryanalyticshub/v1alpha1/bigqueryanalyticshublisting_types.go b/pkg/clients/generated/apis/bigqueryanalyticshub/v1alpha1/bigqueryanalyticshublisting_types.go index 550045da9e..87742bd9d9 100644 --- a/pkg/clients/generated/apis/bigqueryanalyticshub/v1alpha1/bigqueryanalyticshublisting_types.go +++ b/pkg/clients/generated/apis/bigqueryanalyticshub/v1alpha1/bigqueryanalyticshublisting_types.go @@ -58,12 +58,6 @@ type ListingDataProvider struct { PrimaryContact *string `json:"primaryContact,omitempty"` } -type ListingEnabled struct { - /* The bool value. */ - // +optional - Value *bool `json:"value,omitempty"` -} - type ListingPublisher struct { /* Optional. Name of the listing publisher. */ // +optional @@ -74,36 +68,24 @@ type ListingPublisher struct { PrimaryContact *string `json:"primaryContact,omitempty"` } -type ListingRestrictDirectTableAccess struct { - /* The bool value. */ - // +optional - Value *bool `json:"value,omitempty"` -} - -type ListingRestrictQueryResult struct { - /* The bool value. */ - // +optional - Value *bool `json:"value,omitempty"` -} - type ListingRestrictedExportPolicy struct { /* Optional. If true, enable restricted export. */ // +optional - Enabled *ListingEnabled `json:"enabled,omitempty"` + Enabled *bool `json:"enabled,omitempty"` /* Optional. If true, restrict direct table access (read api/tabledata.list) on linked table. */ // +optional - RestrictDirectTableAccess *ListingRestrictDirectTableAccess `json:"restrictDirectTableAccess,omitempty"` + RestrictDirectTableAccess *bool `json:"restrictDirectTableAccess,omitempty"` /* Optional. If true, restrict export of query result derived from restricted linked dataset table. */ // +optional - RestrictQueryResult *ListingRestrictQueryResult `json:"restrictQueryResult,omitempty"` + RestrictQueryResult *bool `json:"restrictQueryResult,omitempty"` } type ListingSelectedResources struct { - /* Optional. Format: For table: `projects/{projectId}/datasets/{datasetId}/tables/{tableId}` Example:"projects/test_project/datasets/test_dataset/tables/test_table" */ + /* Optional. A reference to a BigQueryTable. Format: `projects/{projectId}/datasets/{datasetId}/tables/{tableId}` Example:"projects/test_project/datasets/test_dataset/tables/test_table" */ // +optional - Table *string `json:"table,omitempty"` + Table *v1alpha1.ResourceRef `json:"table,omitempty"` } type ListingSource struct { diff --git a/pkg/clients/generated/apis/bigqueryanalyticshub/v1alpha1/zz_generated.deepcopy.go b/pkg/clients/generated/apis/bigqueryanalyticshub/v1alpha1/zz_generated.deepcopy.go index 76208fe3ea..1fb270b345 100644 --- a/pkg/clients/generated/apis/bigqueryanalyticshub/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/clients/generated/apis/bigqueryanalyticshub/v1alpha1/zz_generated.deepcopy.go @@ -245,27 +245,6 @@ func (in *ListingDataProvider) DeepCopy() *ListingDataProvider { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ListingEnabled) DeepCopyInto(out *ListingEnabled) { - *out = *in - if in.Value != nil { - in, out := &in.Value, &out.Value - *out = new(bool) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListingEnabled. -func (in *ListingEnabled) DeepCopy() *ListingEnabled { - if in == nil { - return nil - } - out := new(ListingEnabled) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ListingObservedStateStatus) DeepCopyInto(out *ListingObservedStateStatus) { *out = *in @@ -313,65 +292,23 @@ func (in *ListingPublisher) DeepCopy() *ListingPublisher { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ListingRestrictDirectTableAccess) DeepCopyInto(out *ListingRestrictDirectTableAccess) { - *out = *in - if in.Value != nil { - in, out := &in.Value, &out.Value - *out = new(bool) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListingRestrictDirectTableAccess. -func (in *ListingRestrictDirectTableAccess) DeepCopy() *ListingRestrictDirectTableAccess { - if in == nil { - return nil - } - out := new(ListingRestrictDirectTableAccess) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ListingRestrictQueryResult) DeepCopyInto(out *ListingRestrictQueryResult) { - *out = *in - if in.Value != nil { - in, out := &in.Value, &out.Value - *out = new(bool) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListingRestrictQueryResult. -func (in *ListingRestrictQueryResult) DeepCopy() *ListingRestrictQueryResult { - if in == nil { - return nil - } - out := new(ListingRestrictQueryResult) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ListingRestrictedExportPolicy) DeepCopyInto(out *ListingRestrictedExportPolicy) { *out = *in if in.Enabled != nil { in, out := &in.Enabled, &out.Enabled - *out = new(ListingEnabled) - (*in).DeepCopyInto(*out) + *out = new(bool) + **out = **in } if in.RestrictDirectTableAccess != nil { in, out := &in.RestrictDirectTableAccess, &out.RestrictDirectTableAccess - *out = new(ListingRestrictDirectTableAccess) - (*in).DeepCopyInto(*out) + *out = new(bool) + **out = **in } if in.RestrictQueryResult != nil { in, out := &in.RestrictQueryResult, &out.RestrictQueryResult - *out = new(ListingRestrictQueryResult) - (*in).DeepCopyInto(*out) + *out = new(bool) + **out = **in } return } @@ -391,7 +328,7 @@ func (in *ListingSelectedResources) DeepCopyInto(out *ListingSelectedResources) *out = *in if in.Table != nil { in, out := &in.Table, &out.Table - *out = new(string) + *out = new(k8sv1alpha1.ResourceRef) **out = **in } return diff --git a/pkg/controller/direct/bigqueryanalyticshub/mapper.go b/pkg/controller/direct/bigqueryanalyticshub/mapper.go index bce98b0020..d78f3c3d43 100644 --- a/pkg/controller/direct/bigqueryanalyticshub/mapper.go +++ b/pkg/controller/direct/bigqueryanalyticshub/mapper.go @@ -16,25 +16,27 @@ package bigqueryanalyticshub import ( pb "cloud.google.com/go/bigquery/analyticshub/apiv1/analyticshubpb" - krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/bigqueryanalyticshub/v1beta1" + krmv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/bigqueryanalyticshub/v1alpha1" + krmv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/bigqueryanalyticshub/v1beta1" + refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct" ) -func BigQueryAnalyticsHubDataExchangeObservedState_FromProto(mapCtx *direct.MapContext, in *pb.DataExchange) *krm.BigQueryAnalyticsHubDataExchangeObservedState { +func BigQueryAnalyticsHubDataExchangeObservedState_FromProto(mapCtx *direct.MapContext, in *pb.DataExchange) *krmv1beta1.BigQueryAnalyticsHubDataExchangeObservedState { if in == nil { return nil } - out := &krm.BigQueryAnalyticsHubDataExchangeObservedState{} + out := &krmv1beta1.BigQueryAnalyticsHubDataExchangeObservedState{} out.ListingCount = direct.LazyPtr(int64(in.GetListingCount())) // MISSING: SharingEnvironmentConfig // not yet return out } -func BigQueryAnalyticsHubDataExchangeSpec_FromProto(mapCtx *direct.MapContext, in *pb.DataExchange) *krm.BigQueryAnalyticsHubDataExchangeSpec { +func BigQueryAnalyticsHubDataExchangeSpec_FromProto(mapCtx *direct.MapContext, in *pb.DataExchange) *krmv1beta1.BigQueryAnalyticsHubDataExchangeSpec { if in == nil { return nil } - out := &krm.BigQueryAnalyticsHubDataExchangeSpec{} + out := &krmv1beta1.BigQueryAnalyticsHubDataExchangeSpec{} out.DisplayName = direct.LazyPtr(in.GetDisplayName()) out.Description = direct.LazyPtr(in.GetDescription()) out.PrimaryContact = direct.LazyPtr(in.GetPrimaryContact()) @@ -45,7 +47,7 @@ func BigQueryAnalyticsHubDataExchangeSpec_FromProto(mapCtx *direct.MapContext, i out.DiscoveryType = direct.Enum_FromProto(mapCtx, in.GetDiscoveryType()) return out } -func BigQueryAnalyticsHubDataExchangeSpec_ToProto(mapCtx *direct.MapContext, in *krm.BigQueryAnalyticsHubDataExchangeSpec) *pb.DataExchange { +func BigQueryAnalyticsHubDataExchangeSpec_ToProto(mapCtx *direct.MapContext, in *krmv1beta1.BigQueryAnalyticsHubDataExchangeSpec) *pb.DataExchange { if in == nil { return nil } @@ -62,3 +64,203 @@ func BigQueryAnalyticsHubDataExchangeSpec_ToProto(mapCtx *direct.MapContext, in return out } + +func Categories_FromProto(mapCtx *direct.MapContext, in []pb.Listing_Category) []string { + if in == nil { + return nil + } + ret := []string{} + for _, v := range in { + toProto := direct.Enum_FromProto(mapCtx, v) + if toProto != nil { + ret = append(ret, *toProto) + } + } + + return ret +} + +func BigQueryAnalyticsHubListingSpec_FromProto(mapCtx *direct.MapContext, in *pb.Listing) *krmv1alpha1.BigQueryAnalyticsHubListingSpec { + if in == nil { + return nil + } + out := &krmv1alpha1.BigQueryAnalyticsHubListingSpec{} + + out.DisplayName = direct.LazyPtr(in.GetDisplayName()) + out.Description = direct.LazyPtr(in.GetDescription()) + out.PrimaryContact = direct.LazyPtr(in.GetPrimaryContact()) + out.Documentation = direct.LazyPtr(in.GetDocumentation()) + // MISSING: Icon // not yet + out.DataProvider = DataProvider_FromProto(mapCtx, in.GetDataProvider()) + out.Categories = Categories_FromProto(mapCtx, in.Categories) + out.Publisher = Publisher_FromProto(mapCtx, in.GetPublisher()) + out.RequestAccess = direct.LazyPtr(in.GetRequestAccess()) + // MISSING: RestrictedExportConfig // not yet + out.DiscoveryType = direct.Enum_FromProto(mapCtx, in.GetDiscoveryType()) + + out.Source = &krmv1alpha1.Source{ + // TODO(KCC): in the future enforce mutual exclusion / one of b/w BigQueryDatasetSource and PubSubTopicSource + BigQueryDatasetSource: Listing_BigQueryDatasetSource_FromProto(mapCtx, in.GetBigqueryDataset()), + } + return out +} + +func Listing_BigQueryDatasetSource_FromProto(mapCtx *direct.MapContext, in *pb.Listing_BigQueryDatasetSource) *krmv1alpha1.BigQueryDatasetSource { + if in == nil { + return nil + } + out := &krmv1alpha1.BigQueryDatasetSource{} + if out.Dataset != nil { + out.Dataset = &refs.BigQueryDatasetRef{ + External: in.Dataset, + } + } + + out.SelectedResources = direct.Slice_FromProto(mapCtx, in.SelectedResources, Listing_BigQueryDatasetSource_SelectedResource_FromProto) + out.RestrictedExportPolicy = Listing_BigQueryDatasetSource_RestrictedExportPolicy_FromProto(mapCtx, in.GetRestrictedExportPolicy()) + return out +} + +func Listing_BigQueryDatasetSource_SelectedResource_FromProto(mapCtx *direct.MapContext, in *pb.Listing_BigQueryDatasetSource_SelectedResource) *krmv1alpha1.SelectedResource { + if in == nil { + return nil + } + out := &krmv1alpha1.SelectedResource{} + if in.GetTable() != "" { + out.TableRef = &refs.BigQueryTableRef{ + External: in.GetTable(), + } + } + + return out +} +func Listing_BigQueryDatasetSource_SelectedResource_ToProto(mapCtx *direct.MapContext, in *krmv1alpha1.SelectedResource) *pb.Listing_BigQueryDatasetSource_SelectedResource { + if in == nil { + return nil + } + out := &pb.Listing_BigQueryDatasetSource_SelectedResource{} + if in.TableRef != nil { + out.Resource = &pb.Listing_BigQueryDatasetSource_SelectedResource_Table{ + Table: in.TableRef.External, + } + } + + return out +} + +func Listing_BigQueryDatasetSource_RestrictedExportPolicy_FromProto(mapCtx *direct.MapContext, in *pb.Listing_BigQueryDatasetSource_RestrictedExportPolicy) *krmv1alpha1.RestrictedExportPolicy { + if in == nil { + return nil + } + out := &krmv1alpha1.RestrictedExportPolicy{} + if in.GetEnabled() != nil { + out.Enabled = direct.LazyPtr(in.GetEnabled().GetValue()) + } + if in.GetRestrictDirectTableAccess() != nil { + out.RestrictDirectTableAccess = direct.LazyPtr(in.GetRestrictDirectTableAccess().GetValue()) + } + if in.GetRestrictQueryResult() != nil { + out.RestrictQueryResult = direct.LazyPtr(in.GetRestrictQueryResult().GetValue()) + } + + return out +} + +func BigQueryAnalyticsHubListingObservedState_FromProto(mapCtx *direct.MapContext, in *pb.Listing) *krmv1alpha1.BigQueryAnalyticsHubListingObservedState { + if in == nil { + return nil + } + out := &krmv1alpha1.BigQueryAnalyticsHubListingObservedState{} + out.State = direct.Enum_FromProto(mapCtx, in.GetState()) + // MISSING: Icon // not yet + // MISSING: RestrictedExportConfig // not yet + return out +} + +func BigQueryAnalyticsHubListingObservedState_ToProto(mapCtx *direct.MapContext, in *krmv1alpha1.BigQueryAnalyticsHubListingObservedState) *pb.Listing { + if in == nil { + return nil + } + out := &pb.Listing{} + out.State = direct.Enum_ToProto[pb.Listing_State](mapCtx, in.State) + // MISSING: Icon // not yet + // MISSING: RestrictedExportConfig // not yet + return out +} + +func Categories_ToProto(mapCtx *direct.MapContext, in []string) []pb.Listing_Category { + if in == nil { + return nil + } + + ret := []pb.Listing_Category{} + for _, v := range in { + ret = append(ret, direct.Enum_ToProto[pb.Listing_Category](mapCtx, &v)) + } + + return ret +} + +func BigQueryAnalyticsHubListingSpec_ToProto(mapCtx *direct.MapContext, in *krmv1alpha1.BigQueryAnalyticsHubListingSpec) *pb.Listing { + if in == nil { + return nil + } + out := &pb.Listing{} + + out.DisplayName = direct.ValueOf(in.DisplayName) + out.Description = direct.ValueOf(in.Description) + out.PrimaryContact = direct.ValueOf(in.PrimaryContact) + out.Documentation = direct.ValueOf(in.Documentation) + // MISSING: Icon // not yet + out.DataProvider = DataProvider_ToProto(mapCtx, in.DataProvider) + out.Categories = Categories_ToProto(mapCtx, in.Categories) + out.Publisher = Publisher_ToProto(mapCtx, in.Publisher) + out.RequestAccess = direct.ValueOf(in.RequestAccess) + // MISSING: RestrictedExportConfig // not yet + + dtype := direct.Enum_ToProto[pb.DiscoveryType](mapCtx, in.DiscoveryType) + out.DiscoveryType = &dtype + + // todo acpana do source + return out +} + +func DataProvider_FromProto(mapCtx *direct.MapContext, in *pb.DataProvider) *krmv1alpha1.DataProvider { + if in == nil { + return nil + } + out := &krmv1alpha1.DataProvider{} + out.Name = direct.LazyPtr(in.GetName()) + out.PrimaryContact = direct.LazyPtr(in.GetPrimaryContact()) + return out +} + +func DataProvider_ToProto(mapCtx *direct.MapContext, in *krmv1alpha1.DataProvider) *pb.DataProvider { + if in == nil { + return nil + } + out := &pb.DataProvider{} + out.Name = direct.ValueOf(in.Name) + out.PrimaryContact = direct.ValueOf(in.PrimaryContact) + return out +} + +func Publisher_FromProto(mapCtx *direct.MapContext, in *pb.Publisher) *krmv1alpha1.Publisher { + if in == nil { + return nil + } + out := &krmv1alpha1.Publisher{} + out.Name = direct.LazyPtr(in.GetName()) + out.PrimaryContact = direct.LazyPtr(in.GetPrimaryContact()) + return out +} + +func Publisher_ToProto(mapCtx *direct.MapContext, in *krmv1alpha1.Publisher) *pb.Publisher { + if in == nil { + return nil + } + out := &pb.Publisher{} + out.Name = direct.ValueOf(in.Name) + out.PrimaryContact = direct.ValueOf(in.PrimaryContact) + return out +} From b17ebe26ca38c494404da81e131bc05ac310f8da Mon Sep 17 00:00:00 2001 From: Alex Pana <8968914+acpana@users.noreply.github.com> Date: Wed, 23 Oct 2024 20:53:12 +0000 Subject: [PATCH 2/7] api:refs: table ref Signed-off-by: Alex Pana <8968914+acpana@users.noreply.github.com> --- apis/refs/v1beta1/bigqueryref.go | 118 +++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/apis/refs/v1beta1/bigqueryref.go b/apis/refs/v1beta1/bigqueryref.go index 006c79daf8..a6ce1da026 100644 --- a/apis/refs/v1beta1/bigqueryref.go +++ b/apis/refs/v1beta1/bigqueryref.go @@ -107,3 +107,121 @@ func (d *BigQueryDataset) String() string { func (d *BigQueryDataset) GetDatasetID() string { return d.datasetID } + +type BigQueryTableRef struct { + // If provided must be in the format `projects/{projectId}/datasets/{datasetId}/tables/{tableId}`. + External string `json:"external,omitempty"` + // The `metadata.name` field of a `BigQueryTable` resource. + Name string `json:"name,omitempty"` + // The `metadata.namespace` field of a `BigQueryTable` resource. + Namespace string `json:"namespace,omitempty"` +} + +type BigQueryTable struct { + projectID string + datasetID string + tableID string +} + +func ResolveBigQueryTable(ctx context.Context, reader client.Reader, src client.Object, ref *BigQueryTableRef) (*BigQueryTable, error) { + if ref == nil { + return nil, nil + } + + if ref.Name == "" && ref.External == "" { + return nil, fmt.Errorf("must specify either name or external on BigQueryTableRef") + } + if ref.Name != "" && ref.External != "" { + return nil, fmt.Errorf("cannot specify both name and external on BigQueryTableRef") + } + + // External is provided. + if ref.External != "" { + // External should be in the `projects/{projectId}/datasets/{datasetId}/tables/{tableId}` format. + tokens := strings.Split(ref.External, "/") + if len(tokens) == 4 && tokens[0] == "projects" && tokens[2] == "datasets" && tokens[4] == "tables" { + return &BigQueryTable{ + projectID: tokens[1], + datasetID: tokens[3], + tableID: tokens[5], + }, nil + } + return nil, fmt.Errorf("format of BigQueryTableRef external=%q was not known (use projects/{projectId}/datasets/{datasetId}/tables/{tableId})", ref.External) + + } + + // Fetch BigQueryDataset object to construct the external form. + table := &unstructured.Unstructured{} + table.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "bigquery.cnrm.cloud.google.com", + Version: "v1beta1", + Kind: "BigQueryTable", + }) + nn := types.NamespacedName{ + Namespace: ref.Namespace, + Name: ref.Name, + } + if nn.Namespace == "" { + nn.Namespace = src.GetNamespace() + } + if err := reader.Get(ctx, nn, table); err != nil { + if apierrors.IsNotFound(err) { + return nil, fmt.Errorf("referenced BigQueryTable %v not found", nn) + } + return nil, fmt.Errorf("error reading referenced BigQueryTable %v: %w", nn, err) + } + projectID, err := ResolveProjectID(ctx, reader, table) + if err != nil { + return nil, err + } + dataset, err := ResolveDatasetForObject(ctx, reader, table) + if err != nil { + return nil, err + } + tableID, err := GetResourceID(table) + if err != nil { + return nil, err + } + return &BigQueryTable{ + projectID: projectID, + datasetID: dataset.datasetID, + tableID: tableID, + }, nil +} + +func (d *BigQueryTable) String() string { + return fmt.Sprintf("projects/%s/datasets/%s/tables/%s", d.projectID, d.datasetID, d.tableID) +} + +func ResolveDatasetForObject(ctx context.Context, reader client.Reader, obj *unstructured.Unstructured) (*BigQueryDataset, error) { + datasetRefExternal, _, err := unstructured.NestedString(obj.Object, "spec", "datasetRef", "external") + if err != nil { + return nil, fmt.Errorf("error fetching datasetRef.external %w", err) + } + if datasetRefExternal != "" { + return ResolveBigQueryDataset(ctx, reader, obj, &BigQueryDatasetRef{External: datasetRefExternal}) + } + + datasetRefName, _, err := unstructured.NestedString(obj.Object, "spec", "datasetRef", "name") + if err != nil { + return nil, fmt.Errorf("error fetching datasetRef.name %w", err) + } + if datasetRefName != "" { + datasetRefNs, _, err := unstructured.NestedString(obj.Object, "spec", "datasetRef", "namespace") + if err != nil { + return nil, fmt.Errorf("error fetching datasetRef.namespace %w", err) + } + + datasetRef := BigQueryDatasetRef{ + Name: datasetRefName, + Namespace: datasetRefNs, + } + if datasetRef.Namespace == "" { + datasetRef.Namespace = obj.GetNamespace() + } + + return ResolveBigQueryDataset(ctx, reader, obj, &datasetRef) + } + + return nil, fmt.Errorf("cannot find datasetRef for %v %v/%v", obj.GetKind(), obj.GetNamespace(), obj.GetName()) +} From 2dee28e5ccd42a2c3552c1982615c01950c156fe Mon Sep 17 00:00:00 2001 From: Alex Pana <8968914+acpana@users.noreply.github.com> Date: Wed, 23 Oct 2024 20:53:42 +0000 Subject: [PATCH 3/7] feat: direct controller listing Signed-off-by: Alex Pana <8968914+acpana@users.noreply.github.com> --- .../v1alpha1/listing_reference.go | 1 + .../v1alpha1/zz_generated.deepcopy.go | 2 +- .../listing_controller.go | 344 ++++++++++++++++++ .../direct/bigqueryanalyticshub/mapper.go | 18 + 4 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 pkg/controller/direct/bigqueryanalyticshub/listing_controller.go diff --git a/apis/bigqueryanalyticshub/v1alpha1/listing_reference.go b/apis/bigqueryanalyticshub/v1alpha1/listing_reference.go index f5e12b3c37..4aaf939bbc 100644 --- a/apis/bigqueryanalyticshub/v1alpha1/listing_reference.go +++ b/apis/bigqueryanalyticshub/v1alpha1/listing_reference.go @@ -154,6 +154,7 @@ func (r *BigQueryAnalyticsHubListingRef) Parent() (*BigQueryAnalyticsHubListingP type BigQueryAnalyticsHubListingParent struct { ProjectID string Location string + // todo acpana rework the parent to include the data exchange ref } func (p *BigQueryAnalyticsHubListingParent) String() string { diff --git a/apis/bigqueryanalyticshub/v1alpha1/zz_generated.deepcopy.go b/apis/bigqueryanalyticshub/v1alpha1/zz_generated.deepcopy.go index 542ccab122..ed2c9995c7 100644 --- a/apis/bigqueryanalyticshub/v1alpha1/zz_generated.deepcopy.go +++ b/apis/bigqueryanalyticshub/v1alpha1/zz_generated.deepcopy.go @@ -756,7 +756,7 @@ func (in *SelectedResource) DeepCopyInto(out *SelectedResource) { *out = *in if in.TableRef != nil { in, out := &in.TableRef, &out.TableRef - *out = new(v1beta1.BigQueryDatasetTableRef) + *out = new(v1beta1.BigQueryTableRef) **out = **in } } diff --git a/pkg/controller/direct/bigqueryanalyticshub/listing_controller.go b/pkg/controller/direct/bigqueryanalyticshub/listing_controller.go new file mode 100644 index 0000000000..d3021321f7 --- /dev/null +++ b/pkg/controller/direct/bigqueryanalyticshub/listing_controller.go @@ -0,0 +1,344 @@ +// Copyright 2024 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 bigqueryanalyticshub + +import ( + "context" + "fmt" + "reflect" + + krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/bigqueryanalyticshub/v1alpha1" + refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1" + "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config" + "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct" + "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase" + "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry" + + gcp "cloud.google.com/go/bigquery/analyticshub/apiv1" + bigqueryanalyticshubpb "cloud.google.com/go/bigquery/analyticshub/apiv1/analyticshubpb" + "google.golang.org/api/option" + "google.golang.org/protobuf/types/known/fieldmaskpb" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + listingCtrlName = "bigqueryanalyticshub-listing-controller" +) + +func init() { + registry.RegisterModel(krm.BigQueryAnalyticsHubListingGVK, NewListingModel) +} + +func NewListingModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) { + return &modelListing{config: *config}, nil +} + +var _ directbase.Model = &modelListing{} + +type modelListing struct { + config config.ControllerConfig +} + +func (m *modelListing) client(ctx context.Context) (*gcp.Client, error) { + var opts []option.ClientOption + opts, err := m.config.RESTClientOptions() + if err != nil { + return nil, err + } + gcpClient, err := gcp.NewRESTClient(ctx, opts...) + if err != nil { + return nil, fmt.Errorf("building Listing client: %w", err) + } + return gcpClient, err +} + +func (m *modelListing) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) { + obj := &krm.BigQueryAnalyticsHubListing{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil { + return nil, fmt.Errorf("error converting to %T: %w", obj, err) + } + + id, err := krm.NewBigQueryAnalyticsHubListingRef(ctx, reader, obj) + if err != nil { + return nil, err + } + + or, err := resolveOptionalReferences(ctx, reader, obj) + if err != nil { + return nil, err + } + // Get bigqueryanalyticshub GCP client + gcpClient, err := m.client(ctx) + if err != nil { + return nil, err + } + return &ListingAdapter{ + id: id, + gcpClient: gcpClient, + desired: obj, + references: or, + }, nil +} + +type optionalReferences struct { + dataExchange *refs.DataExchange +} + +func resolveOptionalReferences(ctx context.Context, reader client.Reader, obj *krm.BigQueryAnalyticsHubListing) (*optionalReferences, error) { + or := &optionalReferences{} + if ref := obj.Spec.DataExchangeRef; ref != nil { + de, err := refs.ResolveDataExchangeRef(ctx, reader, obj, ref) + if err != nil { + return nil, err + } + or.dataExchange = de + } + + if obj.Spec.Source != nil && obj.Spec.Source.BigQueryDatasetSource != nil { + if ref := obj.Spec.Source.BigQueryDatasetSource.Dataset; ref != nil { + // don't need to save the actual reference for this + if _, err := refs.ResolveBigQueryDataset(ctx, reader, obj, ref); err != nil { + return nil, err + } + + for _, selectedResource := range obj.Spec.Source.BigQueryDatasetSource.SelectedResources { + if ref := selectedResource.TableRef; ref != nil { + if _, err := refs.ResolveBigQueryTable(ctx, reader, obj, ref); err != nil { + return nil, err + } + } + } + } + } + + return or, nil +} + +func (m *modelListing) AdapterForURL(ctx context.Context, url string) (directbase.Adapter, error) { + // TODO: Support URLs + return nil, nil +} + +type ListingAdapter struct { + id *krm.BigQueryAnalyticsHubListingRef + gcpClient *gcp.Client + desired *krm.BigQueryAnalyticsHubListing + actual *bigqueryanalyticshubpb.Listing + + references *optionalReferences +} + +var _ directbase.Adapter = &ListingAdapter{} + +func (a *ListingAdapter) Find(ctx context.Context) (bool, error) { + log := klog.FromContext(ctx).WithName(listingCtrlName) + log.V(2).Info("getting Listing", "name", a.id.External) + + req := &bigqueryanalyticshubpb.GetListingRequest{Name: a.id.External} + listingpb, err := a.gcpClient.GetListing(ctx, req) + if err != nil { + if direct.IsNotFound(err) { + return false, nil + } + return false, fmt.Errorf("getting Listing %q: %w", a.id.External, err) + } + + a.actual = listingpb + return true, nil +} + +func (a *ListingAdapter) Create(ctx context.Context, createOp *directbase.CreateOperation) error { + log := klog.FromContext(ctx).WithName(listingCtrlName) + log.V(2).Info("creating Listing", "name", a.id.External) + mapCtx := &direct.MapContext{} + + desired := a.desired.DeepCopy() + resource := BigQueryAnalyticsHubListingSpec_ToProto(mapCtx, &desired.Spec) + if mapCtx.Err() != nil { + return mapCtx.Err() + } + + parent, err := a.id.Parent() + if err != nil { + return err + } + + req := &bigqueryanalyticshubpb.CreateListingRequest{ + Parent: parent.String() + "/dataExchanges/" + a.references.dataExchange.DataExchangeID, + Listing: resource, + ListingId: a.desired.GetName(), + } + created, err := a.gcpClient.CreateListing(ctx, req) + if err != nil { + return fmt.Errorf("creating Listing %s: %w", a.id.External, err) + } + + log.V(2).Info("successfully created Listing", "name", a.id.External) + + status := &krm.BigQueryAnalyticsHubListingStatus{} + status.ObservedState = BigQueryAnalyticsHubListingObservedState_FromProto(mapCtx, created) + if mapCtx.Err() != nil { + return mapCtx.Err() + } + status.ExternalRef = &a.id.External + return createOp.UpdateStatus(ctx, status, nil) +} + +func (a *ListingAdapter) Update(ctx context.Context, updateOp *directbase.UpdateOperation) error { + log := klog.FromContext(ctx).WithName(listingCtrlName) + log.V(2).Info("updating Listing", "name", a.id.External) + mapCtx := &direct.MapContext{} + + desired := a.desired.DeepCopy() + resource := BigQueryAnalyticsHubListingSpec_ToProto(mapCtx, &desired.Spec) + if mapCtx.Err() != nil { + return mapCtx.Err() + } + + updateMask := &fieldmaskpb.FieldMask{} + if a.desired.Spec.DisplayName != nil && !reflect.DeepEqual(a.desired.Spec.DisplayName, a.actual.DisplayName) { + updateMask.Paths = append(updateMask.Paths, "display_name") + } + if a.desired.Spec.Description != nil && !reflect.DeepEqual(a.desired.Spec.Description, a.actual.Description) { + updateMask.Paths = append(updateMask.Paths, "description") + } + if a.desired.Spec.PrimaryContact != nil && !reflect.DeepEqual(a.desired.Spec.PrimaryContact, a.actual.PrimaryContact) { + updateMask.Paths = append(updateMask.Paths, "primary_contact") + } + if a.desired.Spec.Documentation != nil && !reflect.DeepEqual(a.desired.Spec.Documentation, a.actual.Documentation) { + updateMask.Paths = append(updateMask.Paths, "documentation") + } + if a.desired.Spec.DiscoveryType != nil && !reflect.DeepEqual(a.desired.Spec.DiscoveryType, a.actual.DiscoveryType.String()) { + updateMask.Paths = append(updateMask.Paths, "discovery_type") + } + if a.desired.Spec.RequestAccess != nil && reflect.DeepEqual(a.desired.Spec.RequestAccess, a.actual.RequestAccess) { + updateMask.Paths = append(updateMask.Paths, "request_access") + } + + // NOT YET + // if a.desired.Spec.Icon != nil && !reflect.DeepEqual(a.desired.Spec.Icon, a.actual.Icon) { + // updateMask.Paths = append(updateMask.Paths, "icon") + // } + if a.desired.Spec.DataProvider != nil { + mapCtx := &direct.MapContext{} + toProto := DataProvider_ToProto(mapCtx, a.desired.Spec.DataProvider) + if mapCtx.Err() != nil { + return fmt.Errorf("converting data provider: %w", mapCtx.Err()) + } + + if !reflect.DeepEqual(toProto, a.actual.DataProvider) { + updateMask.Paths = append(updateMask.Paths, "data_provider") + } + } + + if a.desired.Spec.Publisher != nil { + mapCtx := &direct.MapContext{} + toProto := Publisher_ToProto(mapCtx, a.desired.Spec.Publisher) + if mapCtx.Err() != nil { + return fmt.Errorf("converting publisher: %w", mapCtx.Err()) + } + + if !reflect.DeepEqual(toProto, a.actual.Publisher) { + updateMask.Paths = append(updateMask.Paths, "publisher") + } + } + + if a.desired.Spec.Categories != nil { + mapCtx := &direct.MapContext{} + toProto := Categories_ToProto(mapCtx, a.desired.Spec.Categories) + if mapCtx.Err() != nil { + return fmt.Errorf("converting categories: %w", mapCtx.Err()) + } + if !reflect.DeepEqual(toProto, a.actual.Categories) { + updateMask.Paths = append(updateMask.Paths, "categories") + } + } + + if len(updateMask.Paths) == 0 { + log.V(2).Info("no field needs update", "name", a.id.External) + return nil + } + + req := &bigqueryanalyticshubpb.UpdateListingRequest{ + UpdateMask: updateMask, + Listing: resource, + } + updated, err := a.gcpClient.UpdateListing(ctx, req) + if err != nil { + return fmt.Errorf("updating Listing %s: %w", a.id.External, err) + } + + log.V(2).Info("successfully updated Listing", "name", a.id.External) + + status := &krm.BigQueryAnalyticsHubListingStatus{} + status.ObservedState = BigQueryAnalyticsHubListingObservedState_FromProto(mapCtx, updated) + if mapCtx.Err() != nil { + return mapCtx.Err() + } + + return updateOp.UpdateStatus(ctx, status, nil) +} + +func (a *ListingAdapter) Export(ctx context.Context) (*unstructured.Unstructured, error) { + if a.actual == nil { + return nil, fmt.Errorf("Find() not called") + } + u := &unstructured.Unstructured{} + + obj := &krm.BigQueryAnalyticsHubListing{} + mapCtx := &direct.MapContext{} + obj.Spec = direct.ValueOf(BigQueryAnalyticsHubListingSpec_FromProto(mapCtx, a.actual)) + if mapCtx.Err() != nil { + return nil, mapCtx.Err() + } + + parent, err := a.id.Parent() + if err != nil { + return nil, err + } + obj.Spec.ProjectRef = &refs.ProjectRef{External: parent.String()} + obj.Spec.Location = parent.Location + uObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + if err != nil { + return nil, err + } + u.Object = uObj + return u, nil +} + +// Delete implements the Adapter interface. +func (a *ListingAdapter) Delete(ctx context.Context, deleteOp *directbase.DeleteOperation) (bool, error) { + log := klog.FromContext(ctx).WithName(listingCtrlName) + log.V(2).Info("deleting Listing", "name", a.id.External) + + parent, err := a.id.Parent() + if err != nil { + return false, err + } + + actualName := parent.String() + "/dataExchanges/" + a.references.dataExchange.DataExchangeID + "/listings/" + a.id.Name + req := &bigqueryanalyticshubpb.DeleteListingRequest{Name: actualName} + err = a.gcpClient.DeleteListing(ctx, req) + if err != nil { + return false, fmt.Errorf("deleting Listing %s: %w", a.id.External, err) + } + log.V(2).Info("successfully deleted Listing", "name", a.id.External) + + return true, nil +} diff --git a/pkg/controller/direct/bigqueryanalyticshub/mapper.go b/pkg/controller/direct/bigqueryanalyticshub/mapper.go index d78f3c3d43..fdde4a4577 100644 --- a/pkg/controller/direct/bigqueryanalyticshub/mapper.go +++ b/pkg/controller/direct/bigqueryanalyticshub/mapper.go @@ -20,6 +20,7 @@ import ( krmv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/bigqueryanalyticshub/v1beta1" refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct" + "google.golang.org/protobuf/types/known/wrapperspb" ) func BigQueryAnalyticsHubDataExchangeObservedState_FromProto(mapCtx *direct.MapContext, in *pb.DataExchange) *krmv1beta1.BigQueryAnalyticsHubDataExchangeObservedState { @@ -165,6 +166,23 @@ func Listing_BigQueryDatasetSource_RestrictedExportPolicy_FromProto(mapCtx *dire return out } +func Listing_BigQueryDatasetSource_RestrictedExportPolicy_ToProto(mapCtx *direct.MapContext, in *krmv1alpha1.RestrictedExportPolicy) *pb.Listing_BigQueryDatasetSource_RestrictedExportPolicy { + if in == nil { + return nil + } + out := &pb.Listing_BigQueryDatasetSource_RestrictedExportPolicy{} + if in.Enabled != nil { + out.Enabled = &wrapperspb.BoolValue{Value: *in.Enabled} + } + if in.RestrictDirectTableAccess != nil { + out.RestrictDirectTableAccess = &wrapperspb.BoolValue{Value: *in.RestrictDirectTableAccess} + } + if in.RestrictQueryResult != nil { + out.RestrictQueryResult = &wrapperspb.BoolValue{Value: *in.RestrictQueryResult} + } + + return out +} func BigQueryAnalyticsHubListingObservedState_FromProto(mapCtx *direct.MapContext, in *pb.Listing) *krmv1alpha1.BigQueryAnalyticsHubListingObservedState { if in == nil { From 8a69815b6e21cf80304574409a25a75aa52911a4 Mon Sep 17 00:00:00 2001 From: Alex Pana <8968914+acpana@users.noreply.github.com> Date: Tue, 12 Nov 2024 22:20:23 +0000 Subject: [PATCH 4/7] tests: turn on mock listing Signed-off-by: Alex Pana <8968914+acpana@users.noreply.github.com> --- config/tests/samples/create/harness.go | 1 + 1 file changed, 1 insertion(+) diff --git a/config/tests/samples/create/harness.go b/config/tests/samples/create/harness.go index 9635ba233c..02b38fc938 100644 --- a/config/tests/samples/create/harness.go +++ b/config/tests/samples/create/harness.go @@ -730,6 +730,7 @@ func MaybeSkip(t *testing.T, name string, resources []*unstructured.Unstructured case schema.GroupKind{Group: "bigquery.cnrm.cloud.google.com", Kind: "BigQueryTable"}: case schema.GroupKind{Group: "bigqueryanalyticshub.cnrm.cloud.google.com", Kind: "BigQueryAnalyticsHubDataExchange"}: + case schema.GroupKind{Group: "bigqueryanalyticshub.cnrm.cloud.google.com", Kind: "BigQueryAnalyticsHubListing"}: case schema.GroupKind{Group: "bigqueryconnection.cnrm.cloud.google.com", Kind: "BigQueryConnectionConnection"}: From 61f4f32fb41ebf999d83d2fb1240d435a042d6bd Mon Sep 17 00:00:00 2001 From: Alex Pana <8968914+acpana@users.noreply.github.com> Date: Tue, 12 Nov 2024 22:22:36 +0000 Subject: [PATCH 5/7] tests: listing base Signed-off-by: Alex Pana <8968914+acpana@users.noreply.github.com> --- ...gqueryanalyticshublisting-base.golden.yaml | 35 ++ .../_http.log | 394 ++++++++++++++++++ .../create.yaml | 30 ++ .../dependencies.yaml | 31 ++ 4 files changed, 490 insertions(+) create mode 100644 pkg/test/resourcefixture/testdata/basic/bigqueryanalyticshub/v1alpha1/bigqueryanalyticshublisting-base/_generated_object_bigqueryanalyticshublisting-base.golden.yaml create mode 100644 pkg/test/resourcefixture/testdata/basic/bigqueryanalyticshub/v1alpha1/bigqueryanalyticshublisting-base/_http.log create mode 100644 pkg/test/resourcefixture/testdata/basic/bigqueryanalyticshub/v1alpha1/bigqueryanalyticshublisting-base/create.yaml create mode 100644 pkg/test/resourcefixture/testdata/basic/bigqueryanalyticshub/v1alpha1/bigqueryanalyticshublisting-base/dependencies.yaml diff --git a/pkg/test/resourcefixture/testdata/basic/bigqueryanalyticshub/v1alpha1/bigqueryanalyticshublisting-base/_generated_object_bigqueryanalyticshublisting-base.golden.yaml b/pkg/test/resourcefixture/testdata/basic/bigqueryanalyticshub/v1alpha1/bigqueryanalyticshublisting-base/_generated_object_bigqueryanalyticshublisting-base.golden.yaml new file mode 100644 index 0000000000..73197a2031 --- /dev/null +++ b/pkg/test/resourcefixture/testdata/basic/bigqueryanalyticshub/v1alpha1/bigqueryanalyticshublisting-base/_generated_object_bigqueryanalyticshublisting-base.golden.yaml @@ -0,0 +1,35 @@ +apiVersion: bigqueryanalyticshub.cnrm.cloud.google.com/v1alpha1 +kind: BigQueryAnalyticsHubListing +metadata: + annotations: + cnrm.cloud.google.com/management-conflict-prevention-policy: none + finalizers: + - cnrm.cloud.google.com/finalizer + - cnrm.cloud.google.com/deletion-defender + generation: 1 + labels: + cnrm-test: "true" + name: bigqueryanalyticshublisting-${uniqueId} + namespace: ${uniqueId} +spec: + dataExchangeRef: + name: bigqueryanalyticshubdataexchange${uniqueId} + displayName: my_data_exchange + location: US + projectRef: + external: ${projectId} + resourceID: bigqueryanalyticshublisting-${uniqueId} + source: + bigQueryDatasetSource: + datasetRef: + name: bigquerydataset${uniqueId} +status: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: The resource is up to date + reason: UpToDate + status: "True" + type: Ready + externalRef: projects/${projectId}/locations/US/dataExchanges/bigqueryanalyticshubdataexchange${uniqueId}/listings/bigqueryanalyticshublisting-${uniqueId} + observedGeneration: 1 + observedState: {} diff --git a/pkg/test/resourcefixture/testdata/basic/bigqueryanalyticshub/v1alpha1/bigqueryanalyticshublisting-base/_http.log b/pkg/test/resourcefixture/testdata/basic/bigqueryanalyticshub/v1alpha1/bigqueryanalyticshublisting-base/_http.log new file mode 100644 index 0000000000..c544fffa27 --- /dev/null +++ b/pkg/test/resourcefixture/testdata/basic/bigqueryanalyticshub/v1alpha1/bigqueryanalyticshublisting-base/_http.log @@ -0,0 +1,394 @@ +GET https://bigquery.googleapis.com/bigquery/v2/projects/${projectId}/datasets/${datasetID}?alt=json +Content-Type: application/json +User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager + +404 Not Found +Cache-Control: private +Content-Type: application/json; charset=UTF-8 +Server: ESF +Vary: Origin +Vary: X-Origin +Vary: Referer +X-Content-Type-Options: nosniff +X-Frame-Options: SAMEORIGIN +X-Xss-Protection: 0 + +{ + "error": { + "code": 404, + "errors": [ + { + "domain": "global", + "message": "Not found: Dataset ${projectId}:bigquerydataset${uniqueId}", + "reason": "notFound" + } + ], + "message": "Not found: Dataset ${projectId}:bigquerydataset${uniqueId}", + "status": "NOT_FOUND" + } +} + +--- + +POST https://bigquery.googleapis.com/bigquery/v2/projects/${projectId}/datasets?alt=json +Content-Type: application/json +User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager + +{ + "datasetReference": { + "datasetId": "bigquerydataset${uniqueId}" + }, + "friendlyName": "my-bigquerydataset", + "labels": { + "cnrm-test": "true", + "managed-by-cnrm": "true" + }, + "location": "US" +} + +200 OK +Cache-Control: private +Content-Type: application/json; charset=UTF-8 +Server: ESF +Vary: Origin +Vary: X-Origin +Vary: Referer +X-Content-Type-Options: nosniff +X-Frame-Options: SAMEORIGIN +X-Xss-Protection: 0 + +{ + "access": [ + { + "role": "OWNER", + "specialGroup": "projectOwners" + }, + { + "role": "OWNER", + "userByEmail": "user@google.com" + }, + { + "role": "READER", + "specialGroup": "projectReaders" + }, + { + "role": "WRITER", + "specialGroup": "projectWriters" + } + ], + "creationTime": "123456789", + "datasetReference": { + "datasetId": "bigquerydataset${uniqueId}", + "projectId": "${projectId}" + }, + "etag": "abcdef0123A=", + "friendlyName": "my-bigquerydataset", + "id": "000000000000000000000", + "kind": "bigquery#dataset", + "labels": { + "cnrm-test": "true", + "managed-by-cnrm": "true" + }, + "lastModifiedTime": "123456789", + "location": "US", + "selfLink": "https://bigquery.googleapis.com/bigquery/v2/projects/${projectId}/datasets/bigquerydataset${uniqueId}", + "type": "DEFAULT" +} + +--- + +GET https://bigquery.googleapis.com/bigquery/v2/projects/${projectId}/datasets/${datasetID}?alt=json +Content-Type: application/json +User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager + +200 OK +Cache-Control: private +Content-Type: application/json; charset=UTF-8 +Server: ESF +Vary: Origin +Vary: X-Origin +Vary: Referer +X-Content-Type-Options: nosniff +X-Frame-Options: SAMEORIGIN +X-Xss-Protection: 0 + +{ + "access": [ + { + "role": "OWNER", + "specialGroup": "projectOwners" + }, + { + "role": "OWNER", + "userByEmail": "user@google.com" + }, + { + "role": "READER", + "specialGroup": "projectReaders" + }, + { + "role": "WRITER", + "specialGroup": "projectWriters" + } + ], + "creationTime": "123456789", + "datasetReference": { + "datasetId": "bigquerydataset${uniqueId}", + "projectId": "${projectId}" + }, + "etag": "abcdef0123A=", + "friendlyName": "my-bigquerydataset", + "id": "000000000000000000000", + "kind": "bigquery#dataset", + "labels": { + "cnrm-test": "true", + "managed-by-cnrm": "true" + }, + "lastModifiedTime": "123456789", + "location": "US", + "maxTimeTravelHours": "168", + "selfLink": "https://bigquery.googleapis.com/bigquery/v2/projects/${projectId}/datasets/bigquerydataset${uniqueId}", + "type": "DEFAULT" +} + +--- + +GET https://analyticshub.googleapis.com/v1/projects/${projectId}/locations/US/dataExchanges/bigqueryanalyticshubdataexchange${uniqueId} +Content-Type: application/json +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Flocations%2FUS%2FdataExchanges%2Fbigqueryanalyticshubdataexchange${uniqueId} + +404 Not Found +Content-Type: application/json + +{ + "code": 5, + "details": [], + "message": "dataExchange \"projects/${projectId}/locations/US/dataExchanges/bigqueryanalyticshubdataexchange${uniqueId}\" not found" +} + +--- + +POST https://analyticshub.googleapis.com/v1/projects/${projectId}/locations/US/dataExchanges?dataExchangeId=bigqueryanalyticshubdataexchange${uniqueId} +Content-Type: application/json +User-Agent: kcc/controller-manager +x-goog-request-params: parent=projects%2F${projectId}%2Flocations%2FUS + +{ + "discoveryType": 0, + "displayName": "my_data_exchange" +} + +200 OK +Content-Type: application/json +Grpc-Metadata-Content-Type: application/grpc + +{ + "description": "", + "discoveryType": "DISCOVERY_TYPE_UNSPECIFIED", + "displayName": "my_data_exchange", + "documentation": "", + "icon": "", + "listingCount": 0, + "name": "projects/${projectId}/locations/US/dataExchanges/bigqueryanalyticshubdataexchange${uniqueId}", + "primaryContact": "", + "sharingEnvironmentConfig": null +} + +--- + +GET https://analyticshub.googleapis.com/v1/projects/${projectId}/locations/US/dataExchanges/bigqueryanalyticshubdataexchange${uniqueId}/listings/bigqueryanalyticshublisting-${uniqueId} +Content-Type: application/json +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Flocations%2FUS%2FdataExchanges%2Fbigqueryanalyticshubdataexchange${uniqueId}%2Flistings%2Fbigqueryanalyticshublisting-${uniqueId} + +404 Not Found +Content-Type: application/json + +{ + "code": 5, + "details": [], + "message": "listing \"projects/${projectId}/locations/US/dataExchanges/bigqueryanalyticshubdataexchange${uniqueId}/listings/bigqueryanalyticshublisting-${uniqueId}\" not found" +} + +--- + +POST https://analyticshub.googleapis.com/v1/projects/${projectId}/locations/US/dataExchanges/bigqueryanalyticshubdataexchange${uniqueId}/listings?listingId=bigqueryanalyticshublisting-${uniqueId} +Content-Type: application/json +User-Agent: kcc/controller-manager +x-goog-request-params: parent=projects%2F${projectId}%2Flocations%2FUS%2FdataExchanges%2Fbigqueryanalyticshubdataexchange${uniqueId} + +{ + "discoveryType": 0, + "displayName": "my_data_exchange" +} + +200 OK +Content-Type: application/json +Grpc-Metadata-Content-Type: application/grpc + +{ + "categories": [], + "dataProvider": null, + "description": "", + "discoveryType": "DISCOVERY_TYPE_UNSPECIFIED", + "displayName": "my_data_exchange", + "documentation": "", + "icon": "", + "name": "projects/${projectId}/locations/US/dataExchanges/bigqueryanalyticshubdataexchange${uniqueId}/listings/bigqueryanalyticshublisting-${uniqueId}", + "primaryContact": "", + "publisher": null, + "requestAccess": "", + "restrictedExportConfig": null, + "state": "STATE_UNSPECIFIED" +} + +--- + +GET https://analyticshub.googleapis.com/v1/projects/${projectId}/locations/US/dataExchanges/bigqueryanalyticshubdataexchange${uniqueId}/listings/bigqueryanalyticshublisting-${uniqueId} +Content-Type: application/json +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Flocations%2FUS%2FdataExchanges%2Fbigqueryanalyticshubdataexchange${uniqueId}%2Flistings%2Fbigqueryanalyticshublisting-${uniqueId} + +200 OK +Content-Type: application/json +Grpc-Metadata-Content-Type: application/grpc + +{ + "categories": [], + "dataProvider": null, + "description": "", + "discoveryType": "DISCOVERY_TYPE_UNSPECIFIED", + "displayName": "my_data_exchange", + "documentation": "", + "icon": "", + "name": "projects/${projectId}/locations/US/dataExchanges/bigqueryanalyticshubdataexchange${uniqueId}/listings/bigqueryanalyticshublisting-${uniqueId}", + "primaryContact": "", + "publisher": null, + "requestAccess": "", + "restrictedExportConfig": null, + "state": "STATE_UNSPECIFIED" +} + +--- + +DELETE https://analyticshub.googleapis.com/v1/projects/${projectId}/locations/US/dataExchanges/bigqueryanalyticshubdataexchange${uniqueId}/listings/bigqueryanalyticshublisting-${uniqueId} +Content-Type: application/json +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Flocations%2FUS%2FdataExchanges%2Fbigqueryanalyticshubdataexchange${uniqueId}%2Flistings%2Fbigqueryanalyticshublisting-${uniqueId} + +200 OK +Content-Type: application/json +Grpc-Metadata-Content-Type: application/grpc +Grpc-Metadata-X-Http-Code: 204 + +{} + +--- + +GET https://analyticshub.googleapis.com/v1/projects/${projectId}/locations/US/dataExchanges/bigqueryanalyticshubdataexchange${uniqueId} +Content-Type: application/json +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Flocations%2FUS%2FdataExchanges%2Fbigqueryanalyticshubdataexchange${uniqueId} + +200 OK +Content-Type: application/json +Grpc-Metadata-Content-Type: application/grpc + +{ + "description": "", + "discoveryType": "DISCOVERY_TYPE_UNSPECIFIED", + "displayName": "my_data_exchange", + "documentation": "", + "icon": "", + "listingCount": 0, + "name": "projects/${projectId}/locations/US/dataExchanges/bigqueryanalyticshubdataexchange${uniqueId}", + "primaryContact": "", + "sharingEnvironmentConfig": null +} + +--- + +DELETE https://analyticshub.googleapis.com/v1/projects/${projectId}/locations/US/dataExchanges/bigqueryanalyticshubdataexchange${uniqueId} +Content-Type: application/json +User-Agent: kcc/controller-manager +x-goog-request-params: name=projects%2F${projectId}%2Flocations%2FUS%2FdataExchanges%2Fbigqueryanalyticshubdataexchange${uniqueId} + +200 OK +Content-Type: application/json +Grpc-Metadata-Content-Type: application/grpc +Grpc-Metadata-X-Http-Code: 204 + +{} + +--- + +GET https://bigquery.googleapis.com/bigquery/v2/projects/${projectId}/datasets/${datasetID}?alt=json +Content-Type: application/json +User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager + +200 OK +Cache-Control: private +Content-Type: application/json; charset=UTF-8 +Server: ESF +Vary: Origin +Vary: X-Origin +Vary: Referer +X-Content-Type-Options: nosniff +X-Frame-Options: SAMEORIGIN +X-Xss-Protection: 0 + +{ + "access": [ + { + "role": "OWNER", + "specialGroup": "projectOwners" + }, + { + "role": "OWNER", + "userByEmail": "user@google.com" + }, + { + "role": "READER", + "specialGroup": "projectReaders" + }, + { + "role": "WRITER", + "specialGroup": "projectWriters" + } + ], + "creationTime": "123456789", + "datasetReference": { + "datasetId": "bigquerydataset${uniqueId}", + "projectId": "${projectId}" + }, + "etag": "abcdef0123A=", + "friendlyName": "my-bigquerydataset", + "id": "000000000000000000000", + "kind": "bigquery#dataset", + "labels": { + "cnrm-test": "true", + "managed-by-cnrm": "true" + }, + "lastModifiedTime": "123456789", + "location": "US", + "maxTimeTravelHours": "168", + "selfLink": "https://bigquery.googleapis.com/bigquery/v2/projects/${projectId}/datasets/bigquerydataset${uniqueId}", + "type": "DEFAULT" +} + +--- + +DELETE https://bigquery.googleapis.com/bigquery/v2/projects/${projectId}/datasets/${datasetID}?alt=json&deleteContents=false +Content-Type: application/json +User-Agent: Terraform/ (+https://www.terraform.io) Terraform-Plugin-SDK/2.10.1 terraform-provider-google-beta/kcc/controller-manager + +204 No Content +Content-Type: application/json; charset=UTF-8 +Server: ESF +Vary: Origin +Vary: X-Origin +Vary: Referer +X-Content-Type-Options: nosniff +X-Frame-Options: SAMEORIGIN +X-Xss-Protection: 0 \ No newline at end of file diff --git a/pkg/test/resourcefixture/testdata/basic/bigqueryanalyticshub/v1alpha1/bigqueryanalyticshublisting-base/create.yaml b/pkg/test/resourcefixture/testdata/basic/bigqueryanalyticshub/v1alpha1/bigqueryanalyticshublisting-base/create.yaml new file mode 100644 index 0000000000..7e6ff4f592 --- /dev/null +++ b/pkg/test/resourcefixture/testdata/basic/bigqueryanalyticshub/v1alpha1/bigqueryanalyticshublisting-base/create.yaml @@ -0,0 +1,30 @@ +# Copyright 2024 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. + +apiVersion: bigqueryanalyticshub.cnrm.cloud.google.com/v1alpha1 +kind: BigQueryAnalyticsHubListing +metadata: + name: bigqueryanalyticshublisting-${uniqueId} +spec: + displayName: my_data_exchange + location: US + source: + bigQueryDatasetSource: + datasetRef: + name: bigquerydataset${uniqueId} + dataExchangeRef: + name: bigqueryanalyticshubdataexchange${uniqueId} + projectRef: + external: ${projectId} + resourceID: bigqueryanalyticshublisting-${uniqueId} \ No newline at end of file diff --git a/pkg/test/resourcefixture/testdata/basic/bigqueryanalyticshub/v1alpha1/bigqueryanalyticshublisting-base/dependencies.yaml b/pkg/test/resourcefixture/testdata/basic/bigqueryanalyticshub/v1alpha1/bigqueryanalyticshublisting-base/dependencies.yaml new file mode 100644 index 0000000000..bf8dcbc54c --- /dev/null +++ b/pkg/test/resourcefixture/testdata/basic/bigqueryanalyticshub/v1alpha1/bigqueryanalyticshublisting-base/dependencies.yaml @@ -0,0 +1,31 @@ +# Copyright 2024 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. + +apiVersion: bigquery.cnrm.cloud.google.com/v1beta1 +kind: BigQueryDataset +metadata: + name: bigquerydataset${uniqueId} +spec: + friendlyName: my-bigquerydataset +--- +apiVersion: bigqueryanalyticshub.cnrm.cloud.google.com/v1alpha1 +kind: BigQueryAnalyticsHubDataExchange +metadata: + name: bigqueryanalyticshubdataexchange${uniqueId} +spec: + displayName: my_data_exchange + location: US + projectRef: + external: ${projectId} + resourceID: bigqueryanalyticshubdataexchange${uniqueId} From 569b1703f5f3ac65c7c93320beb8256d1ce9e423 Mon Sep 17 00:00:00 2001 From: Alex Pana <8968914+acpana@users.noreply.github.com> Date: Tue, 19 Nov 2024 23:37:26 +0000 Subject: [PATCH 6/7] refactor:refs: rework refs Signed-off-by: Alex Pana <8968914+acpana@users.noreply.github.com> --- .../v1alpha1/listing_reference.go | 34 ++++++++++----- .../v1alpha1/zz_generated.deepcopy.go | 2 +- apis/refs/v1beta1/dataexchangeref.go | 33 ++++++++++++++ .../listing_controller.go | 43 ++++++------------- 4 files changed, 71 insertions(+), 41 deletions(-) diff --git a/apis/bigqueryanalyticshub/v1alpha1/listing_reference.go b/apis/bigqueryanalyticshub/v1alpha1/listing_reference.go index 4aaf939bbc..f1448ebeae 100644 --- a/apis/bigqueryanalyticshub/v1alpha1/listing_reference.go +++ b/apis/bigqueryanalyticshub/v1alpha1/listing_reference.go @@ -23,6 +23,7 @@ import ( "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/runtime" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -99,7 +100,19 @@ func NewBigQueryAnalyticsHubListingRef(ctx context.Context, reader client.Reader return nil, fmt.Errorf("cannot resolve project") } location := obj.Spec.Location - id.parent = &BigQueryAnalyticsHubListingParent{ProjectID: projectID, Location: location} + if location == "" { + return nil, fmt.Errorf("location cannot be empty") + } + contents, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + if err != nil { + return nil, fmt.Errorf("cannot convert typed to unstructured: %w", err) + } + + dataExchangeRef, err := refsv1beta1.ResolveDataExchangeForObject(ctx, reader, &unstructured.Unstructured{Object: contents}) + if err != nil { + return nil, fmt.Errorf("cannot resolve dataset ref: %w", err) + } + id.parent = &BigQueryAnalyticsHubListingParent{ProjectID: projectID, Location: location, DataExchangeID: dataExchangeRef.DataExchangeID} // Get desired ID resourceID := valueOf(obj.Spec.ResourceID) @@ -152,13 +165,13 @@ func (r *BigQueryAnalyticsHubListingRef) Parent() (*BigQueryAnalyticsHubListingP } type BigQueryAnalyticsHubListingParent struct { - ProjectID string - Location string - // todo acpana rework the parent to include the data exchange ref + ProjectID string + Location string + DataExchangeID string } func (p *BigQueryAnalyticsHubListingParent) String() string { - return "projects/" + p.ProjectID + "/locations/" + p.Location + return "projects/" + p.ProjectID + "/locations/" + p.Location + "/dataExchanges/" + p.DataExchangeID } func asBigQueryAnalyticsHubListingExternal(parent *BigQueryAnalyticsHubListingParent, resourceID string) (external string) { @@ -168,14 +181,15 @@ func asBigQueryAnalyticsHubListingExternal(parent *BigQueryAnalyticsHubListingPa func parseBigQueryAnalyticsHubListingExternal(external string) (parent *BigQueryAnalyticsHubListingParent, resourceID string, err error) { external = strings.TrimPrefix(external, "/") tokens := strings.Split(external, "/") - if len(tokens) != 6 || tokens[0] != "projects" || tokens[2] != "locations" || tokens[4] != "listings" { - return nil, "", fmt.Errorf("format of BigQueryAnalyticsHubListing external=%q was not known (use projects//locations//listings/)", external) + if len(tokens) != 8 || tokens[0] != "projects" || tokens[2] != "locations" || tokens[4] != "dataExchanges" || tokens[6] != "listings" { + return nil, "", fmt.Errorf("format of BigQueryAnalyticsHubListing external=%q was not known (use projects//locations//dataExchanges//listings/)", external) } parent = &BigQueryAnalyticsHubListingParent{ - ProjectID: tokens[1], - Location: tokens[3], + ProjectID: tokens[1], + Location: tokens[3], + DataExchangeID: tokens[5], } - resourceID = tokens[5] + resourceID = tokens[7] return parent, resourceID, nil } diff --git a/apis/bigqueryanalyticshub/v1alpha1/zz_generated.deepcopy.go b/apis/bigqueryanalyticshub/v1alpha1/zz_generated.deepcopy.go index ed2c9995c7..bb85ba0268 100644 --- a/apis/bigqueryanalyticshub/v1alpha1/zz_generated.deepcopy.go +++ b/apis/bigqueryanalyticshub/v1alpha1/zz_generated.deepcopy.go @@ -21,7 +21,7 @@ package v1alpha1 import ( "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1" k8sv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1" - runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. diff --git a/apis/refs/v1beta1/dataexchangeref.go b/apis/refs/v1beta1/dataexchangeref.go index b835f5b507..5995eb7bca 100644 --- a/apis/refs/v1beta1/dataexchangeref.go +++ b/apis/refs/v1beta1/dataexchangeref.go @@ -115,3 +115,36 @@ func ResolveDataExchangeRef(ctx context.Context, reader client.Reader, obj clien DataExchangeID: resourceID, }, nil } + +func ResolveDataExchangeForObject(ctx context.Context, reader client.Reader, obj *unstructured.Unstructured) (*DataExchange, error) { + dataExchangeRefExternal, _, err := unstructured.NestedString(obj.Object, "spec", "dataExchangeRef", "external") + if err != nil { + return nil, fmt.Errorf("error fetching dataExchangeRef.external %w", err) + } + if dataExchangeRefExternal != "" { + return ResolveDataExchangeRef(ctx, reader, obj, &DataExchangeRef{External: dataExchangeRefExternal}) + } + + dataExchangeRefName, _, err := unstructured.NestedString(obj.Object, "spec", "dataExchangeRef", "name") + if err != nil { + return nil, fmt.Errorf("error fetching dataExchangeRef.name %w", err) + } + if dataExchangeRefName != "" { + dataExchangeRefNs, _, err := unstructured.NestedString(obj.Object, "spec", "dataExchangeRef", "namespace") + if err != nil { + return nil, fmt.Errorf("error fetching dataExchangeRef.namespace %w", err) + } + + dataExchangeRef := DataExchangeRef{ + Name: dataExchangeRefName, + Namespace: dataExchangeRefNs, + } + if dataExchangeRef.Namespace == "" { + dataExchangeRef.Namespace = obj.GetNamespace() + } + + return ResolveDataExchangeRef(ctx, reader, obj, &dataExchangeRef) + } + + return nil, fmt.Errorf("cannot find dataExchangeRef for %v %v/%v", obj.GetKind(), obj.GetNamespace(), obj.GetName()) +} diff --git a/pkg/controller/direct/bigqueryanalyticshub/listing_controller.go b/pkg/controller/direct/bigqueryanalyticshub/listing_controller.go index d3021321f7..7d3883c0ad 100644 --- a/pkg/controller/direct/bigqueryanalyticshub/listing_controller.go +++ b/pkg/controller/direct/bigqueryanalyticshub/listing_controller.go @@ -79,8 +79,7 @@ func (m *modelListing) AdapterForObject(ctx context.Context, reader client.Reade return nil, err } - or, err := resolveOptionalReferences(ctx, reader, obj) - if err != nil { + if err := resolveOptionalReferences(ctx, reader, obj); err != nil { return nil, err } // Get bigqueryanalyticshub GCP client @@ -89,45 +88,37 @@ func (m *modelListing) AdapterForObject(ctx context.Context, reader client.Reade return nil, err } return &ListingAdapter{ - id: id, - gcpClient: gcpClient, - desired: obj, - references: or, + id: id, + gcpClient: gcpClient, + desired: obj, }, nil } -type optionalReferences struct { - dataExchange *refs.DataExchange -} - -func resolveOptionalReferences(ctx context.Context, reader client.Reader, obj *krm.BigQueryAnalyticsHubListing) (*optionalReferences, error) { - or := &optionalReferences{} +func resolveOptionalReferences(ctx context.Context, reader client.Reader, obj *krm.BigQueryAnalyticsHubListing) error { if ref := obj.Spec.DataExchangeRef; ref != nil { - de, err := refs.ResolveDataExchangeRef(ctx, reader, obj, ref) + _, err := refs.ResolveDataExchangeRef(ctx, reader, obj, ref) if err != nil { - return nil, err + return err } - or.dataExchange = de } if obj.Spec.Source != nil && obj.Spec.Source.BigQueryDatasetSource != nil { if ref := obj.Spec.Source.BigQueryDatasetSource.Dataset; ref != nil { - // don't need to save the actual reference for this if _, err := refs.ResolveBigQueryDataset(ctx, reader, obj, ref); err != nil { - return nil, err + return err } for _, selectedResource := range obj.Spec.Source.BigQueryDatasetSource.SelectedResources { if ref := selectedResource.TableRef; ref != nil { if _, err := refs.ResolveBigQueryTable(ctx, reader, obj, ref); err != nil { - return nil, err + return err } } } } } - return or, nil + return nil } func (m *modelListing) AdapterForURL(ctx context.Context, url string) (directbase.Adapter, error) { @@ -140,8 +131,6 @@ type ListingAdapter struct { gcpClient *gcp.Client desired *krm.BigQueryAnalyticsHubListing actual *bigqueryanalyticshubpb.Listing - - references *optionalReferences } var _ directbase.Adapter = &ListingAdapter{} @@ -180,7 +169,7 @@ func (a *ListingAdapter) Create(ctx context.Context, createOp *directbase.Create } req := &bigqueryanalyticshubpb.CreateListingRequest{ - Parent: parent.String() + "/dataExchanges/" + a.references.dataExchange.DataExchangeID, + Parent: parent.String(), Listing: resource, ListingId: a.desired.GetName(), } @@ -327,14 +316,8 @@ func (a *ListingAdapter) Delete(ctx context.Context, deleteOp *directbase.Delete log := klog.FromContext(ctx).WithName(listingCtrlName) log.V(2).Info("deleting Listing", "name", a.id.External) - parent, err := a.id.Parent() - if err != nil { - return false, err - } - - actualName := parent.String() + "/dataExchanges/" + a.references.dataExchange.DataExchangeID + "/listings/" + a.id.Name - req := &bigqueryanalyticshubpb.DeleteListingRequest{Name: actualName} - err = a.gcpClient.DeleteListing(ctx, req) + req := &bigqueryanalyticshubpb.DeleteListingRequest{Name: a.id.External} + err := a.gcpClient.DeleteListing(ctx, req) if err != nil { return false, fmt.Errorf("deleting Listing %s: %w", a.id.External, err) } From 4556cde11e5bc9e5e1abba14ad780955876ccbbd Mon Sep 17 00:00:00 2001 From: Alex Pana <8968914+acpana@users.noreply.github.com> Date: Fri, 22 Nov 2024 22:28:18 +0000 Subject: [PATCH 7/7] refs: use NormalizeExternal (new refs style) Signed-off-by: Alex Pana <8968914+acpana@users.noreply.github.com> --- .../v1alpha1/dataexchange_reference.go | 179 ++++++++++++++++++ .../v1alpha1/listing_reference.go | 17 +- .../v1alpha1/listing_types.go | 4 +- .../v1alpha1/zz_generated.deepcopy.go | 40 +++- .../v1beta1/dataexchange_identity.go | 88 +++++++++ .../v1beta1/dataexchange_reference.go | 100 ++++++++++ .../v1beta1/zz_generated.deepcopy.go | 50 +++++ apis/refs/v1beta1/bigqueryref.go | 4 + apis/refs/v1beta1/dataexchangeref.go | 150 --------------- ...eryanalyticshub.cnrm.cloud.google.com.yaml | 12 +- .../bigqueryanalyticshublisting_types.go | 1 + .../dataexchange_controller.go | 3 +- .../dataexchange_externalresource.go | 3 - .../listing_controller.go | 4 +- ...yanalyticshubdataexchange-base.golden.yaml | 2 +- ...yanalyticshubdataexchange-full.golden.yaml | 2 +- 16 files changed, 488 insertions(+), 171 deletions(-) create mode 100644 apis/bigqueryanalyticshub/v1alpha1/dataexchange_reference.go create mode 100644 apis/bigqueryanalyticshub/v1beta1/dataexchange_identity.go create mode 100644 apis/bigqueryanalyticshub/v1beta1/dataexchange_reference.go delete mode 100644 apis/refs/v1beta1/dataexchangeref.go diff --git a/apis/bigqueryanalyticshub/v1alpha1/dataexchange_reference.go b/apis/bigqueryanalyticshub/v1alpha1/dataexchange_reference.go new file mode 100644 index 0000000000..068d82b85f --- /dev/null +++ b/apis/bigqueryanalyticshub/v1alpha1/dataexchange_reference.go @@ -0,0 +1,179 @@ +// Copyright 2024 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" + + 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 = &BigQueryAnalyticsHubDataExchangeRef{} + +// BigQueryAnalyticsHubDataExchangeRef defines the resource reference to BigQueryAnalyticsHubDataExchange, which "External" field +// holds the GCP identifier for the KRM object. +type BigQueryAnalyticsHubDataExchangeRef struct { + // A reference to an externally managed BigQueryAnalyticsHubDataExchange resource. + // Should be in the format "projects//locations//dataexchanges/". + External string `json:"external,omitempty"` + + // The name of a BigQueryAnalyticsHubDataExchange resource. + Name string `json:"name,omitempty"` + + // The namespace of a BigQueryAnalyticsHubDataExchange resource. + Namespace string `json:"namespace,omitempty"` + + parent *BigQueryAnalyticsHubDataExchangeParent +} + +// NormalizedExternal provision the "External" value for other resource that depends on BigQueryAnalyticsHubDataExchange. +// If the "External" is given in the other resource's spec.BigQueryAnalyticsHubDataExchangeRef, the given value will be used. +// Otherwise, the "Name" and "Namespace" will be used to query the actual BigQueryAnalyticsHubDataExchange object from the cluster. +func (r *BigQueryAnalyticsHubDataExchangeRef) 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", BigQueryAnalyticsHubDataExchangeGVK.Kind) + } + // From given External + if r.External != "" { + if _, _, err := parseBigQueryAnalyticsHubDataExchangeExternal(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(BigQueryAnalyticsHubDataExchangeGVK) + 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", BigQueryAnalyticsHubDataExchangeGVK, 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 +} + +// New builds a BigQueryAnalyticsHubDataExchangeRef from the Config Connector BigQueryAnalyticsHubDataExchange object. +func NewBigQueryAnalyticsHubDataExchangeRef(ctx context.Context, reader client.Reader, obj *BigQueryAnalyticsHubDataExchange) (*BigQueryAnalyticsHubDataExchangeRef, error) { + id := &BigQueryAnalyticsHubDataExchangeRef{} + + // Get Parent + projectRef, err := refsv1beta1.ResolveProject(ctx, reader, obj, obj.Spec.ProjectRef) + if err != nil { + return nil, err + } + projectID := projectRef.ProjectID + if projectID == "" { + return nil, fmt.Errorf("cannot resolve project") + } + location := obj.Spec.Location + id.parent = &BigQueryAnalyticsHubDataExchangeParent{ProjectID: projectID, Location: location} + + // Get desired ID + resourceID := valueOf(obj.Spec.ResourceID) + if resourceID == "" { + resourceID = obj.GetName() + } + if resourceID == "" { + return nil, fmt.Errorf("cannot resolve resource ID") + } + + // Use approved External + externalRef := valueOf(obj.Status.ExternalRef) + if externalRef == "" { + id.External = asBigQueryAnalyticsHubDataExchangeExternal(id.parent, resourceID) + return id, nil + } + + // Validate desired with actual + actualParent, actualResourceID, err := parseBigQueryAnalyticsHubDataExchangeExternal(externalRef) + if err != nil { + return nil, err + } + if actualParent.ProjectID != projectID { + return nil, fmt.Errorf("spec.projectRef changed, expect %s, got %s", actualParent.ProjectID, projectID) + } + if actualParent.Location != location { + return nil, fmt.Errorf("spec.location changed, expect %s, got %s", actualParent.Location, location) + } + 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) + } + id.External = externalRef + id.parent = &BigQueryAnalyticsHubDataExchangeParent{ProjectID: projectID, Location: location} + return id, nil +} + +func (r *BigQueryAnalyticsHubDataExchangeRef) Parent() (*BigQueryAnalyticsHubDataExchangeParent, error) { + if r.parent != nil { + return r.parent, nil + } + if r.External != "" { + parent, _, err := parseBigQueryAnalyticsHubDataExchangeExternal(r.External) + if err != nil { + return nil, err + } + return parent, nil + } + return nil, fmt.Errorf("BigQueryAnalyticsHubDataExchangeRef not initialized from `NewBigQueryAnalyticsHubDataExchangeRef` or `NormalizedExternal`") +} + +type BigQueryAnalyticsHubDataExchangeParent struct { + ProjectID string + Location string +} + +func (p *BigQueryAnalyticsHubDataExchangeParent) String() string { + return "projects/" + p.ProjectID + "/locations/" + p.Location +} + +func asBigQueryAnalyticsHubDataExchangeExternal(parent *BigQueryAnalyticsHubDataExchangeParent, resourceID string) (external string) { + return parent.String() + "/dataexchanges/" + resourceID +} + +func parseBigQueryAnalyticsHubDataExchangeExternal(external string) (parent *BigQueryAnalyticsHubDataExchangeParent, resourceID string, err error) { + external = strings.TrimPrefix(external, "/") + tokens := strings.Split(external, "/") + if len(tokens) != 6 || tokens[0] != "projects" || tokens[2] != "locations" || tokens[4] != "dataexchange" { + return nil, "", fmt.Errorf("format of BigQueryAnalyticsHubDataExchange external=%q was not known (use projects//locations//dataexchanges/)", external) + } + parent = &BigQueryAnalyticsHubDataExchangeParent{ + ProjectID: tokens[1], + Location: tokens[3], + } + resourceID = tokens[5] + return parent, resourceID, nil +} diff --git a/apis/bigqueryanalyticshub/v1alpha1/listing_reference.go b/apis/bigqueryanalyticshub/v1alpha1/listing_reference.go index f1448ebeae..6ab77d5e73 100644 --- a/apis/bigqueryanalyticshub/v1alpha1/listing_reference.go +++ b/apis/bigqueryanalyticshub/v1alpha1/listing_reference.go @@ -19,11 +19,11 @@ import ( "fmt" "strings" + v1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/bigqueryanalyticshub/v1beta1" 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/runtime" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -103,16 +103,21 @@ func NewBigQueryAnalyticsHubListingRef(ctx context.Context, reader client.Reader if location == "" { return nil, fmt.Errorf("location cannot be empty") } - contents, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + + if obj.Spec.DataExchangeRef == nil { + return nil, fmt.Errorf("spec.dataExchangeRef cannot be empty") + } + dataExchangeExternal, err := obj.Spec.DataExchangeRef.NormalizedExternal(ctx, reader, obj.Namespace) if err != nil { - return nil, fmt.Errorf("cannot convert typed to unstructured: %w", err) + return nil, fmt.Errorf("cannot normalize dataExchangeRef for listing ref: %w", err) } - dataExchangeRef, err := refsv1beta1.ResolveDataExchangeForObject(ctx, reader, &unstructured.Unstructured{Object: contents}) + dataExchangeID, err := v1beta1.ParseDataExchangeIdentity(dataExchangeExternal) if err != nil { - return nil, fmt.Errorf("cannot resolve dataset ref: %w", err) + return nil, fmt.Errorf("cannot parse dataExchangeRef for listing ref: %w", err) } - id.parent = &BigQueryAnalyticsHubListingParent{ProjectID: projectID, Location: location, DataExchangeID: dataExchangeRef.DataExchangeID} + + id.parent = &BigQueryAnalyticsHubListingParent{ProjectID: projectID, Location: location, DataExchangeID: dataExchangeID.ID()} // Get desired ID resourceID := valueOf(obj.Spec.ResourceID) diff --git a/apis/bigqueryanalyticshub/v1alpha1/listing_types.go b/apis/bigqueryanalyticshub/v1alpha1/listing_types.go index c34ffcf067..fcdcf79fa2 100644 --- a/apis/bigqueryanalyticshub/v1alpha1/listing_types.go +++ b/apis/bigqueryanalyticshub/v1alpha1/listing_types.go @@ -15,6 +15,8 @@ package v1alpha1 import ( + v1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/bigqueryanalyticshub/v1beta1" + refv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -136,7 +138,7 @@ type BigQueryAnalyticsHubListingSpec struct { ProjectRef *refv1beta1.ProjectRef `json:"projectRef"` // +required - DataExchangeRef *refv1beta1.DataExchangeRef `json:"dataExchangeRef"` + DataExchangeRef *v1beta1.BigQueryAnalyticsHubDataExchangeRef `json:"dataExchangeRef"` // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable" // Immutable. diff --git a/apis/bigqueryanalyticshub/v1alpha1/zz_generated.deepcopy.go b/apis/bigqueryanalyticshub/v1alpha1/zz_generated.deepcopy.go index bb85ba0268..aa552d85b9 100644 --- a/apis/bigqueryanalyticshub/v1alpha1/zz_generated.deepcopy.go +++ b/apis/bigqueryanalyticshub/v1alpha1/zz_generated.deepcopy.go @@ -19,9 +19,10 @@ package v1alpha1 import ( + bigqueryanalyticshubv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/bigqueryanalyticshub/v1beta1" "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1" k8sv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1" - "k8s.io/apimachinery/pkg/runtime" + runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -103,6 +104,41 @@ func (in *BigQueryAnalyticsHubDataExchangeObservedState) DeepCopy() *BigQueryAna return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BigQueryAnalyticsHubDataExchangeParent) DeepCopyInto(out *BigQueryAnalyticsHubDataExchangeParent) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BigQueryAnalyticsHubDataExchangeParent. +func (in *BigQueryAnalyticsHubDataExchangeParent) DeepCopy() *BigQueryAnalyticsHubDataExchangeParent { + if in == nil { + return nil + } + out := new(BigQueryAnalyticsHubDataExchangeParent) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BigQueryAnalyticsHubDataExchangeRef) DeepCopyInto(out *BigQueryAnalyticsHubDataExchangeRef) { + *out = *in + if in.parent != nil { + in, out := &in.parent, &out.parent + *out = new(BigQueryAnalyticsHubDataExchangeParent) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BigQueryAnalyticsHubDataExchangeRef. +func (in *BigQueryAnalyticsHubDataExchangeRef) DeepCopy() *BigQueryAnalyticsHubDataExchangeRef { + if in == nil { + return nil + } + out := new(BigQueryAnalyticsHubDataExchangeRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BigQueryAnalyticsHubDataExchangeSpec) DeepCopyInto(out *BigQueryAnalyticsHubDataExchangeSpec) { *out = *in @@ -362,7 +398,7 @@ func (in *BigQueryAnalyticsHubListingSpec) DeepCopyInto(out *BigQueryAnalyticsHu } if in.DataExchangeRef != nil { in, out := &in.DataExchangeRef, &out.DataExchangeRef - *out = new(v1beta1.DataExchangeRef) + *out = new(bigqueryanalyticshubv1beta1.BigQueryAnalyticsHubDataExchangeRef) **out = **in } if in.ResourceID != nil { diff --git a/apis/bigqueryanalyticshub/v1beta1/dataexchange_identity.go b/apis/bigqueryanalyticshub/v1beta1/dataexchange_identity.go new file mode 100644 index 0000000000..e8167249e2 --- /dev/null +++ b/apis/bigqueryanalyticshub/v1beta1/dataexchange_identity.go @@ -0,0 +1,88 @@ +// Copyright 2024 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 v1beta1 + +import ( + "context" + "fmt" + + "github.com/GoogleCloudPlatform/k8s-config-connector/apis/common" + refsv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type DataExchangeIdentity struct { + id string + parent *DataExchangeParent +} + +func (i *DataExchangeIdentity) String() string { + return i.parent.String() + "/dataExchanges/" + i.id +} + +func (r *DataExchangeIdentity) Parent() *DataExchangeParent { + return r.parent +} + +func (r *DataExchangeIdentity) ID() string { + return r.id +} + +type DataExchangeParent struct { + ProjectID string + Location string +} + +func (p *DataExchangeParent) String() string { + return "projects/" + p.ProjectID + "/locations/" + p.Location +} + +func NewDataExchangeIdentity(ctx context.Context, reader client.Reader, obj *BigQueryAnalyticsHubDataExchange, u *unstructured.Unstructured) (*DataExchangeIdentity, error) { + // Get Parent + projectID, err := refsv1beta1.ResolveProjectID(ctx, reader, u) + 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 != "" { + actualIdentity, err := ParseDataExchangeIdentity(externalRef) + if err != nil { + return nil, err + } + if actualIdentity.parent.ProjectID != projectID { + return nil, fmt.Errorf("spec.projectRef changed, expect %s, got %s", actualIdentity.parent.ProjectID, projectID) + } + if actualIdentity.id != resourceID { + return nil, fmt.Errorf("cannot reset `metadata.name` or `spec.resourceID` to %s, since it has already assigned to %s", + resourceID, actualIdentity.id) + } + } + + return &DataExchangeIdentity{ + parent: &DataExchangeParent{ProjectID: projectID}, + id: resourceID, + }, nil +} diff --git a/apis/bigqueryanalyticshub/v1beta1/dataexchange_reference.go b/apis/bigqueryanalyticshub/v1beta1/dataexchange_reference.go new file mode 100644 index 0000000000..db59ff880c --- /dev/null +++ b/apis/bigqueryanalyticshub/v1beta1/dataexchange_reference.go @@ -0,0 +1,100 @@ +// Copyright 2024 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 v1beta1 + +import ( + "context" + "fmt" + "strings" + + 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 = &BigQueryAnalyticsHubDataExchangeRef{} + +// BigQueryAnalyticsHubDataExchangeRef defines the resource reference to BigQueryAnalyticsHubDataExchange, which "External" field +// holds the GCP identifier for the KRM object. +type BigQueryAnalyticsHubDataExchangeRef struct { + // A reference to an externally managed BigQueryAnalyticsHubDataExchange resource. + // Should be in the format "projects//locations//dataexchanges/". + External string `json:"external,omitempty"` + + // The name of a BigQueryAnalyticsHubDataExchange resource. + Name string `json:"name,omitempty"` + + // The namespace of a BigQueryAnalyticsHubDataExchange resource. + Namespace string `json:"namespace,omitempty"` +} + +// NormalizedExternal provision the "External" value for other resource that depends on BigQueryAnalyticsHubDataExchange. +// If the "External" is given in the other resource's spec.BigQueryAnalyticsHubDataExchangeRef, the given value will be used. +// Otherwise, the "Name" and "Namespace" will be used to query the actual BigQueryAnalyticsHubDataExchange object from the cluster. +func (r *BigQueryAnalyticsHubDataExchangeRef) 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", BigQueryAnalyticsHubDataExchangeGVK.Kind) + } + // From given External + if r.External != "" { + if _, err := ParseDataExchangeIdentity(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(BigQueryAnalyticsHubDataExchangeGVK) + 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", BigQueryAnalyticsHubDataExchangeGVK, 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) + } + + return actualExternalRef, nil +} + +func ParseDataExchangeIdentity(external string) (identity *DataExchangeIdentity, err error) { + external = strings.TrimPrefix(external, "/") + tokens := strings.Split(external, "/") + if len(tokens) != 6 || tokens[0] != "projects" || tokens[2] != "locations" || tokens[4] != "dataExchanges" { + return nil, fmt.Errorf("format of BigQueryAnalyticsHubDataExchange external=%q was not known (use projects//locations//dataExchanges/)", external) + } + + return &DataExchangeIdentity{ + parent: &DataExchangeParent{ + ProjectID: tokens[1], + Location: tokens[3], + }, + id: tokens[5], + }, nil +} diff --git a/apis/bigqueryanalyticshub/v1beta1/zz_generated.deepcopy.go b/apis/bigqueryanalyticshub/v1beta1/zz_generated.deepcopy.go index 294a071038..9fe4972a93 100644 --- a/apis/bigqueryanalyticshub/v1beta1/zz_generated.deepcopy.go +++ b/apis/bigqueryanalyticshub/v1beta1/zz_generated.deepcopy.go @@ -103,6 +103,21 @@ func (in *BigQueryAnalyticsHubDataExchangeObservedState) DeepCopy() *BigQueryAna return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BigQueryAnalyticsHubDataExchangeRef) DeepCopyInto(out *BigQueryAnalyticsHubDataExchangeRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BigQueryAnalyticsHubDataExchangeRef. +func (in *BigQueryAnalyticsHubDataExchangeRef) DeepCopy() *BigQueryAnalyticsHubDataExchangeRef { + if in == nil { + return nil + } + out := new(BigQueryAnalyticsHubDataExchangeRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BigQueryAnalyticsHubDataExchangeSpec) DeepCopyInto(out *BigQueryAnalyticsHubDataExchangeSpec) { *out = *in @@ -263,6 +278,41 @@ func (in *DataExchange) DeepCopy() *DataExchange { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DataExchangeIdentity) DeepCopyInto(out *DataExchangeIdentity) { + *out = *in + if in.parent != nil { + in, out := &in.parent, &out.parent + *out = new(DataExchangeParent) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DataExchangeIdentity. +func (in *DataExchangeIdentity) DeepCopy() *DataExchangeIdentity { + if in == nil { + return nil + } + out := new(DataExchangeIdentity) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DataExchangeParent) DeepCopyInto(out *DataExchangeParent) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DataExchangeParent. +func (in *DataExchangeParent) DeepCopy() *DataExchangeParent { + if in == nil { + return nil + } + out := new(DataExchangeParent) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DataProvider) DeepCopyInto(out *DataProvider) { *out = *in diff --git a/apis/refs/v1beta1/bigqueryref.go b/apis/refs/v1beta1/bigqueryref.go index a6ce1da026..3839273e3d 100644 --- a/apis/refs/v1beta1/bigqueryref.go +++ b/apis/refs/v1beta1/bigqueryref.go @@ -123,6 +123,8 @@ type BigQueryTable struct { tableID string } +// TODO(acpana): Once we have a BigQueryTable direct resource with a reference that implements the +// ExternalNormalizer we can remove this code. func ResolveBigQueryTable(ctx context.Context, reader client.Reader, src client.Object, ref *BigQueryTableRef) (*BigQueryTable, error) { if ref == nil { return nil, nil @@ -193,6 +195,8 @@ func (d *BigQueryTable) String() string { return fmt.Sprintf("projects/%s/datasets/%s/tables/%s", d.projectID, d.datasetID, d.tableID) } +// TODO(acpana): Once we have a BigQueryTable direct resource with a reference that implements the +// ExternalNormalizer we can remove this code. func ResolveDatasetForObject(ctx context.Context, reader client.Reader, obj *unstructured.Unstructured) (*BigQueryDataset, error) { datasetRefExternal, _, err := unstructured.NestedString(obj.Object, "spec", "datasetRef", "external") if err != nil { diff --git a/apis/refs/v1beta1/dataexchangeref.go b/apis/refs/v1beta1/dataexchangeref.go deleted file mode 100644 index 5995eb7bca..0000000000 --- a/apis/refs/v1beta1/dataexchangeref.go +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2024 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 v1beta1 - -import ( - "context" - "fmt" - "strings" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -type DataExchangeRef struct { - /* The DataExchange selfLink, when not managed by Config Connector. */ - External string `json:"external,omitempty"` - /* The `name` field of a `DataExchange` resource. */ - Name string `json:"name,omitempty"` - /* The `namespace` field of a `DataExchange` resource. */ - Namespace string `json:"namespace,omitempty"` -} - -type DataExchange struct { - ProjectID string - Location string - DataExchangeID string -} - -func (s *DataExchange) String() string { - return "projects/" + s.ProjectID + "locations/" + s.Location + "/dataExchanges/" + s.DataExchangeID -} - -func ResolveDataExchangeRef(ctx context.Context, reader client.Reader, obj client.Object, ref *DataExchangeRef) (*DataExchange, error) { - if ref == nil { - return nil, nil - } - - if ref.Name == "" && ref.External == "" { - return nil, fmt.Errorf("must specify either name or external on dataExchangeRef") - } - if ref.External != "" && ref.Name != "" { - return nil, fmt.Errorf("cannot specify both spec.dataExchangeRef.name and spec.dataExchangeRef.external") - } - - if ref.External != "" { - // External should be in the `projects/[projectID]/locations/[Location]/dataExchanges/[dataExchangeID]` format. - tokens := strings.Split(ref.External, "/") - if len(tokens) == 6 && tokens[0] == "projects" && tokens[2] == "locations" && tokens[4] == "dataExchanges" { - return &DataExchange{ - ProjectID: tokens[1], - Location: tokens[3], - DataExchangeID: tokens[5], - }, nil - } - return nil, fmt.Errorf("format of DataExchange external=%q was not known (use projects//locations//dataExchanges/)", ref.External) - } - - key := types.NamespacedName{ - Namespace: ref.Namespace, - Name: ref.Name, - } - if key.Namespace == "" { - key.Namespace = obj.GetNamespace() - } - - exchange := &unstructured.Unstructured{} - exchange.SetGroupVersionKind(schema.GroupVersionKind{ - Group: "bigqueryanalyticshub.cnrm.cloud.google.com", - Version: "v1alpha1", - Kind: "BigQueryAnalyticsHubDataExchange", - }) - if err := reader.Get(ctx, key, exchange); err != nil { - if apierrors.IsNotFound(err) { - return nil, fmt.Errorf("referenced DataExchange %v not found", key) - } - return nil, fmt.Errorf("error reading referenced DataExchange %v: %w", key, err) - } - - resourceID, _, err := unstructured.NestedString(exchange.Object, "spec", "resourceID") - if err != nil { - return nil, fmt.Errorf("reading spec.resourceID from DataExchange %s/%s: %w", exchange.GetNamespace(), exchange.GetName(), err) - } - if resourceID == "" { - resourceID = exchange.GetName() - } - - location, _, err := unstructured.NestedString(exchange.Object, "spec", "location") - if err != nil { - return nil, fmt.Errorf("reading spec.region from DataExchange %s/%s: %w", exchange.GetNamespace(), exchange.GetName(), err) - } - - projectID, err := ResolveProjectID(ctx, reader, exchange) - if err != nil { - return nil, err - } - - return &DataExchange{ - ProjectID: projectID, - Location: location, - DataExchangeID: resourceID, - }, nil -} - -func ResolveDataExchangeForObject(ctx context.Context, reader client.Reader, obj *unstructured.Unstructured) (*DataExchange, error) { - dataExchangeRefExternal, _, err := unstructured.NestedString(obj.Object, "spec", "dataExchangeRef", "external") - if err != nil { - return nil, fmt.Errorf("error fetching dataExchangeRef.external %w", err) - } - if dataExchangeRefExternal != "" { - return ResolveDataExchangeRef(ctx, reader, obj, &DataExchangeRef{External: dataExchangeRefExternal}) - } - - dataExchangeRefName, _, err := unstructured.NestedString(obj.Object, "spec", "dataExchangeRef", "name") - if err != nil { - return nil, fmt.Errorf("error fetching dataExchangeRef.name %w", err) - } - if dataExchangeRefName != "" { - dataExchangeRefNs, _, err := unstructured.NestedString(obj.Object, "spec", "dataExchangeRef", "namespace") - if err != nil { - return nil, fmt.Errorf("error fetching dataExchangeRef.namespace %w", err) - } - - dataExchangeRef := DataExchangeRef{ - Name: dataExchangeRefName, - Namespace: dataExchangeRefNs, - } - if dataExchangeRef.Namespace == "" { - dataExchangeRef.Namespace = obj.GetNamespace() - } - - return ResolveDataExchangeRef(ctx, reader, obj, &dataExchangeRef) - } - - return nil, fmt.Errorf("cannot find dataExchangeRef for %v %v/%v", obj.GetKind(), obj.GetNamespace(), obj.GetName()) -} diff --git a/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_bigqueryanalyticshublistings.bigqueryanalyticshub.cnrm.cloud.google.com.yaml b/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_bigqueryanalyticshublistings.bigqueryanalyticshub.cnrm.cloud.google.com.yaml index 055adc95ac..c516f47354 100644 --- a/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_bigqueryanalyticshublistings.bigqueryanalyticshub.cnrm.cloud.google.com.yaml +++ b/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_bigqueryanalyticshublistings.bigqueryanalyticshub.cnrm.cloud.google.com.yaml @@ -65,6 +65,9 @@ spec: type: string type: array dataExchangeRef: + description: BigQueryAnalyticsHubDataExchangeRef defines the resource + reference to BigQueryAnalyticsHubDataExchange, which "External" + field holds the GCP identifier for the KRM object. oneOf: - not: required: @@ -81,14 +84,15 @@ spec: - external properties: external: - description: The DataExchange selfLink, when not managed by Config - Connector. + description: A reference to an externally managed BigQueryAnalyticsHubDataExchange + resource. Should be in the format "projects//locations//dataexchanges/". type: string name: - description: The `name` field of a `DataExchange` resource. + description: The name of a BigQueryAnalyticsHubDataExchange resource. type: string namespace: - description: The `namespace` field of a `DataExchange` resource. + description: The namespace of a BigQueryAnalyticsHubDataExchange + resource. type: string type: object dataProvider: diff --git a/pkg/clients/generated/apis/bigqueryanalyticshub/v1alpha1/bigqueryanalyticshublisting_types.go b/pkg/clients/generated/apis/bigqueryanalyticshub/v1alpha1/bigqueryanalyticshublisting_types.go index 87742bd9d9..09ccf8afbb 100644 --- a/pkg/clients/generated/apis/bigqueryanalyticshub/v1alpha1/bigqueryanalyticshublisting_types.go +++ b/pkg/clients/generated/apis/bigqueryanalyticshub/v1alpha1/bigqueryanalyticshublisting_types.go @@ -99,6 +99,7 @@ type BigQueryAnalyticsHubListingSpec struct { // +optional Categories []string `json:"categories,omitempty"` + /* BigQueryAnalyticsHubDataExchangeRef defines the resource reference to BigQueryAnalyticsHubDataExchange, which "External" field holds the GCP identifier for the KRM object. */ DataExchangeRef v1alpha1.ResourceRef `json:"dataExchangeRef"` /* Optional. Details of the data provider who owns the source data. */ diff --git a/pkg/controller/direct/bigqueryanalyticshub/dataexchange_controller.go b/pkg/controller/direct/bigqueryanalyticshub/dataexchange_controller.go index 9b3ae36eb1..0b7559a970 100644 --- a/pkg/controller/direct/bigqueryanalyticshub/dataexchange_controller.go +++ b/pkg/controller/direct/bigqueryanalyticshub/dataexchange_controller.go @@ -193,7 +193,8 @@ func (a *Adapter) Create(ctx context.Context, createOp *directbase.CreateOperati if mapCtx.Err() != nil { return mapCtx.Err() } - status.ExternalRef = a.id.AsExternalRef() + externalRef := a.id.FullyQualifiedName() + status.ExternalRef = &externalRef return setStatus(u, status) } diff --git a/pkg/controller/direct/bigqueryanalyticshub/dataexchange_externalresource.go b/pkg/controller/direct/bigqueryanalyticshub/dataexchange_externalresource.go index 3e7ae242af..2e091f3767 100644 --- a/pkg/controller/direct/bigqueryanalyticshub/dataexchange_externalresource.go +++ b/pkg/controller/direct/bigqueryanalyticshub/dataexchange_externalresource.go @@ -47,9 +47,6 @@ func (c *DataExchangeIdentity) AsExternalRef() *string { // asID builds a DataExchangeIdentity from a `status.externalRef` func asID(externalRef string) (*DataExchangeIdentity, error) { - if !strings.HasPrefix(externalRef, serviceDomain) { - return nil, fmt.Errorf("externalRef should have prefix %s, got %s", serviceDomain, externalRef) - } path := strings.TrimPrefix(externalRef, serviceDomain+"/") tokens := strings.Split(path, "/") diff --git a/pkg/controller/direct/bigqueryanalyticshub/listing_controller.go b/pkg/controller/direct/bigqueryanalyticshub/listing_controller.go index 7d3883c0ad..31bb2244e0 100644 --- a/pkg/controller/direct/bigqueryanalyticshub/listing_controller.go +++ b/pkg/controller/direct/bigqueryanalyticshub/listing_controller.go @@ -96,9 +96,9 @@ func (m *modelListing) AdapterForObject(ctx context.Context, reader client.Reade func resolveOptionalReferences(ctx context.Context, reader client.Reader, obj *krm.BigQueryAnalyticsHubListing) error { if ref := obj.Spec.DataExchangeRef; ref != nil { - _, err := refs.ResolveDataExchangeRef(ctx, reader, obj, ref) + _, err := ref.NormalizedExternal(ctx, reader, obj.GetNamespace()) if err != nil { - return err + return fmt.Errorf("failed to resolve optional DataExchangeRef: %w", err) } } diff --git a/pkg/test/resourcefixture/testdata/basic/bigqueryanalyticshub/v1beta1/bigqueryanalyticshubdataexchange-base/_generated_object_bigqueryanalyticshubdataexchange-base.golden.yaml b/pkg/test/resourcefixture/testdata/basic/bigqueryanalyticshub/v1beta1/bigqueryanalyticshubdataexchange-base/_generated_object_bigqueryanalyticshubdataexchange-base.golden.yaml index f1a4b7b201..e43fa1ca92 100644 --- a/pkg/test/resourcefixture/testdata/basic/bigqueryanalyticshub/v1beta1/bigqueryanalyticshubdataexchange-base/_generated_object_bigqueryanalyticshubdataexchange-base.golden.yaml +++ b/pkg/test/resourcefixture/testdata/basic/bigqueryanalyticshub/v1beta1/bigqueryanalyticshubdataexchange-base/_generated_object_bigqueryanalyticshubdataexchange-base.golden.yaml @@ -24,6 +24,6 @@ status: reason: UpToDate status: "True" type: Ready - externalRef: //bigqueryanalyticshub.googleapis.com/projects/${projectId}/locations/US/dataExchanges/bigqueryanalyticshubdataexchange${uniqueId} + externalRef: projects/${projectId}/locations/US/dataExchanges/bigqueryanalyticshubdataexchange${uniqueId} observedGeneration: 1 observedState: {} diff --git a/pkg/test/resourcefixture/testdata/basic/bigqueryanalyticshub/v1beta1/bigqueryanalyticshubdataexchange-full/_generated_object_bigqueryanalyticshubdataexchange-full.golden.yaml b/pkg/test/resourcefixture/testdata/basic/bigqueryanalyticshub/v1beta1/bigqueryanalyticshubdataexchange-full/_generated_object_bigqueryanalyticshubdataexchange-full.golden.yaml index f9cf75f71d..ee75f852a4 100644 --- a/pkg/test/resourcefixture/testdata/basic/bigqueryanalyticshub/v1beta1/bigqueryanalyticshubdataexchange-full/_generated_object_bigqueryanalyticshubdataexchange-full.golden.yaml +++ b/pkg/test/resourcefixture/testdata/basic/bigqueryanalyticshub/v1beta1/bigqueryanalyticshubdataexchange-full/_generated_object_bigqueryanalyticshubdataexchange-full.golden.yaml @@ -28,6 +28,6 @@ status: reason: UpToDate status: "True" type: Ready - externalRef: //bigqueryanalyticshub.googleapis.com/projects/${projectId}/locations/US/dataExchanges/bigqueryanalyticshubdataexchange${uniqueId} + externalRef: projects/${projectId}/locations/US/dataExchanges/bigqueryanalyticshubdataexchange${uniqueId} observedGeneration: 2 observedState: {}