diff --git a/apis/firestore/v1alpha1/backupschedule_identity.go b/apis/firestore/v1alpha1/backupschedule_identity.go new file mode 100644 index 00000000000..c880469b476 --- /dev/null +++ b/apis/firestore/v1alpha1/backupschedule_identity.go @@ -0,0 +1,15 @@ +// Copyright 2025 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 diff --git a/apis/firestore/v1alpha1/backupschedule_reference.go b/apis/firestore/v1alpha1/backupschedule_reference.go new file mode 100644 index 00000000000..c880469b476 --- /dev/null +++ b/apis/firestore/v1alpha1/backupschedule_reference.go @@ -0,0 +1,15 @@ +// Copyright 2025 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 diff --git a/apis/firestore/v1alpha1/backupschedule_types.go b/apis/firestore/v1alpha1/backupschedule_types.go new file mode 100644 index 00000000000..49bfcc75aae --- /dev/null +++ b/apis/firestore/v1alpha1/backupschedule_types.go @@ -0,0 +1,122 @@ +// Copyright 2025 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 ( + v1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/firestore/v1beta1" + "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var FirestoreDatabaseBackupScheduleGVK = GroupVersion.WithKind("FirestoreDatabaseBackupSchedule") + +// FirestoreDatabaseBackupScheduleSpec defines the desired state of FirestoreDatabaseBackupSchedule +// +kcc:spec:proto=google.firestore.admin.v1.BackupSchedule +type FirestoreDatabaseBackupScheduleSpec struct { + /* The database that this resource belongs to. */ + // +required + DatabaseRef v1beta1.FirestoreDatabaseRef `json:"databaseRef"` + + // At what relative time in the future, compared to its creation time, + // the backup should be deleted, e.g. keep backups for 7 days. + // + // The maximum supported retention period is 14 weeks. + // +kcc:proto:field=google.firestore.admin.v1.BackupSchedule.retention + Retention *string `json:"retention,omitempty"` + + // For a schedule that runs daily. + // +kcc:proto:field=google.firestore.admin.v1.BackupSchedule.daily_recurrence + DailyRecurrence *DailyRecurrence `json:"dailyRecurrence,omitempty"` + + // For a schedule that runs weekly on a specific day. + // +kcc:proto:field=google.firestore.admin.v1.BackupSchedule.weekly_recurrence + WeeklyRecurrence *WeeklyRecurrence `json:"weeklyRecurrence,omitempty"` +} + +// FirestoreDatabaseBackupScheduleStatus defines the config connector machine state of FirestoreDatabaseBackupSchedule +type FirestoreDatabaseBackupScheduleStatus struct { + /* Conditions represent the latest available observations of the + object's current state. */ + Conditions []v1alpha1.Condition `json:"conditions,omitempty"` + + // ObservedGeneration is the generation of the resource that was most recently observed by the Config Connector controller. If this is equal to metadata.generation, then that means that the current reported status reflects the most recent desired state of the resource. + ObservedGeneration *int64 `json:"observedGeneration,omitempty"` + + // A unique specifier for the FirestoreDatabaseBackupSchedule resource in GCP. + ExternalRef *string `json:"externalRef,omitempty"` + + // ObservedState is the state of the resource as most recently observed in GCP. + ObservedState *FirestoreDatabaseBackupScheduleObservedState `json:"observedState,omitempty"` +} + +// FirestoreDatabaseBackupScheduleObservedState is the state of the FirestoreDatabaseBackupSchedule resource as most recently observed in GCP. +// +kcc:observedstate:proto=google.firestore.admin.v1.BackupSchedule +type FirestoreDatabaseBackupScheduleObservedState struct { + // Output only. The unique backup schedule identifier across all locations and + // databases for the given project. + // + // This will be auto-assigned. + // + // Format is + // `projects/{project}/databases/{database}/backupSchedules/{backup_schedule}` + // +kcc:proto:field=google.firestore.admin.v1.BackupSchedule.name + Name *string `json:"name,omitempty"` + + // Output only. The timestamp at which this backup schedule was created and + // effective since. + // + // No backups will be created for this schedule before this time. + // +kcc:proto:field=google.firestore.admin.v1.BackupSchedule.create_time + CreateTime *string `json:"createTime,omitempty"` + + // Output only. The timestamp at which this backup schedule was most recently + // updated. When a backup schedule is first created, this is the same as + // create_time. + // +kcc:proto:field=google.firestore.admin.v1.BackupSchedule.update_time + UpdateTime *string `json:"updateTime,omitempty"` +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:categories=gcp,shortName=gcpfirestoredatabasebackupschedule;gcpfirestoredatabasebackupschedules +// +kubebuilder:subresource:status +// +kubebuilder:metadata:labels="cnrm.cloud.google.com/managed-by-kcc=true";"cnrm.cloud.google.com/system=true" +// +kubebuilder:printcolumn:name="Age",JSONPath=".metadata.creationTimestamp",type="date" +// +kubebuilder:printcolumn:name="Ready",JSONPath=".status.conditions[?(@.type=='Ready')].status",type="string",description="When 'True', the most recent reconcile of the resource succeeded" +// +kubebuilder:printcolumn:name="Status",JSONPath=".status.conditions[?(@.type=='Ready')].reason",type="string",description="The reason for the value in 'Ready'" +// +kubebuilder:printcolumn:name="Status Age",JSONPath=".status.conditions[?(@.type=='Ready')].lastTransitionTime",type="date",description="The last transition time for the value in 'Status'" + +// FirestoreDatabaseBackupSchedule is the Schema for the FirestoreDatabaseBackupSchedule API +// +k8s:openapi-gen=true +type FirestoreDatabaseBackupSchedule struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // +required + Spec FirestoreDatabaseBackupScheduleSpec `json:"spec,omitempty"` + Status FirestoreDatabaseBackupScheduleStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// FirestoreDatabaseBackupScheduleList contains a list of FirestoreDatabaseBackupSchedule +type FirestoreDatabaseBackupScheduleList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []FirestoreDatabaseBackupSchedule `json:"items"` +} + +func init() { + SchemeBuilder.Register(&FirestoreDatabaseBackupSchedule{}, &FirestoreDatabaseBackupScheduleList{}) +} diff --git a/apis/firestore/v1alpha1/document_identity.go b/apis/firestore/v1alpha1/document_identity.go new file mode 100644 index 00000000000..c880469b476 --- /dev/null +++ b/apis/firestore/v1alpha1/document_identity.go @@ -0,0 +1,15 @@ +// Copyright 2025 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 diff --git a/apis/firestore/v1alpha1/document_reference.go b/apis/firestore/v1alpha1/document_reference.go new file mode 100644 index 00000000000..c880469b476 --- /dev/null +++ b/apis/firestore/v1alpha1/document_reference.go @@ -0,0 +1,15 @@ +// Copyright 2025 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 diff --git a/apis/firestore/v1alpha1/document_types.go b/apis/firestore/v1alpha1/document_types.go new file mode 100644 index 00000000000..22eaba43520 --- /dev/null +++ b/apis/firestore/v1alpha1/document_types.go @@ -0,0 +1,106 @@ +// Copyright 2025 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 ( + "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var FirestoreDocumentGVK = GroupVersion.WithKind("FirestoreDocument") + +// FirestoreDocumentSpec defines the desired state of FirestoreDocument +// +kcc:spec:proto=google.firestore.v1.Document +type FirestoreDocumentSpec struct { + // The FirestoreDocument name. If not given, the metadata.name will be used. + ResourceID *string `json:"resourceID,omitempty"` + + // // The resource name of the document, for example + // // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. + // // +kcc:proto:field=google.firestore.v1.Document.name + // Name *string `json:"name,omitempty"` + + // Fields holds the field values; values follow JSON typing conventions. + Fields map[string]string `json:"fields,omitempty"` +} + +// FirestoreDocumentStatus defines the config connector machine state of FirestoreDocument +type FirestoreDocumentStatus struct { + /* Conditions represent the latest available observations of the + object's current state. */ + Conditions []v1alpha1.Condition `json:"conditions,omitempty"` + + // ObservedGeneration is the generation of the resource that was most recently observed by the Config Connector controller. If this is equal to metadata.generation, then that means that the current reported status reflects the most recent desired state of the resource. + ObservedGeneration *int64 `json:"observedGeneration,omitempty"` + + // A unique specifier for the FirestoreDocument resource in GCP. + ExternalRef *string `json:"externalRef,omitempty"` + + // ObservedState is the state of the resource as most recently observed in GCP. + ObservedState *FirestoreDocumentObservedState `json:"observedState,omitempty"` +} + +// FirestoreDocumentObservedState is the state of the FirestoreDocument resource as most recently observed in GCP. +// +kcc:observedstate:proto=google.firestore.v1.Document +type FirestoreDocumentObservedState struct { + // Output only. The time at which the document was created. + // + // This value increases monotonically when a document is deleted then + // recreated. It can also be compared to values from other documents and + // the `read_time` of a query. + // +kcc:proto:field=google.firestore.v1.Document.create_time + CreateTime *string `json:"createTime,omitempty"` + + // Output only. The time at which the document was last changed. + // + // This value is initially set to the `create_time` then increases + // monotonically with each change to the document. It can also be + // compared to values from other documents and the `read_time` of a query. + // +kcc:proto:field=google.firestore.v1.Document.update_time + UpdateTime *string `json:"updateTime,omitempty"` +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:categories=gcp,shortName=gcpfirestoredocument;gcpfirestoredocuments +// +kubebuilder:subresource:status +// +kubebuilder:metadata:labels="cnrm.cloud.google.com/managed-by-kcc=true";"cnrm.cloud.google.com/system=true" +// +kubebuilder:printcolumn:name="Age",JSONPath=".metadata.creationTimestamp",type="date" +// +kubebuilder:printcolumn:name="Ready",JSONPath=".status.conditions[?(@.type=='Ready')].status",type="string",description="When 'True', the most recent reconcile of the resource succeeded" +// +kubebuilder:printcolumn:name="Status",JSONPath=".status.conditions[?(@.type=='Ready')].reason",type="string",description="The reason for the value in 'Ready'" +// +kubebuilder:printcolumn:name="Status Age",JSONPath=".status.conditions[?(@.type=='Ready')].lastTransitionTime",type="date",description="The last transition time for the value in 'Status'" + +// FirestoreDocument is the Schema for the FirestoreDocument API +// +k8s:openapi-gen=true +type FirestoreDocument struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // +required + Spec FirestoreDocumentSpec `json:"spec,omitempty"` + Status FirestoreDocumentStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// FirestoreDocumentList contains a list of FirestoreDocument +type FirestoreDocumentList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []FirestoreDocument `json:"items"` +} + +func init() { + SchemeBuilder.Register(&FirestoreDocument{}, &FirestoreDocumentList{}) +} diff --git a/apis/firestore/v1alpha1/generate.sh b/apis/firestore/v1alpha1/generate.sh index cf8430933fa..d5937993b6e 100755 --- a/apis/firestore/v1alpha1/generate.sh +++ b/apis/firestore/v1alpha1/generate.sh @@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. - set -o errexit set -o nounset set -o pipefail @@ -21,18 +20,21 @@ set -o pipefail REPO_ROOT="$(git rev-parse --show-toplevel)" cd ${REPO_ROOT}/dev/tools/controllerbuilder -go run . generate-types \ + go run . generate-types \ --service google.firestore.admin.v1 \ --api-version firestore.cnrm.cloud.google.com/v1alpha1 \ + --resource FirestoreDocument:google.firestore.v1.Document \ + --resource FirestoreDatabaseBackupSchedule:BackupSchedule \ --resource FirestoreField:Field go run . generate-mapper \ --multiversion \ --service google.firestore.admin.v1 \ + --service google.firestore.v1 \ --api-version firestore.cnrm.cloud.google.com/v1alpha1 - cd ${REPO_ROOT} + dev/tasks/generate-crds -go run -mod=readonly golang.org/x/tools/cmd/goimports@latest -w pkg/controller/direct/firestore/ +go run -mod=readonly golang.org/x/tools/cmd/goimports@latest -w pkg/controller/direct/firestore/ diff --git a/apis/firestore/v1alpha1/types.generated.go b/apis/firestore/v1alpha1/types.generated.go index 69f2892f0dd..22396379c44 100644 --- a/apis/firestore/v1alpha1/types.generated.go +++ b/apis/firestore/v1alpha1/types.generated.go @@ -16,10 +16,16 @@ // krm.group: firestore.cnrm.cloud.google.com // krm.version: v1alpha1 // proto.service: google.firestore.admin.v1 +// resource: FirestoreDocument:google.firestore.v1.Document +// resource: FirestoreDatabaseBackupSchedule:BackupSchedule // resource: FirestoreField:Field package v1alpha1 +// +kcc:proto=google.firestore.admin.v1.DailyRecurrence +type DailyRecurrence struct { +} + // +kcc:proto=google.firestore.admin.v1.Field.TtlConfig type Field_TTLConfig struct { } @@ -65,6 +71,101 @@ type Index_IndexField_VectorConfig struct { type Index_IndexField_VectorConfig_FlatIndex struct { } +// +kcc:proto=google.firestore.admin.v1.WeeklyRecurrence +type WeeklyRecurrence struct { + // The day of week to run. + // + // DAY_OF_WEEK_UNSPECIFIED is not allowed. + // +kcc:proto:field=google.firestore.admin.v1.WeeklyRecurrence.day + Day *string `json:"day,omitempty"` +} + +// +kcc:proto=google.firestore.v1.ArrayValue +type ArrayValue struct { + // Values in the array. + // +kcc:proto:field=google.firestore.v1.ArrayValue.values + Values []Value `json:"values,omitempty"` +} + +// +kcc:proto=google.firestore.v1.MapValue +type MapValue struct { + + // TODO: unsupported map type with key string and value message + +} + +// +kcc:proto=google.firestore.v1.Value +type Value struct { + // A null value. + // +kcc:proto:field=google.firestore.v1.Value.null_value + NullValue *string `json:"nullValue,omitempty"` + + // A boolean value. + // +kcc:proto:field=google.firestore.v1.Value.boolean_value + BooleanValue *bool `json:"booleanValue,omitempty"` + + // An integer value. + // +kcc:proto:field=google.firestore.v1.Value.integer_value + IntegerValue *int64 `json:"integerValue,omitempty"` + + // A double value. + // +kcc:proto:field=google.firestore.v1.Value.double_value + DoubleValue *float64 `json:"doubleValue,omitempty"` + + // A timestamp value. + // + // Precise only to microseconds. When stored, any additional precision is + // rounded down. + // +kcc:proto:field=google.firestore.v1.Value.timestamp_value + TimestampValue *string `json:"timestampValue,omitempty"` + + // A string value. + // + // The string, represented as UTF-8, must not exceed 1 MiB - 89 bytes. + // Only the first 1,500 bytes of the UTF-8 representation are considered by + // queries. + // +kcc:proto:field=google.firestore.v1.Value.string_value + StringValue *string `json:"stringValue,omitempty"` + + // A bytes value. + // + // Must not exceed 1 MiB - 89 bytes. + // Only the first 1,500 bytes are considered by queries. + // +kcc:proto:field=google.firestore.v1.Value.bytes_value + BytesValue []byte `json:"bytesValue,omitempty"` + + // A reference to a document. For example: + // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. + // +kcc:proto:field=google.firestore.v1.Value.reference_value + ReferenceValue *string `json:"referenceValue,omitempty"` + + // A geo point value representing a point on the surface of Earth. + // +kcc:proto:field=google.firestore.v1.Value.geo_point_value + GeoPointValue *LatLng `json:"geoPointValue,omitempty"` + + // An array value. + // + // Cannot directly contain another array value, though can contain a + // map which contains another array. + // +kcc:proto:field=google.firestore.v1.Value.array_value + ArrayValue *ArrayValue `json:"arrayValue,omitempty"` + + // A map value. + // +kcc:proto:field=google.firestore.v1.Value.map_value + MapValue *MapValue `json:"mapValue,omitempty"` +} + +// +kcc:proto=google.type.LatLng +type LatLng struct { + // The latitude in degrees. It must be in the range [-90.0, +90.0]. + // +kcc:proto:field=google.type.LatLng.latitude + Latitude *float64 `json:"latitude,omitempty"` + + // The longitude in degrees. It must be in the range [-180.0, +180.0]. + // +kcc:proto:field=google.type.LatLng.longitude + Longitude *float64 `json:"longitude,omitempty"` +} + // +kcc:observedstate:proto=google.firestore.admin.v1.Field.TtlConfig type Field_TTLConfigObservedState struct { // Output only. The state of the TTL configuration. diff --git a/apis/firestore/v1alpha1/zz_generated.deepcopy.go b/apis/firestore/v1alpha1/zz_generated.deepcopy.go index 064cd4b339e..1fe10dc94d0 100644 --- a/apis/firestore/v1alpha1/zz_generated.deepcopy.go +++ b/apis/firestore/v1alpha1/zz_generated.deepcopy.go @@ -23,6 +23,43 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ArrayValue) DeepCopyInto(out *ArrayValue) { + *out = *in + if in.Values != nil { + in, out := &in.Values, &out.Values + *out = make([]Value, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArrayValue. +func (in *ArrayValue) DeepCopy() *ArrayValue { + if in == nil { + return nil + } + out := new(ArrayValue) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DailyRecurrence) DeepCopyInto(out *DailyRecurrence) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DailyRecurrence. +func (in *DailyRecurrence) DeepCopy() *DailyRecurrence { + if in == nil { + return nil + } + out := new(DailyRecurrence) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Field_IndexConfig) DeepCopyInto(out *Field_IndexConfig) { *out = *in @@ -117,6 +154,307 @@ func (in *Field_TTLConfigObservedState) DeepCopy() *Field_TTLConfigObservedState return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FirestoreDatabaseBackupSchedule) DeepCopyInto(out *FirestoreDatabaseBackupSchedule) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirestoreDatabaseBackupSchedule. +func (in *FirestoreDatabaseBackupSchedule) DeepCopy() *FirestoreDatabaseBackupSchedule { + if in == nil { + return nil + } + out := new(FirestoreDatabaseBackupSchedule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FirestoreDatabaseBackupSchedule) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FirestoreDatabaseBackupScheduleList) DeepCopyInto(out *FirestoreDatabaseBackupScheduleList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]FirestoreDatabaseBackupSchedule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirestoreDatabaseBackupScheduleList. +func (in *FirestoreDatabaseBackupScheduleList) DeepCopy() *FirestoreDatabaseBackupScheduleList { + if in == nil { + return nil + } + out := new(FirestoreDatabaseBackupScheduleList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FirestoreDatabaseBackupScheduleList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FirestoreDatabaseBackupScheduleObservedState) DeepCopyInto(out *FirestoreDatabaseBackupScheduleObservedState) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(string) + **out = **in + } + if in.CreateTime != nil { + in, out := &in.CreateTime, &out.CreateTime + *out = new(string) + **out = **in + } + if in.UpdateTime != nil { + in, out := &in.UpdateTime, &out.UpdateTime + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirestoreDatabaseBackupScheduleObservedState. +func (in *FirestoreDatabaseBackupScheduleObservedState) DeepCopy() *FirestoreDatabaseBackupScheduleObservedState { + if in == nil { + return nil + } + out := new(FirestoreDatabaseBackupScheduleObservedState) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FirestoreDatabaseBackupScheduleSpec) DeepCopyInto(out *FirestoreDatabaseBackupScheduleSpec) { + *out = *in + out.DatabaseRef = in.DatabaseRef + if in.Retention != nil { + in, out := &in.Retention, &out.Retention + *out = new(string) + **out = **in + } + if in.DailyRecurrence != nil { + in, out := &in.DailyRecurrence, &out.DailyRecurrence + *out = new(DailyRecurrence) + **out = **in + } + if in.WeeklyRecurrence != nil { + in, out := &in.WeeklyRecurrence, &out.WeeklyRecurrence + *out = new(WeeklyRecurrence) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirestoreDatabaseBackupScheduleSpec. +func (in *FirestoreDatabaseBackupScheduleSpec) DeepCopy() *FirestoreDatabaseBackupScheduleSpec { + if in == nil { + return nil + } + out := new(FirestoreDatabaseBackupScheduleSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FirestoreDatabaseBackupScheduleStatus) DeepCopyInto(out *FirestoreDatabaseBackupScheduleStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]k8sv1alpha1.Condition, len(*in)) + copy(*out, *in) + } + if in.ObservedGeneration != nil { + in, out := &in.ObservedGeneration, &out.ObservedGeneration + *out = new(int64) + **out = **in + } + if in.ExternalRef != nil { + in, out := &in.ExternalRef, &out.ExternalRef + *out = new(string) + **out = **in + } + if in.ObservedState != nil { + in, out := &in.ObservedState, &out.ObservedState + *out = new(FirestoreDatabaseBackupScheduleObservedState) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirestoreDatabaseBackupScheduleStatus. +func (in *FirestoreDatabaseBackupScheduleStatus) DeepCopy() *FirestoreDatabaseBackupScheduleStatus { + if in == nil { + return nil + } + out := new(FirestoreDatabaseBackupScheduleStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FirestoreDocument) DeepCopyInto(out *FirestoreDocument) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirestoreDocument. +func (in *FirestoreDocument) DeepCopy() *FirestoreDocument { + if in == nil { + return nil + } + out := new(FirestoreDocument) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FirestoreDocument) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FirestoreDocumentList) DeepCopyInto(out *FirestoreDocumentList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]FirestoreDocument, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirestoreDocumentList. +func (in *FirestoreDocumentList) DeepCopy() *FirestoreDocumentList { + if in == nil { + return nil + } + out := new(FirestoreDocumentList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FirestoreDocumentList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FirestoreDocumentObservedState) DeepCopyInto(out *FirestoreDocumentObservedState) { + *out = *in + if in.CreateTime != nil { + in, out := &in.CreateTime, &out.CreateTime + *out = new(string) + **out = **in + } + if in.UpdateTime != nil { + in, out := &in.UpdateTime, &out.UpdateTime + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirestoreDocumentObservedState. +func (in *FirestoreDocumentObservedState) DeepCopy() *FirestoreDocumentObservedState { + if in == nil { + return nil + } + out := new(FirestoreDocumentObservedState) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FirestoreDocumentSpec) DeepCopyInto(out *FirestoreDocumentSpec) { + *out = *in + if in.ResourceID != nil { + in, out := &in.ResourceID, &out.ResourceID + *out = new(string) + **out = **in + } + if in.Fields != nil { + in, out := &in.Fields, &out.Fields + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirestoreDocumentSpec. +func (in *FirestoreDocumentSpec) DeepCopy() *FirestoreDocumentSpec { + if in == nil { + return nil + } + out := new(FirestoreDocumentSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FirestoreDocumentStatus) DeepCopyInto(out *FirestoreDocumentStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]k8sv1alpha1.Condition, len(*in)) + copy(*out, *in) + } + if in.ObservedGeneration != nil { + in, out := &in.ObservedGeneration, &out.ObservedGeneration + *out = new(int64) + **out = **in + } + if in.ExternalRef != nil { + in, out := &in.ExternalRef, &out.ExternalRef + *out = new(string) + **out = **in + } + if in.ObservedState != nil { + in, out := &in.ObservedState, &out.ObservedState + *out = new(FirestoreDocumentObservedState) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirestoreDocumentStatus. +func (in *FirestoreDocumentStatus) DeepCopy() *FirestoreDocumentStatus { + if in == nil { + return nil + } + out := new(FirestoreDocumentStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FirestoreField) DeepCopyInto(out *FirestoreField) { *out = *in @@ -412,3 +750,133 @@ func (in *Index_ObservedState) DeepCopy() *Index_ObservedState { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LatLng) DeepCopyInto(out *LatLng) { + *out = *in + if in.Latitude != nil { + in, out := &in.Latitude, &out.Latitude + *out = new(float64) + **out = **in + } + if in.Longitude != nil { + in, out := &in.Longitude, &out.Longitude + *out = new(float64) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LatLng. +func (in *LatLng) DeepCopy() *LatLng { + if in == nil { + return nil + } + out := new(LatLng) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MapValue) DeepCopyInto(out *MapValue) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MapValue. +func (in *MapValue) DeepCopy() *MapValue { + if in == nil { + return nil + } + out := new(MapValue) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Value) DeepCopyInto(out *Value) { + *out = *in + if in.NullValue != nil { + in, out := &in.NullValue, &out.NullValue + *out = new(string) + **out = **in + } + if in.BooleanValue != nil { + in, out := &in.BooleanValue, &out.BooleanValue + *out = new(bool) + **out = **in + } + if in.IntegerValue != nil { + in, out := &in.IntegerValue, &out.IntegerValue + *out = new(int64) + **out = **in + } + if in.DoubleValue != nil { + in, out := &in.DoubleValue, &out.DoubleValue + *out = new(float64) + **out = **in + } + if in.TimestampValue != nil { + in, out := &in.TimestampValue, &out.TimestampValue + *out = new(string) + **out = **in + } + if in.StringValue != nil { + in, out := &in.StringValue, &out.StringValue + *out = new(string) + **out = **in + } + if in.BytesValue != nil { + in, out := &in.BytesValue, &out.BytesValue + *out = make([]byte, len(*in)) + copy(*out, *in) + } + if in.ReferenceValue != nil { + in, out := &in.ReferenceValue, &out.ReferenceValue + *out = new(string) + **out = **in + } + if in.GeoPointValue != nil { + in, out := &in.GeoPointValue, &out.GeoPointValue + *out = new(LatLng) + (*in).DeepCopyInto(*out) + } + if in.ArrayValue != nil { + in, out := &in.ArrayValue, &out.ArrayValue + *out = new(ArrayValue) + (*in).DeepCopyInto(*out) + } + if in.MapValue != nil { + in, out := &in.MapValue, &out.MapValue + *out = new(MapValue) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Value. +func (in *Value) DeepCopy() *Value { + if in == nil { + return nil + } + out := new(Value) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WeeklyRecurrence) DeepCopyInto(out *WeeklyRecurrence) { + *out = *in + if in.Day != nil { + in, out := &in.Day, &out.Day + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WeeklyRecurrence. +func (in *WeeklyRecurrence) DeepCopy() *WeeklyRecurrence { + if in == nil { + return nil + } + out := new(WeeklyRecurrence) + in.DeepCopyInto(out) + return out +} diff --git a/apis/firestore/v1beta1/database_reference.go b/apis/firestore/v1beta1/database_reference.go index 1c8f7ee1ca9..d7956f87f70 100644 --- a/apis/firestore/v1beta1/database_reference.go +++ b/apis/firestore/v1beta1/database_reference.go @@ -14,4 +14,57 @@ package v1beta1 -// Not yet needed +import ( + "context" + + refsv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var _ refsv1beta1.Ref = &FirestoreDatabaseRef{} + +// FirestoreDatabaseRef is a reference to a FirestoreDatabase resource. +type FirestoreDatabaseRef struct { + // A reference to an externally managed Firestore database resource. + // Should be in the format "projects/{{projectID}}/databases/{{databaseID}}". + External string `json:"external,omitempty"` + + // The name of a FirestoreDatabase resource. + Name string `json:"name,omitempty"` + + // The namespace of a FirestoreDatabase resource. + Namespace string `json:"namespace,omitempty"` +} + +func (r *FirestoreDatabaseRef) GetGVK() schema.GroupVersionKind { + return FirestoreDatabaseGVK +} + +func (r *FirestoreDatabaseRef) GetNamespacedName() types.NamespacedName { + return types.NamespacedName{ + Name: r.Name, + Namespace: r.Namespace, + } +} + +func (r *FirestoreDatabaseRef) GetExternal() string { + return r.External +} + +func (r *FirestoreDatabaseRef) SetExternal(ref string) { + r.External = ref +} + +func (r *FirestoreDatabaseRef) ValidateExternal(ref string) error { + id := &FirestoreDatabaseIdentity{} + if err := id.FromExternal(r.GetExternal()); err != nil { + return err + } + return nil +} + +func (r *FirestoreDatabaseRef) Normalize(ctx context.Context, reader client.Reader, defaultNamespace string) error { + return refsv1beta1.Normalize(ctx, reader, r, defaultNamespace) +} diff --git a/apis/firestore/v1beta1/generate.sh b/apis/firestore/v1beta1/generate.sh index 8fd6cff818a..c9c4075c174 100755 --- a/apis/firestore/v1beta1/generate.sh +++ b/apis/firestore/v1beta1/generate.sh @@ -30,6 +30,7 @@ go run . generate-types \ go run . generate-mapper \ --multiversion \ --service google.firestore.admin.v1 \ + --service google.firestore.v1 \ --api-version firestore.cnrm.cloud.google.com/v1beta1 cd ${REPO_ROOT} diff --git a/apis/firestore/v1beta1/zz_generated.deepcopy.go b/apis/firestore/v1beta1/zz_generated.deepcopy.go index 3c0e749438c..904be51a51e 100644 --- a/apis/firestore/v1beta1/zz_generated.deepcopy.go +++ b/apis/firestore/v1beta1/zz_generated.deepcopy.go @@ -217,6 +217,21 @@ func (in *FirestoreDatabaseObservedState) DeepCopy() *FirestoreDatabaseObservedS return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FirestoreDatabaseRef) DeepCopyInto(out *FirestoreDatabaseRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirestoreDatabaseRef. +func (in *FirestoreDatabaseRef) DeepCopy() *FirestoreDatabaseRef { + if in == nil { + return nil + } + out := new(FirestoreDatabaseRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FirestoreDatabaseSpec) DeepCopyInto(out *FirestoreDatabaseSpec) { *out = *in diff --git a/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_firestoredatabasebackupschedules.firestore.cnrm.cloud.google.com.yaml b/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_firestoredatabasebackupschedules.firestore.cnrm.cloud.google.com.yaml new file mode 100644 index 00000000000..2db858f6e9e --- /dev/null +++ b/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_firestoredatabasebackupschedules.firestore.cnrm.cloud.google.com.yaml @@ -0,0 +1,191 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cnrm.cloud.google.com/version: 0.0.0-dev + creationTimestamp: null + labels: + cnrm.cloud.google.com/managed-by-kcc: "true" + cnrm.cloud.google.com/system: "true" + name: firestoredatabasebackupschedules.firestore.cnrm.cloud.google.com +spec: + group: firestore.cnrm.cloud.google.com + names: + categories: + - gcp + kind: FirestoreDatabaseBackupSchedule + listKind: FirestoreDatabaseBackupScheduleList + plural: firestoredatabasebackupschedules + shortNames: + - gcpfirestoredatabasebackupschedule + - gcpfirestoredatabasebackupschedules + singular: firestoredatabasebackupschedule + preserveUnknownFields: false + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: When 'True', the most recent reconcile of the resource succeeded + jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - description: The reason for the value in 'Ready' + jsonPath: .status.conditions[?(@.type=='Ready')].reason + name: Status + type: string + - description: The last transition time for the value in 'Status' + jsonPath: .status.conditions[?(@.type=='Ready')].lastTransitionTime + name: Status Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: FirestoreDatabaseBackupSchedule is the Schema for the FirestoreDatabaseBackupSchedule + API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: FirestoreDatabaseBackupScheduleSpec defines the desired state + of FirestoreDatabaseBackupSchedule + properties: + dailyRecurrence: + description: For a schedule that runs daily. + type: object + databaseRef: + description: The database that this resource belongs to. + oneOf: + - not: + required: + - external + required: + - name + - not: + anyOf: + - required: + - name + - required: + - namespace + required: + - external + properties: + external: + description: A reference to an externally managed Firestore database + resource. Should be in the format "projects/{{projectID}}/databases/{{databaseID}}". + type: string + name: + description: The name of a FirestoreDatabase resource. + type: string + namespace: + description: The namespace of a FirestoreDatabase resource. + type: string + type: object + retention: + description: |- + At what relative time in the future, compared to its creation time, + the backup should be deleted, e.g. keep backups for 7 days. + + The maximum supported retention period is 14 weeks. + type: string + weeklyRecurrence: + description: For a schedule that runs weekly on a specific day. + properties: + day: + description: |- + The day of week to run. + + DAY_OF_WEEK_UNSPECIFIED is not allowed. + type: string + type: object + required: + - databaseRef + type: object + status: + description: FirestoreDatabaseBackupScheduleStatus defines the config + connector machine state of FirestoreDatabaseBackupSchedule + properties: + conditions: + description: Conditions represent the latest available observations + of the object's current state. + items: + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + type: string + message: + description: Human-readable message indicating details about + last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's + last transition. + type: string + status: + description: Status is the status of the condition. Can be True, + False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + type: array + externalRef: + description: A unique specifier for the FirestoreDatabaseBackupSchedule + resource in GCP. + type: string + observedGeneration: + description: ObservedGeneration is the generation of the resource + that was most recently observed by the Config Connector controller. + If this is equal to metadata.generation, then that means that the + current reported status reflects the most recent desired state of + the resource. + format: int64 + type: integer + observedState: + description: ObservedState is the state of the resource as most recently + observed in GCP. + properties: + createTime: + description: |- + Output only. The timestamp at which this backup schedule was created and + effective since. + + No backups will be created for this schedule before this time. + type: string + name: + description: |- + Output only. The unique backup schedule identifier across all locations and + databases for the given project. + + This will be auto-assigned. + + Format is + `projects/{project}/databases/{database}/backupSchedules/{backup_schedule}` + type: string + updateTime: + description: Output only. The timestamp at which this backup schedule + was most recently updated. When a backup schedule is first created, + this is the same as create_time. + type: string + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_firestoredocuments.firestore.cnrm.cloud.google.com.yaml b/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_firestoredocuments.firestore.cnrm.cloud.google.com.yaml new file mode 100644 index 00000000000..1e98abb3abf --- /dev/null +++ b/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_firestoredocuments.firestore.cnrm.cloud.google.com.yaml @@ -0,0 +1,143 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cnrm.cloud.google.com/version: 0.0.0-dev + creationTimestamp: null + labels: + cnrm.cloud.google.com/managed-by-kcc: "true" + cnrm.cloud.google.com/system: "true" + name: firestoredocuments.firestore.cnrm.cloud.google.com +spec: + group: firestore.cnrm.cloud.google.com + names: + categories: + - gcp + kind: FirestoreDocument + listKind: FirestoreDocumentList + plural: firestoredocuments + shortNames: + - gcpfirestoredocument + - gcpfirestoredocuments + singular: firestoredocument + preserveUnknownFields: false + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: When 'True', the most recent reconcile of the resource succeeded + jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - description: The reason for the value in 'Ready' + jsonPath: .status.conditions[?(@.type=='Ready')].reason + name: Status + type: string + - description: The last transition time for the value in 'Status' + jsonPath: .status.conditions[?(@.type=='Ready')].lastTransitionTime + name: Status Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: FirestoreDocument is the Schema for the FirestoreDocument API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: FirestoreDocumentSpec defines the desired state of FirestoreDocument + properties: + fields: + additionalProperties: + type: string + description: Fields holds the field values; values follow JSON typing + conventions. + type: object + resourceID: + description: The FirestoreDocument name. If not given, the metadata.name + will be used. + type: string + type: object + status: + description: FirestoreDocumentStatus defines the config connector machine + state of FirestoreDocument + properties: + conditions: + description: Conditions represent the latest available observations + of the object's current state. + items: + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + type: string + message: + description: Human-readable message indicating details about + last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's + last transition. + type: string + status: + description: Status is the status of the condition. Can be True, + False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + type: array + externalRef: + description: A unique specifier for the FirestoreDocument resource + in GCP. + type: string + observedGeneration: + description: ObservedGeneration is the generation of the resource + that was most recently observed by the Config Connector controller. + If this is equal to metadata.generation, then that means that the + current reported status reflects the most recent desired state of + the resource. + format: int64 + type: integer + observedState: + description: ObservedState is the state of the resource as most recently + observed in GCP. + properties: + createTime: + description: |- + Output only. The time at which the document was created. + + This value increases monotonically when a document is deleted then + recreated. It can also be compared to values from other documents and + the `read_time` of a query. + type: string + updateTime: + description: |- + Output only. The time at which the document was last changed. + + This value is initially set to the `create_time` then increases + monotonically with each change to the document. It can also be + compared to values from other documents and the `read_time` of a query. + type: string + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/dev/tools/controllerbuilder/generate-proto.sh b/dev/tools/controllerbuilder/generate-proto.sh index 1721cb38eb4..67406c98f72 100755 --- a/dev/tools/controllerbuilder/generate-proto.sh +++ b/dev/tools/controllerbuilder/generate-proto.sh @@ -81,7 +81,8 @@ protoc --include_imports --include_source_info \ ${THIRD_PARTY}/googleapis/google/cloud/*/*/*/*.proto \ ${THIRD_PARTY}/googleapis/google/cloud/*/*/*/*/*.proto \ ${THIRD_PARTY}/googleapis/google/dataflow/*/*.proto \ - ${THIRD_PARTY}/googleapis/google/firestore/admin/v1/*.proto \ + ${THIRD_PARTY}/googleapis/google/firestore/*/*.proto \ + ${THIRD_PARTY}/googleapis/google/firestore/*/*/*.proto \ ${THIRD_PARTY}/googleapis/google/iam/v1/*.proto \ ${THIRD_PARTY}/googleapis/google/logging/v2/*.proto \ ${THIRD_PARTY}/googleapis/google/monitoring/v3/*.proto \ diff --git a/dev/tools/controllerbuilder/pkg/codegen/mappergenerator.go b/dev/tools/controllerbuilder/pkg/codegen/mappergenerator.go index 609874777f6..4c242d4601c 100644 --- a/dev/tools/controllerbuilder/pkg/codegen/mappergenerator.go +++ b/dev/tools/controllerbuilder/pkg/codegen/mappergenerator.go @@ -63,6 +63,14 @@ func NewMapperGenerator(goPathForMessage OutputFunc, outputBaseDir string, gener return g } +func (v *MapperGenerator) AddGoImportAlias(goPackage string, alias string) string { + v.importedPackages[goPackage] = importedPackage{ + alias: alias, + goPackage: goPackage, + } + return alias +} + type OutputFunc func(msg protoreflect.MessageDescriptor) (goPath string, shouldWrite bool) func (v *MapperGenerator) VisitGoCode(goPackage string, basePath string) error { @@ -207,10 +215,6 @@ func (v *MapperGenerator) GenerateMappers(goImports map[string]string) error { out.addImport("", "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct") } - v.importedPackages[pair.ProtoGoPackage] = importedPackage{ - alias: "pb", - goPackage: pair.ProtoGoPackage, - } v.writeMapFunctionsForPair(&out.body, out.OutputDir(), &pair) for _, importedPackage := range v.importedPackages { @@ -225,6 +229,7 @@ func (v *MapperGenerator) writeMapFunctionsForPair(out io.Writer, srcDir string, klog.V(2).InfoS("writeMapFunctionsForPair", "pair.Proto.FullName", pair.Proto.FullName(), "pair.KRMType.Name", pair.KRMType.Name) msg := pair.Proto pbTypeName := protoNameForType(msg) + pbTypeGoImport := v.goPackageForProto(msg.ParentFile()) goType := pair.KRMType goTypeName := goType.Name @@ -242,7 +247,7 @@ func (v *MapperGenerator) writeMapFunctionsForPair(out io.Writer, srcDir string, } if v.findFuncDeclaration(goTypeName+versionSpecifier+"_FromProto", srcDir, true) == nil { - fmt.Fprintf(out, "func %s_FromProto(mapCtx *direct.MapContext, in *pb.%s) *%s.%s {\n", goTypeName+versionSpecifier, pbTypeName, krmImportName, goTypeName) + fmt.Fprintf(out, "func %s_FromProto(mapCtx *direct.MapContext, in *%s.%s) *%s.%s {\n", goTypeName+versionSpecifier, pbTypeGoImport, pbTypeName, krmImportName, goTypeName) fmt.Fprintf(out, "\tif in == nil {\n") fmt.Fprintf(out, "\t\treturn nil\n") fmt.Fprintf(out, "\t}\n") @@ -529,14 +534,15 @@ func (v *MapperGenerator) writeMapFunctionsForPair(out io.Writer, srcDir string, } if v.findFuncDeclaration(goTypeName+versionSpecifier+"_ToProto", srcDir, true) == nil { - fmt.Fprintf(out, "func %s_ToProto(mapCtx *direct.MapContext, in *%s.%s) *pb.%s {\n", goTypeName+versionSpecifier, krmImportName, goTypeName, pbTypeName) + fmt.Fprintf(out, "func %s_ToProto(mapCtx *direct.MapContext, in *%s.%s) *%s.%s {\n", goTypeName+versionSpecifier, krmImportName, goTypeName, pbTypeGoImport, pbTypeName) fmt.Fprintf(out, "\tif in == nil {\n") fmt.Fprintf(out, "\t\treturn nil\n") fmt.Fprintf(out, "\t}\n") - fmt.Fprintf(out, "\tout := &pb.%s{}\n", pbTypeName) + fmt.Fprintf(out, "\tout := &%s.%s{}\n", pbTypeGoImport, pbTypeName) for i := 0; i < msg.Fields().Len(); i++ { protoField := msg.Fields().Get(i) protoFieldName := protoNameForField(protoField) + protoFieldPackage := v.goPackageForProto(protoField.ParentFile()) krmFieldName := goFieldName(protoField) krmField := goFields[krmFieldName] @@ -636,12 +642,12 @@ func (v *MapperGenerator) writeMapFunctionsForPair(out io.Writer, srcDir string, functionName = krmToProtoFunctionName(protoField, krmField.Name) } toProtoElemFunc = functionName - protoSliceElemType = "*pb." + protoNameForType(protoField.Message()) + protoSliceElemType = "*" + v.goPackageForProto(protoField.Message().ParentFile()) + "." + protoNameForType(protoField.Message()) case protoreflect.EnumKind: protoTypeName := v.goPackageForProto(protoField.Enum().ParentFile()) + "." + protoNameForEnum(protoField.Enum()) toProtoElemFunc = fmt.Sprintf("direct.Enum_ToProto[%s]", protoTypeName) - protoSliceElemType = "pb." + protoNameForEnum(protoField.Enum()) + protoSliceElemType = v.goPackageForProto(protoField.Message().ParentFile()) + "." + protoNameForEnum(protoField.Enum()) default: protoSliceElemType = goTypeForProtoKind(protoField.Kind()) @@ -767,8 +773,9 @@ func (v *MapperGenerator) writeMapFunctionsForPair(out io.Writer, srcDir string, oneofTypeName := protoNameForOneOf(protoField) - fmt.Fprintf(out, "\t\tout.%s = &pb.%s{%s: oneof}\n", + fmt.Fprintf(out, "\t\tout.%s = &%s.%s{%s: oneof}\n", oneofFieldName, + protoFieldPackage, oneofTypeName, protoFieldName) fmt.Fprintf(out, "\t}\n") @@ -901,7 +908,7 @@ func (v *MapperGenerator) writeMapFunctionsForPair(out io.Writer, srcDir string, } krmFieldType := krmField.Type - oneofWrapperTypeName := "pb." + protoNameForOneOf(protoField) + oneofWrapperTypeName := v.goPackageForProto(protoField.ParentFile()) + "." + protoNameForOneOf(protoField) fmt.Fprintf(out, "func %s(mapCtx *direct.MapContext, in %s) *%s {\n", functionName, krmFieldType, oneofWrapperTypeName) fmt.Fprintf(out, "\tif in == nil {\n") @@ -1158,27 +1165,28 @@ func (o *MapperGenerator) getGoImportAlias(goPackage string) string { return importAlias } -// goPackageForProto returns the go package import alias for a proto message -func (o *MapperGenerator) goPackageForProto(parentFile protoreflect.FileDescriptor) string { - // protoPackage := parentFile.Package() - +// GoPackageForProto returns the full go package import path for a proto message +// For the import alias, see goPackageForProto +func GoPackageForProto(parentFile protoreflect.FileDescriptor) string { fileOptions := parentFile.Options().(*descriptorpb.FileOptions) protoGoPackage := fileOptions.GetGoPackage() if ix := strings.Index(protoGoPackage, ";"); ix != -1 { protoGoPackage = protoGoPackage[:ix] } + return protoGoPackage +} +// goPackageForProto returns the go package import alias for a proto message +func (o *MapperGenerator) goPackageForProto(parentFile protoreflect.FileDescriptor) string { + protoGoPackage := GoPackageForProto(parentFile) existing, found := o.importedPackages[protoGoPackage] if found { return existing.alias } - importAlias := lastComponent(protoGoPackage) + "pb" + importAlias := strings.TrimSuffix(lastComponent(protoGoPackage), "pb") + "pb" - o.importedPackages[protoGoPackage] = importedPackage{ - alias: importAlias, - goPackage: protoGoPackage, - } + o.AddGoImportAlias(protoGoPackage, importAlias) return importAlias } diff --git a/dev/tools/controllerbuilder/pkg/commands/generatecontroller/generatecontrollercommand.go b/dev/tools/controllerbuilder/pkg/commands/generatecontroller/generatecontrollercommand.go index 12f64ad30fc..60da6c85a18 100644 --- a/dev/tools/controllerbuilder/pkg/commands/generatecontroller/generatecontrollercommand.go +++ b/dev/tools/controllerbuilder/pkg/commands/generatecontroller/generatecontrollercommand.go @@ -30,6 +30,8 @@ import ( type GenerateControllerOptions struct { *options.GenerateOptions + ServiceName string + Resource options.Resource } @@ -58,7 +60,7 @@ func BuildCommand(baseOptions *options.GenerateOptions) *cobra.Command { return fmt.Errorf("unable to parse --api-version: %w", err) } - if baseOptions.ServiceName == "" { + if opt.ServiceName == "" { return fmt.Errorf("--service is required") } return nil @@ -79,7 +81,7 @@ func BuildCommand(baseOptions *options.GenerateOptions) *cobra.Command { func RunController(ctx context.Context, o *GenerateControllerOptions) error { gv, _ := schema.ParseGroupVersion(o.GenerateOptions.APIVersion) - gcpTokens := strings.Split(o.GenerateOptions.ServiceName, ".") + gcpTokens := strings.Split(o.ServiceName, ".") version := gcpTokens[len(gcpTokens)-1] if version[0] != 'v' { return fmt.Errorf("--service does not contain GCP version") diff --git a/dev/tools/controllerbuilder/pkg/commands/generatedirectreconciler/command.go b/dev/tools/controllerbuilder/pkg/commands/generatedirectreconciler/command.go index 08c231066d5..c77591345aa 100644 --- a/dev/tools/controllerbuilder/pkg/commands/generatedirectreconciler/command.go +++ b/dev/tools/controllerbuilder/pkg/commands/generatedirectreconciler/command.go @@ -31,12 +31,14 @@ type GenerateBasicReconcilerOptions struct { *options.GenerateOptions Resource options.Resource + ServiceName string APIGoPackagePath string APIDirectory string OutputMapperDirectory string } func (o *GenerateBasicReconcilerOptions) BindFlags(cmd *cobra.Command) { + cmd.Flags().StringVarP(&o.ServiceName, "service", "s", o.ServiceName, "the GCP service name") cmd.Flags().Var(&o.Resource, "resource", "the KRM Kind and the equivalent proto resource separated with a colon. e.g. for resource google.storage.v1.Bucket, the flag should be `StorageBucket:Bucket`") cmd.Flags().StringVar(&o.APIGoPackagePath, "api-go-package-path", o.APIGoPackagePath, "package path") cmd.Flags().StringVar(&o.APIDirectory, "api-dir", o.APIDirectory, "base directory for reading APIs") @@ -81,7 +83,7 @@ func BuildCommand(baseOptions *options.GenerateOptions) *cobra.Command { return fmt.Errorf("unable to parse --api-version: %w", err) } - if baseOptions.ServiceName == "" { + if opt.ServiceName == "" { return fmt.Errorf("--service is required") } return nil diff --git a/dev/tools/controllerbuilder/pkg/commands/generatemapper/generatemappercommand.go b/dev/tools/controllerbuilder/pkg/commands/generatemapper/generatemappercommand.go index d1806a362ef..159b57d249b 100644 --- a/dev/tools/controllerbuilder/pkg/commands/generatemapper/generatemappercommand.go +++ b/dev/tools/controllerbuilder/pkg/commands/generatemapper/generatemappercommand.go @@ -33,6 +33,8 @@ import ( type GenerateMapperOptions struct { *options.GenerateOptions + ServiceNames []string + APIGoPackagePath string APIDirectory string OutputMapperDirectory string @@ -52,6 +54,8 @@ func (o *GenerateMapperOptions) InitDefaults() error { } func (o *GenerateMapperOptions) BindFlags(cmd *cobra.Command) { + cmd.Flags().StringSliceVarP(&o.ServiceNames, "service", "s", o.ServiceNames, "the GCP service name(s); if multiple, must be comma-separated") + cmd.Flags().StringVar(&o.APIGoPackagePath, "api-go-package-path", o.APIGoPackagePath, "package path") cmd.Flags().StringVar(&o.APIDirectory, "api-dir", o.APIDirectory, "base directory for reading APIs") cmd.Flags().StringVar(&o.OutputMapperDirectory, "output-dir", o.OutputMapperDirectory, "base directory for writing mappers") @@ -118,7 +122,13 @@ func RunGenerateMapper(ctx context.Context, o *GenerateMapperOptions) error { if strings.HasSuffix(fullName, "Metadata") { return "", false } - if !strings.HasPrefix(fullName, o.ServiceName+".") { + matchedService := false + for _, serviceName := range o.ServiceNames { + if strings.HasPrefix(fullName, serviceName+".") { + matchedService = true + } + } + if !matchedService { return "", false } @@ -128,7 +138,7 @@ func RunGenerateMapper(ctx context.Context, o *GenerateMapperOptions) error { generatedFileAnnotation := &annotations.FileAnnotation{ Key: "+generated:mapper", Attributes: map[string][]string{ - "proto.service": {o.ServiceName}, + "proto.service": o.ServiceNames, "krm.group": {gv.Group}, "krm.version": {gv.Version}, }, @@ -136,6 +146,13 @@ func RunGenerateMapper(ctx context.Context, o *GenerateMapperOptions) error { mapperGenerator := codegen.NewMapperGenerator(pathForMessage, o.OutputMapperDirectory, generatedFileAnnotation, o.Multiversion) + // Ensure that our first proto package is always imported with the "pb" alias. + firstService, err := api.GetFileDescriptorByPackage(o.ServiceNames[0]) + if err != nil { + return err + } + mapperGenerator.AddGoImportAlias(codegen.GoPackageForProto(firstService[0]), "pb") + if err := mapperGenerator.VisitGoCode(o.APIGoPackagePath, o.APIDirectory); err != nil { return err } @@ -177,13 +194,13 @@ func (o *GenerateMapperOptions) loadAndApplyConfig() error { return fmt.Errorf("mapper generation is disabled for this service in config file %s", o.ConfigFilePath) } - o.ServiceName = config.Service + o.ServiceNames = []string{config.Service} o.APIVersion = config.APIVersion return nil } func (o *GenerateMapperOptions) validate() error { - if o.ServiceName == "" { + if len(o.ServiceNames) == 0 { return fmt.Errorf("ServiceName is required") } if o.GenerateOptions.ProtoSourcePath == "" { diff --git a/dev/tools/controllerbuilder/pkg/commands/generatetypes/generatetypescommand.go b/dev/tools/controllerbuilder/pkg/commands/generatetypes/generatetypescommand.go index 4518e8344d8..010374463e9 100644 --- a/dev/tools/controllerbuilder/pkg/commands/generatetypes/generatetypescommand.go +++ b/dev/tools/controllerbuilder/pkg/commands/generatetypes/generatetypescommand.go @@ -34,6 +34,8 @@ import ( type GenerateCRDOptions struct { *options.GenerateOptions + ServiceName string + OutputAPIDirectory string SkipScaffoldFiles bool @@ -50,6 +52,8 @@ func (o *GenerateCRDOptions) InitDefaults() error { } func (o *GenerateCRDOptions) BindFlags(cmd *cobra.Command) { + cmd.Flags().StringVarP(&o.ServiceName, "service", "s", o.ServiceName, "the GCP service name") + cmd.Flags().StringVar(&o.OutputAPIDirectory, "output-api", o.OutputAPIDirectory, "base directory for writing APIs") cmd.Flags().Var(&o.Resources, "resource", "the KRM Kind and the equivalent proto resource separated with a colon. e.g. for resource google.storage.v1.Bucket, the flag should be `StorageBucket:Bucket`. Can be specified multiple times.") cmd.Flags().BoolVar(&o.SkipScaffoldFiles, "skip-scaffold-files", false, "skip generating scaffold files (types, refs, and identity) for all resources generated by this command") diff --git a/dev/tools/controllerbuilder/pkg/commands/updatetypes/updatetypescommand.go b/dev/tools/controllerbuilder/pkg/commands/updatetypes/updatetypescommand.go index f396655b5f1..0d8c3b4813f 100644 --- a/dev/tools/controllerbuilder/pkg/commands/updatetypes/updatetypescommand.go +++ b/dev/tools/controllerbuilder/pkg/commands/updatetypes/updatetypescommand.go @@ -26,6 +26,8 @@ import ( type baseUpdateTypeOptions struct { *options.GenerateOptions + ServiceName string + ignoredFields string // TODO: could be part of GenerateOptions apiDirectory string apiGoPackagePath string @@ -46,6 +48,7 @@ func (o *baseUpdateTypeOptions) BindFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.ignoredFields, "ignored-fields", o.ignoredFields, "Comma-separated list of fields to ignore") cmd.Flags().StringVar(&o.apiDirectory, "api-dir", o.apiDirectory, "Base directory for APIs") cmd.Flags().StringVar(&o.apiGoPackagePath, "api-go-package-path", o.apiGoPackagePath, "API Go package path") + cmd.Flags().StringVarP(&o.ServiceName, "service", "s", o.ServiceName, "the GCP service name") } func BuildCommand(baseOptions *options.GenerateOptions) *cobra.Command { diff --git a/dev/tools/controllerbuilder/pkg/options/generateoptions.go b/dev/tools/controllerbuilder/pkg/options/generateoptions.go index 622c2769052..3b250173a7d 100644 --- a/dev/tools/controllerbuilder/pkg/options/generateoptions.go +++ b/dev/tools/controllerbuilder/pkg/options/generateoptions.go @@ -23,7 +23,6 @@ import ( type GenerateOptions struct { ProtoSourcePath string - ServiceName string APIVersion string ConfigFilePath string } @@ -40,7 +39,6 @@ func (o *GenerateOptions) InitDefaults() error { func (o *GenerateOptions) BindPersistentFlags(cmd *cobra.Command) { cmd.PersistentFlags().StringVar(&o.ProtoSourcePath, "proto-source-path", o.ProtoSourcePath, "path to (compiled) proto for APIs") cmd.PersistentFlags().StringVarP(&o.APIVersion, "api-version", "v", o.APIVersion, "the KRM API version. used to import the KRM API") - cmd.PersistentFlags().StringVarP(&o.ServiceName, "service", "s", o.ServiceName, "the GCP service name") cmd.PersistentFlags().StringVar(&o.ConfigFilePath, "config", "", "path to service config file, the config file will override other flags") } diff --git a/dev/tools/controllerbuilder/pkg/protoapi/loader.go b/dev/tools/controllerbuilder/pkg/protoapi/loader.go index 7ec8f14fa6e..b255bcef801 100644 --- a/dev/tools/controllerbuilder/pkg/protoapi/loader.go +++ b/dev/tools/controllerbuilder/pkg/protoapi/loader.go @@ -66,3 +66,16 @@ func (p *Proto) SortedFiles() []protoreflect.FileDescriptor { func (p *Proto) Files() *protoregistry.Files { return p.files } + +func (p *Proto) GetFileDescriptorByPackage(protoPackage string) ([]protoreflect.FileDescriptor, error) { + var files []protoreflect.FileDescriptor + for _, f := range p.SortedFiles() { + if string(f.Package()) == protoPackage { + files = append(files, f) + } + } + if len(files) == 0 { + return nil, fmt.Errorf("could not find FileDescriptor for package %q", protoPackage) + } + return files, nil +} diff --git a/docs/reports/crd_report.csv b/docs/reports/crd_report.csv index 9a688f5063b..50f9076759e 100644 --- a/docs/reports/crd_report.csv +++ b/docs/reports/crd_report.csv @@ -257,6 +257,7 @@ firebasehosting.cnrm.cloud.google.com,FirebaseHostingSite,True,False,False,Terra firebase.cnrm.cloud.google.com,FirebaseProject,True,False,False,Terraform firebasestorage.cnrm.cloud.google.com,FirebaseStorageBucket,True,False,False,Terraform firebase.cnrm.cloud.google.com,FirebaseWebApp,True,False,False,Terraform +firestore.cnrm.cloud.google.com,FirestoreDatabaseBackupSchedule,True,False,False,Direct firestore.cnrm.cloud.google.com,FirestoreDatabase,True,True,False,Direct firestore.cnrm.cloud.google.com,FirestoreField,True,False,False,Direct firestore.cnrm.cloud.google.com,FirestoreIndex,False,True,False,Terraform diff --git a/docs/reports/crd_report.md b/docs/reports/crd_report.md index 7f4fde30d93..bd817a87b2a 100644 --- a/docs/reports/crd_report.md +++ b/docs/reports/crd_report.md @@ -260,6 +260,7 @@ | firebase.cnrm.cloud.google.com | FirebaseProject | True | False | False | Terraform | | firebasestorage.cnrm.cloud.google.com | FirebaseStorageBucket | True | False | False | Terraform | | firebase.cnrm.cloud.google.com | FirebaseWebApp | True | False | False | Terraform | +| firestore.cnrm.cloud.google.com | FirestoreDatabaseBackupSchedule | True | False | False | Direct | | firestore.cnrm.cloud.google.com | FirestoreDatabase | True | True | False | Direct | | firestore.cnrm.cloud.google.com | FirestoreField | True | False | False | Direct | | firestore.cnrm.cloud.google.com | FirestoreIndex | False | True | False | Terraform | diff --git a/pkg/controller/direct/firestore/backupschedule_fuzzer.go b/pkg/controller/direct/firestore/backupschedule_fuzzer.go new file mode 100644 index 00000000000..54b47eab62f --- /dev/null +++ b/pkg/controller/direct/firestore/backupschedule_fuzzer.go @@ -0,0 +1,44 @@ +// Copyright 2025 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. + +// +tool:fuzz-gen +// proto.message: google.firestore.admin.v1.BackupSchedule +// api.group: firestore.cnrm.cloud.google.com +package firestore + +import ( + pb "cloud.google.com/go/firestore/apiv1/admin/adminpb" + "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/fuzztesting" +) + +func init() { + fuzztesting.RegisterKRMFuzzer(backupScheduleFuzzer()) +} + +func backupScheduleFuzzer() fuzztesting.KRMFuzzer { + f := fuzztesting.NewKRMTypedFuzzer(&pb.BackupSchedule{}, + FirestoreDatabaseBackupScheduleSpec_v1alpha1_FromProto, FirestoreDatabaseBackupScheduleSpec_v1alpha1_ToProto, + FirestoreDatabaseBackupScheduleObservedState_v1alpha1_FromProto, FirestoreDatabaseBackupScheduleObservedState_v1alpha1_ToProto, + ) + + f.SpecField(".retention") + f.SpecField(".daily_recurrence") + f.SpecField(".weekly_recurrence") + + f.StatusField(".name") // Server generated ID, so surface in observedState + f.StatusField(".create_time") + f.StatusField(".update_time") + + return f +} diff --git a/pkg/controller/direct/firestore/firestoredocument_fuzzer.go b/pkg/controller/direct/firestore/firestoredocument_fuzzer.go new file mode 100644 index 00000000000..22abd54c267 --- /dev/null +++ b/pkg/controller/direct/firestore/firestoredocument_fuzzer.go @@ -0,0 +1,54 @@ +// Copyright 2025 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. + +// +tool:fuzz-gen +// proto.message: google.firestore.v1.Document +// api.group: firestore.cnrm.cloud.google.com + +package firestore + +import ( + pb "cloud.google.com/go/firestore/apiv1/firestorepb" + "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/fuzztesting" + structpb "google.golang.org/protobuf/types/known/structpb" +) + +func init() { + fuzztesting.RegisterKRMFuzzer(firestoreDocumentFuzzer()) +} + +func firestoreDocumentFuzzer() fuzztesting.KRMFuzzer { + f := fuzztesting.NewKRMTypedFuzzer(&pb.Document{}, + FirestoreDocumentSpec_v1alpha1_FromProto, FirestoreDocumentSpec_v1alpha1_ToProto, + FirestoreDocumentObservedState_v1alpha1_FromProto, FirestoreDocumentObservedState_v1alpha1_ToProto, + ) + f.FilterSpec = func(in *pb.Document) { + for _, field := range in.GetFields() { + switch field.ValueType.(type) { + // These types do not easily round-trip to JSON, so we omit them for now. + case *pb.Value_BytesValue, *pb.Value_TimestampValue, *pb.Value_ReferenceValue, *pb.Value_GeoPointValue: + field.ValueType = &pb.Value_NullValue{NullValue: structpb.NullValue_NULL_VALUE} + } + } + } + + f.IdentityField(".name") + + f.SpecField(".fields") + + f.StatusField(".create_time") + f.StatusField(".update_time") + + return f +} diff --git a/pkg/controller/direct/firestore/firestoredocument_mappers.go b/pkg/controller/direct/firestore/firestoredocument_mappers.go new file mode 100644 index 00000000000..6ce70da2752 --- /dev/null +++ b/pkg/controller/direct/firestore/firestoredocument_mappers.go @@ -0,0 +1,138 @@ +package firestore + +import ( + "encoding/json" + + pb "cloud.google.com/go/firestore/apiv1/firestorepb" + krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/firestore/v1alpha1" + "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct" +) + +func FirestoreDocumentSpec_v1alpha1_FromProto(mapCtx *direct.MapContext, in *pb.Document) *krm.FirestoreDocumentSpec { + if in == nil { + return nil + } + out := &krm.FirestoreDocumentSpec{} + + if in.Fields != nil { + out.Fields = make(map[string]string, len(in.Fields)) + } + for k, v := range in.Fields { + outV := Field_FromProto(mapCtx, v) + b, err := json.MarshalIndent(outV, "", " ") + if err != nil { + return nil + } + out.Fields[k] = string(b) + } + + return out +} + +func Field_FromProto(mapCtx *direct.MapContext, in *pb.Value) any { + if in == nil { + return nil + } + switch v := in.ValueType.(type) { + case *pb.Value_NullValue: + return nil + case *pb.Value_BooleanValue: + return v.BooleanValue + case *pb.Value_IntegerValue: + return v.IntegerValue + case *pb.Value_DoubleValue: + return v.DoubleValue + case *pb.Value_StringValue: + return v.StringValue + + // These types do not easily round-trip to JSON, so we omit them for now. + // case *pb.Value_BytesValue: + // return v.BytesValue + // case *pb.Value_TimestampValue: + // return v.TimestampValue + // case *pb.Value_ReferenceValue: + // return v.ReferenceValue + // case *pb.Value_GeoPointValue: + // return v.GeoPointValue + + case *pb.Value_ArrayValue: + arr := make([]any, len(v.ArrayValue.Values)) + for i, elem := range v.ArrayValue.Values { + arr[i] = Field_FromProto(mapCtx, elem) + } + return arr + case *pb.Value_MapValue: + m := make(map[string]any) + for k, elem := range v.MapValue.Fields { + m[k] = Field_FromProto(mapCtx, elem) + } + return m + default: + // Unknown type + mapCtx.Errorf("unknown type in FirestoreDocument: %T", v) + return nil + } +} + +func FirestoreDocumentSpec_v1alpha1_ToProto(mapCtx *direct.MapContext, in *krm.FirestoreDocumentSpec) *pb.Document { + if in == nil { + return nil + } + out := &pb.Document{} + if in.Fields != nil { + out.Fields = make(map[string]*pb.Value, len(in.Fields)) + } + for k, v := range in.Fields { + outV := Field_ToProto(mapCtx, v) + out.Fields[k] = outV + } + + return out +} + +func Field_ToProto(mapCtx *direct.MapContext, in any) *pb.Value { + if in == nil { + return &pb.Value{ValueType: &pb.Value_NullValue{}} + } + switch in := in.(type) { + case bool: + return &pb.Value{ValueType: &pb.Value_BooleanValue{BooleanValue: in}} + case string: + return &pb.Value{ValueType: &pb.Value_StringValue{StringValue: in}} + case int64: + return &pb.Value{ValueType: &pb.Value_IntegerValue{IntegerValue: in}} + case float64: + return &pb.Value{ValueType: &pb.Value_DoubleValue{DoubleValue: in}} + case []any: + arr := make([]*pb.Value, len(in)) + for i, elem := range in { + arr[i] = Field_ToProto(mapCtx, elem) + } + return &pb.Value{ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{Values: arr}}} + case map[string]any: + m := make(map[string]*pb.Value, len(in)) + for k, elem := range in { + m[k] = Field_ToProto(mapCtx, elem) + } + return &pb.Value{ValueType: &pb.Value_MapValue{MapValue: &pb.MapValue{Fields: m}}} + default: + // Unknown type + mapCtx.Errorf("unknown type in FirestoreDocument: %T", in) + return nil + } +} + +func Value_v1alpha1_FromProto(mapCtx *direct.MapContext, in *pb.Value) *krm.Value { + mapCtx.NotImplemented() + return nil +} + +func Value_v1alpha1_ToProto(mapCtx *direct.MapContext, in *krm.Value) *pb.Value { + mapCtx.NotImplemented() + return nil +} + +func Value_BytesValue_ToProto(mapCtx *direct.MapContext, in []byte) *pb.Value_BytesValue { + mapCtx.NotImplemented() + return nil +} diff --git a/pkg/controller/direct/firestore/firestorefield_fuzzer.go b/pkg/controller/direct/firestore/firestorefield_fuzzer.go index 8b8906f906e..50b2ea10033 100644 --- a/pkg/controller/direct/firestore/firestorefield_fuzzer.go +++ b/pkg/controller/direct/firestore/firestorefield_fuzzer.go @@ -32,6 +32,17 @@ func firestoreFieldFuzzer() fuzztesting.KRMFuzzer { FirestoreFieldSpec_v1alpha1_FromProto, FirestoreFieldSpec_v1alpha1_ToProto, FirestoreFieldObservedState_v1alpha1_FromProto, FirestoreFieldObservedState_v1alpha1_ToProto, ) + f.FilterSpec = func(in *pb.Field) { + for _, index := range in.GetIndexConfig().GetIndexes() { + for _, field := range index.GetFields() { + if x, ok := field.GetValueMode().(*pb.Index_IndexField_ArrayConfig_); ok { + if x.ArrayConfig == pb.Index_IndexField_ARRAY_CONFIG_UNSPECIFIED { + x.ArrayConfig = pb.Index_IndexField_CONTAINS + } + } + } + } + } f.IdentityField(".name") diff --git a/pkg/controller/direct/firestore/mapper.generated.go b/pkg/controller/direct/firestore/mapper.generated.go index 3702f9d2d7f..dbabcb980de 100644 --- a/pkg/controller/direct/firestore/mapper.generated.go +++ b/pkg/controller/direct/firestore/mapper.generated.go @@ -16,16 +16,50 @@ // krm.group: firestore.cnrm.cloud.google.com // krm.version: v1beta1 // proto.service: google.firestore.admin.v1 +// proto.service: google.firestore.v1 package firestore import ( pb "cloud.google.com/go/firestore/apiv1/admin/adminpb" + firestorepb "cloud.google.com/go/firestore/apiv1/firestorepb" krmfirestorev1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/firestore/v1alpha1" krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/firestore/v1beta1" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct" + dayofweekpb "google.golang.org/genproto/googleapis/type/dayofweek" + structpb "google.golang.org/protobuf/types/known/structpb" ) +func ArrayValue_v1alpha1_FromProto(mapCtx *direct.MapContext, in *firestorepb.ArrayValue) *krmfirestorev1alpha1.ArrayValue { + if in == nil { + return nil + } + out := &krmfirestorev1alpha1.ArrayValue{} + out.Values = direct.Slice_FromProto(mapCtx, in.Values, Value_v1alpha1_FromProto) + return out +} +func ArrayValue_v1alpha1_ToProto(mapCtx *direct.MapContext, in *krmfirestorev1alpha1.ArrayValue) *firestorepb.ArrayValue { + if in == nil { + return nil + } + out := &firestorepb.ArrayValue{} + out.Values = direct.Slice_ToProto(mapCtx, in.Values, Value_v1alpha1_ToProto) + return out +} +func DailyRecurrence_v1alpha1_FromProto(mapCtx *direct.MapContext, in *pb.DailyRecurrence) *krmfirestorev1alpha1.DailyRecurrence { + if in == nil { + return nil + } + out := &krmfirestorev1alpha1.DailyRecurrence{} + return out +} +func DailyRecurrence_v1alpha1_ToProto(mapCtx *direct.MapContext, in *krmfirestorev1alpha1.DailyRecurrence) *pb.DailyRecurrence { + if in == nil { + return nil + } + out := &pb.DailyRecurrence{} + return out +} func Database_CmekConfig_v1beta1_FromProto(mapCtx *direct.MapContext, in *pb.Database_CmekConfig) *krm.Database_CmekConfig { if in == nil { return nil @@ -174,6 +208,50 @@ func Field_TTLConfigObservedState_v1alpha1_ToProto(mapCtx *direct.MapContext, in out.State = direct.Enum_ToProto[pb.Field_TtlConfig_State](mapCtx, in.State) return out } +func FirestoreDatabaseBackupScheduleObservedState_v1alpha1_FromProto(mapCtx *direct.MapContext, in *pb.BackupSchedule) *krmfirestorev1alpha1.FirestoreDatabaseBackupScheduleObservedState { + if in == nil { + return nil + } + out := &krmfirestorev1alpha1.FirestoreDatabaseBackupScheduleObservedState{} + out.Name = direct.LazyPtr(in.GetName()) + out.CreateTime = direct.StringTimestamp_FromProto(mapCtx, in.GetCreateTime()) + out.UpdateTime = direct.StringTimestamp_FromProto(mapCtx, in.GetUpdateTime()) + return out +} +func FirestoreDatabaseBackupScheduleObservedState_v1alpha1_ToProto(mapCtx *direct.MapContext, in *krmfirestorev1alpha1.FirestoreDatabaseBackupScheduleObservedState) *pb.BackupSchedule { + if in == nil { + return nil + } + out := &pb.BackupSchedule{} + out.Name = direct.ValueOf(in.Name) + out.CreateTime = direct.StringTimestamp_ToProto(mapCtx, in.CreateTime) + out.UpdateTime = direct.StringTimestamp_ToProto(mapCtx, in.UpdateTime) + return out +} +func FirestoreDatabaseBackupScheduleSpec_v1alpha1_FromProto(mapCtx *direct.MapContext, in *pb.BackupSchedule) *krmfirestorev1alpha1.FirestoreDatabaseBackupScheduleSpec { + if in == nil { + return nil + } + out := &krmfirestorev1alpha1.FirestoreDatabaseBackupScheduleSpec{} + out.Retention = direct.StringDuration_FromProto(mapCtx, in.GetRetention()) + out.DailyRecurrence = DailyRecurrence_v1alpha1_FromProto(mapCtx, in.GetDailyRecurrence()) + out.WeeklyRecurrence = WeeklyRecurrence_v1alpha1_FromProto(mapCtx, in.GetWeeklyRecurrence()) + return out +} +func FirestoreDatabaseBackupScheduleSpec_v1alpha1_ToProto(mapCtx *direct.MapContext, in *krmfirestorev1alpha1.FirestoreDatabaseBackupScheduleSpec) *pb.BackupSchedule { + if in == nil { + return nil + } + out := &pb.BackupSchedule{} + out.Retention = direct.StringDuration_ToProto(mapCtx, in.Retention) + if oneof := DailyRecurrence_v1alpha1_ToProto(mapCtx, in.DailyRecurrence); oneof != nil { + out.Recurrence = &pb.BackupSchedule_DailyRecurrence{DailyRecurrence: oneof} + } + if oneof := WeeklyRecurrence_v1alpha1_ToProto(mapCtx, in.WeeklyRecurrence); oneof != nil { + out.Recurrence = &pb.BackupSchedule_WeeklyRecurrence{WeeklyRecurrence: oneof} + } + return out +} func FirestoreDatabaseObservedState_v1beta1_FromProto(mapCtx *direct.MapContext, in *pb.Database) *krm.FirestoreDatabaseObservedState { if in == nil { return nil @@ -266,6 +344,26 @@ func FirestoreDatabaseSpec_v1beta1_ToProto(mapCtx *direct.MapContext, in *krm.Fi // MISSING: DatabaseEdition return out } +func FirestoreDocumentObservedState_v1alpha1_FromProto(mapCtx *direct.MapContext, in *firestorepb.Document) *krmfirestorev1alpha1.FirestoreDocumentObservedState { + if in == nil { + return nil + } + out := &krmfirestorev1alpha1.FirestoreDocumentObservedState{} + // MISSING: Name + out.CreateTime = direct.StringTimestamp_FromProto(mapCtx, in.GetCreateTime()) + out.UpdateTime = direct.StringTimestamp_FromProto(mapCtx, in.GetUpdateTime()) + return out +} +func FirestoreDocumentObservedState_v1alpha1_ToProto(mapCtx *direct.MapContext, in *krmfirestorev1alpha1.FirestoreDocumentObservedState) *firestorepb.Document { + if in == nil { + return nil + } + out := &firestorepb.Document{} + // MISSING: Name + out.CreateTime = direct.StringTimestamp_ToProto(mapCtx, in.CreateTime) + out.UpdateTime = direct.StringTimestamp_ToProto(mapCtx, in.UpdateTime) + return out +} func FirestoreFieldObservedState_v1alpha1_FromProto(mapCtx *direct.MapContext, in *pb.Field) *krmfirestorev1alpha1.FirestoreFieldObservedState { if in == nil { return nil @@ -558,3 +656,74 @@ func Index_ObservedState_v1alpha1_ToProto(mapCtx *direct.MapContext, in *krmfire // MISSING: ShardCount return out } +func MapValue_v1alpha1_FromProto(mapCtx *direct.MapContext, in *firestorepb.MapValue) *krmfirestorev1alpha1.MapValue { + if in == nil { + return nil + } + out := &krmfirestorev1alpha1.MapValue{} + // MISSING: Fields + return out +} +func MapValue_v1alpha1_ToProto(mapCtx *direct.MapContext, in *krmfirestorev1alpha1.MapValue) *firestorepb.MapValue { + if in == nil { + return nil + } + out := &firestorepb.MapValue{} + // MISSING: Fields + return out +} +func Value_NullValue_ToProto(mapCtx *direct.MapContext, in *string) *firestorepb.Value_NullValue { + if in == nil { + return nil + } + return &firestorepb.Value_NullValue{NullValue: direct.Enum_ToProto[structpb.NullValue](mapCtx, in)} +} +func Value_BooleanValue_ToProto(mapCtx *direct.MapContext, in *bool) *firestorepb.Value_BooleanValue { + if in == nil { + return nil + } + if !*in { + return nil + } + return &firestorepb.Value_BooleanValue{BooleanValue: *in} +} +func Value_IntegerValue_ToProto(mapCtx *direct.MapContext, in *int64) *firestorepb.Value_IntegerValue { + if in == nil { + return nil + } + return &firestorepb.Value_IntegerValue{IntegerValue: *in} +} +func Value_DoubleValue_ToProto(mapCtx *direct.MapContext, in *float64) *firestorepb.Value_DoubleValue { + if in == nil { + return nil + } + return &firestorepb.Value_DoubleValue{DoubleValue: *in} +} +func Value_StringValue_ToProto(mapCtx *direct.MapContext, in *string) *firestorepb.Value_StringValue { + if in == nil { + return nil + } + return &firestorepb.Value_StringValue{StringValue: *in} +} +func Value_ReferenceValue_ToProto(mapCtx *direct.MapContext, in *string) *firestorepb.Value_ReferenceValue { + if in == nil { + return nil + } + return &firestorepb.Value_ReferenceValue{ReferenceValue: *in} +} +func WeeklyRecurrence_v1alpha1_FromProto(mapCtx *direct.MapContext, in *pb.WeeklyRecurrence) *krmfirestorev1alpha1.WeeklyRecurrence { + if in == nil { + return nil + } + out := &krmfirestorev1alpha1.WeeklyRecurrence{} + out.Day = direct.Enum_FromProto(mapCtx, in.GetDay()) + return out +} +func WeeklyRecurrence_v1alpha1_ToProto(mapCtx *direct.MapContext, in *krmfirestorev1alpha1.WeeklyRecurrence) *pb.WeeklyRecurrence { + if in == nil { + return nil + } + out := &pb.WeeklyRecurrence{} + out.Day = direct.Enum_ToProto[dayofweekpb.DayOfWeek](mapCtx, in.Day) + return out +} diff --git a/pkg/fuzztesting/fuzzkrm.go b/pkg/fuzztesting/fuzzkrm.go index e18a688871e..291e7f65dee 100644 --- a/pkg/fuzztesting/fuzzkrm.go +++ b/pkg/fuzztesting/fuzzkrm.go @@ -60,6 +60,9 @@ type KRMTypedFuzzer[ProtoT proto.Message, SpecType any, StatusType any] struct { UnimplementedFields sets.Set[string] SpecFields sets.Set[string] StatusFields sets.Set[string] + + FilterSpec func(in ProtoT) + FilterStatus func(in ProtoT) } // SpecField marks the specified fieldPath as round-tripping to/from the Spec @@ -133,6 +136,7 @@ func (f *KRMTypedFuzzer[ProtoT, SpecType, StatusType]) FuzzSpec(t *testing.T, se fuzzer := NewFuzzTest(f.ProtoType, f.SpecFromProto, f.SpecToProto) fuzzer.IgnoreFields = f.StatusFields fuzzer.UnimplementedFields = f.UnimplementedFields + fuzzer.Filter = f.FilterSpec fuzzer.Fuzz(t, seed) } @@ -140,6 +144,7 @@ func (f *KRMTypedFuzzer[ProtoT, SpecType, StatusType]) FuzzStatus(t *testing.T, fuzzer := NewFuzzTest(f.ProtoType, f.StatusFromProto, f.StatusToProto) fuzzer.IgnoreFields = f.SpecFields fuzzer.UnimplementedFields = f.UnimplementedFields + fuzzer.Filter = f.FilterStatus fuzzer.Fuzz(t, seed) } @@ -172,6 +177,8 @@ type FuzzTest[ProtoT proto.Message, KRMType any] struct { UnimplementedFields sets.Set[string] IgnoreFields sets.Set[string] + + Filter func(in ProtoT) } func NewFuzzTest[ProtoT proto.Message, KRMType any](protoType ProtoT, fromProto func(ctx *direct.MapContext, in ProtoT) *KRMType, toProto func(ctx *direct.MapContext, in *KRMType) ProtoT) *FuzzTest[ProtoT, KRMType] { @@ -200,6 +207,10 @@ func (f *FuzzTest[ProtoT, KRMType]) Fuzz(t *testing.T, seed int64) { } fuzz.Visit("", p1.ProtoReflect(), nil, clearFields) + if f.Filter != nil { + f.Filter(p1) + } + ctx := &direct.MapContext{} krm := f.FromProto(ctx, p1) if ctx.Err() != nil { diff --git a/tests/apichecks/testdata/exceptions/alpha-missingfields.txt b/tests/apichecks/testdata/exceptions/alpha-missingfields.txt index c037ada1d63..7fe49a31f3a 100644 --- a/tests/apichecks/testdata/exceptions/alpha-missingfields.txt +++ b/tests/apichecks/testdata/exceptions/alpha-missingfields.txt @@ -1561,6 +1561,10 @@ [missing_field] crd=firebasewebapps.firebase.cnrm.cloud.google.com version=v1alpha1: field ".spec.deletionPolicy" is not set in unstructured objects [missing_field] crd=firebasewebapps.firebase.cnrm.cloud.google.com version=v1alpha1: field ".spec.displayName" is not set in unstructured objects [missing_field] crd=firebasewebapps.firebase.cnrm.cloud.google.com version=v1alpha1: field ".spec.project" is not set in unstructured objects +[missing_field] crd=firestoredatabasebackupschedules.firestore.cnrm.cloud.google.com version=v1alpha1: field ".spec.dailyRecurrence" is not set in unstructured objects +[missing_field] crd=firestoredatabasebackupschedules.firestore.cnrm.cloud.google.com version=v1alpha1: field ".spec.databaseRef" is not set; neither 'external' nor 'name' are set +[missing_field] crd=firestoredatabasebackupschedules.firestore.cnrm.cloud.google.com version=v1alpha1: field ".spec.retention" is not set in unstructured objects +[missing_field] crd=firestoredatabasebackupschedules.firestore.cnrm.cloud.google.com version=v1alpha1: field ".spec.weeklyRecurrence.day" is not set in unstructured objects [missing_field] crd=firestorefields.firestore.cnrm.cloud.google.com version=v1alpha1: field ".spec.collectionGroup" is not set in unstructured objects [missing_field] crd=firestorefields.firestore.cnrm.cloud.google.com version=v1alpha1: field ".spec.indexConfig.indexes[].apiScope" is not set in unstructured objects [missing_field] crd=firestorefields.firestore.cnrm.cloud.google.com version=v1alpha1: field ".spec.indexConfig.indexes[].density" is not set in unstructured objects