diff --git a/.env b/.env index 0c659fb7c..c9f5fa23d 100644 --- a/.env +++ b/.env @@ -8,4 +8,12 @@ EKSCTL_VERSION=v0.191.0 EKS_CLUSTER_K8_VERSION=1.31 EKS_INSTANCE_TYPE=m5.2xlarge EKS_INSTANCE_TYPE_ARM64=c6g.4xlarge -SPLUNK_ENTERPRISE_RELEASE_IMAGE=splunk/splunk:9.4.1 \ No newline at end of file +SPLUNK_ENTERPRISE_RELEASE_IMAGE=splunk/splunk:9.4.1 +RELATED_IMAGE_SPLUNK_ENTERPRISE=splunk/splunk:9.2.3 +RELATED_IMAGE_RAY_HEAD=667741767953.dkr.ecr.us-west-2.amazonaws.com/ml-platform/ray/ray-head:build-5 +RELATED_IMAGE_RAY_WORKER=667741767953.dkr.ecr.us-west-2.amazonaws.com/ml-platform/ray/ray-worker-gpu:build-6 +RELATED_IMAGE_WEAVIATE=semitechnologies/weaviate:stable-v1.28-007846a +RELATED_IMAGE_POST_INSTALL_HOOK=667741767953.dkr.ecr.us-west-2.amazonaws.com/vivek/ml-platform/saia/ai-helm-post-hook:0.0.5 +CLUSTER_NAME=sok-ml-platform +MODEL_VERSION=v0.3.14-36-g1549f5a +RAY_VERSION=2.44.0 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 9dd010cf5..beef60df6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ ARG BASE_IMAGE=registry.access.redhat.com/ubi8/ubi-minimal@sha256 ARG BASE_IMAGE_VERSION=b2a1bec3dfbc7a14a1d84d98934dfe8fdde6eb822a211286601cf109cbccb075 # Build the manager binary -FROM golang:1.23.0 AS builder +FROM golang:1.24 AS builder WORKDIR /workspace @@ -90,6 +90,7 @@ COPY LICENSE /licenses/LICENSE-2.0.txt COPY tools/k8_probes/livenessProbe.sh /tools/k8_probes/ COPY tools/k8_probes/readinessProbe.sh /tools/k8_probes/ COPY tools/k8_probes/startupProbe.sh /tools/k8_probes/ +COPY applications.yaml /home/nonroot/applications.yaml # Set the user USER 1001 diff --git a/PROJECT b/PROJECT index 9e6e1cff9..cc06f4c9e 100644 --- a/PROJECT +++ b/PROJECT @@ -1,3 +1,7 @@ +# Code generated by tool. DO NOT EDIT. +# This file is used to track the info used to scaffold your project +# and allow the plugins properly work. +# More info: https://book.kubebuilder.io/reference/project-config.html domain: splunk.com layout: - go.kubebuilder.io/v3 @@ -109,4 +113,22 @@ resources: kind: LicenseManager path: github.com/splunk/splunk-operator/api/v4 version: v4 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: splunk.com + group: enterprise + kind: AIPlatform + path: github.com/splunk/splunk-operator/api/v4 + version: v4 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: splunk.com + group: enterprise + kind: AIAssistant + path: github.com/splunk/splunk-operator/api/v4 + version: v4 version: "3" diff --git a/api/v4/aiplatform_types.go b/api/v4/aiplatform_types.go new file mode 100644 index 000000000..2c86af33d --- /dev/null +++ b/api/v4/aiplatform_types.go @@ -0,0 +1,191 @@ +/* +Copyright 2024. + +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 v4 + +import ( + rayv1 "github.com/ray-project/kuberay/ray-operator/apis/ray/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// AIPlatform is the Schema for the AIPlatform API +// +k8s:openapi-gen=true +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:path=aiplatforms,scope=Namespaced,shortName=spai +// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +type AIPlatform struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec AIPlatformSpec `json:"spec,omitempty"` + Status AIPlatformStatus `json:"status,omitempty"` +} + +// AIPlatformSpec defines the desired state +type AIPlatformSpec struct { + + Volume AiVolumeSpec `json:"volume,omitempty"` + // s3://bucket/artifacts + // s3://bucket/tasks - get rid of this + + AppsVolume AiVolumeSpec `json:"appsVolume,omitempty"` + ArtifactsVolume AiVolumeSpec `json:"artifactsVolume,omitempty"` + + Features: + // saia-spl + serviceAccountName string `json:"serviceAccountName,omitempty"` + // saia-sec + serviceAccountName string `json:"serviceAccountName,omitempty"` + + HeadGroupSpec HeadGroupSpec `json:"headGroupSpec,omitempty"` + WorkerGroupSpec WorkerGroupSpec `json:"workerGroupSpec,omitempty"` + DefaultAcceleratorType string `json:"defaultAcceleratorType"` + // Which sidecars to inject + Sidecars SidecarConfig `json:"sidecars,omitempty"` + + // cert-manager Certificate for mTLS + CertificateRef string `json:"certificateRef,omitempty"` + + // Cluster domain (default: cluster.local) + // +kubebuilder:default=cluster.local + ClusterDomain string `json:"clusterDomain,omitempty"` + + // SplunkConfiguration instance reference + SplunkConfiguration SplunkConfiguration `json:"splunkConfiguration,omitempty"` + + Weaviate WeaviateSpec `json:"weaviate,omitempty"` + weaviateStorage PersistentVolumeClaim `json:"storage,omitempty"` + SchedulingSpec `json:",inline"` // inlines NodeSelector, Tolerations, Affinity + Ingress `json:",inline"` +} + +type WeaviateSpec struct { + // +kubebuilder:validation:Minimum=1 + Replicas *int32 `json:"replicas"` + //Image string `json:"image"` + Resources corev1.ResourceRequirements `json:"resources,omitempty"` + ServiceAccountName string `json:"serviceAccountName,omitempty"` + SchedulingSpec `json:",inline"` // inlines NodeSelector, Tolerations, Affinity +} + +type HeadGroupSpec struct { + ServiceAccountName string `json:"serviceAccountName,omitempty"` + SchedulingSpec `json:",inline"` // inlines NodeSelector, Tolerations, Affinity + // image registries for Ray + ImageRegistry string `json:"imageRegistry,omitempty"` +} + +type WorkerGroupSpec struct { + ServiceAccountName string `json:"serviceAccountName,omitempty"` + ImageRegistry string `json:"imageRegistry,omitempty"` + GPUConfigs []GPUConfig `json:"gpuConfigs,omitempty"` + SchedulingSpec `json:",inline"` // inlines NodeSelector, Tolerations, Affinity +} + +// GPUConfig defines one worker-tier with scheduling and accelerator settings. +type GPUConfig struct { + Tier string `json:"tier"` + MinReplicas int32 `json:"minReplicas"` + MaxReplicas int32 `json:"maxReplicas"` + GPUsPerPod int32 `json:"gpusPerPod"` + Resources corev1.ResourceRequirements `json:"resources,omitempty"` +} + +// SchedulingSpec exposes common pod-scheduling knobs. +type SchedulingSpec struct { + NodeSelector map[string]string `json:"nodeSelector,omitempty"` + Tolerations []corev1.Toleration `json:"tolerations,omitempty"` + Affinity *corev1.Affinity `json:"affinity,omitempty"` +} + +type SplunkConfiguration struct { + // Name of the SplunkConfiguration instance + // +kubebuilder:validation:Pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + // +kubebuilder:validation:MaxLength=63 + // +kubebuilder:validation:MinLength=1 + CRName string `json:"crName,omitempty"` + // Namespace of the SplunkConfiguration instance + // +kubebuilder:validation:Pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + // +kubebuilder:validation:MaxLength=63 + // +kubebuilder:validation:MinLength=1 + CRNamespace string `json:"crNamespace,omitempty"` + // Splunk secret reference + SecretRef corev1.SecretReference `json:"secretRef,omitempty"` + Endpoint string `json:"endpoint,omitempty"` + Token string `json:"token,omitempty"` +} + +// ReplicasSpec sets min/max worker replicas +type ReplicasSpec struct { + Min int32 `json:"min,omitempty"` + Max int32 `json:"max,omitempty"` +} + +// MachineClass configures CPU, memory, GPU per-worker +type MachineClass struct { + ResourceRequirements corev1.ResourceRequirements `json:"resourceRequirements,omitempty"` + GPU int32 `json:"gpu,omitempty"` + EphimeralStorage string `json:"ephemeral-storage,omitempty"` // e.g. "100Gi" +} + +// SidecarConfig toggles injection of sidecars +type SidecarConfig struct { + // +kubebuilder:default=true + Envoy bool `json:"envoy,omitempty"` + // +kubebuilder:default=true + FluentBit bool `json:"fluentBit,omitempty"` + // +kubebuilder:default=true + Otel bool `json:"otel,omitempty"` + // +kubebuilder:default=true + PrometheusOperator bool `json:"prometheusOperator,omitempty"` +} + +type AiVolumeSpec struct { + // Remote volume URI in the format s3://bucketname/ + Path string `json:"path"` // s3://bucketname/ or gs://bucketname/ or azure://containername/ + + // optional override endpoint (only really needed for S3-compatible like MinIO) + Endpoint string `json:"endpoint,omitempty"` + + // Region of the remote storage volume where apps reside. Used for aws, if provided. Not used for minio and azure. + Region string `json:"region"` + + // Secret object name + SecretRef string `json:"secretRef"` +} + +// AIPlatformStatus defines observed state +type AIPlatformStatus struct { + RayServiceName string `json:"rayServiceName,omitempty"` + VectorDbServiceName string `json:"vectorDbServiceName,omitempty"` + RayServiceStatus rayv1.ServiceStatus `json:"rayServiceStatus,omitempty"` + Conditions []metav1.Condition `json:"conditions,omitempty"` + ObservedGeneration int64 `json:"observedGeneration,omitempty"` +} + +// +kubebuilder:object:root=true +type AIPlatformList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []AIPlatform `json:"items"` +} + +func init() { + SchemeBuilder.Register(&AIPlatform{}, &AIPlatformList{}) +} diff --git a/api/v4/aiservice_types.go b/api/v4/aiservice_types.go new file mode 100644 index 000000000..88f04bd2f --- /dev/null +++ b/api/v4/aiservice_types.go @@ -0,0 +1,111 @@ +/* +Copyright 2024. + +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 v4 + +import ( + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// AIServiceSpec defines the desired state of AIService +type AIServiceSpec struct { + // SplunkConfiguration instance reference + Version string `json:"version,omitempty"` + TaskVolume AiVolumeSpec `json:"taskVolume,omitempty"` + SplunkConfiguration SplunkConfiguration `json:"splunkConfiguration,omitempty"` + VectorDbUrl string `json:"vectorDbUrl"` + AIPlatformUrl string `json:"aiPlatformUrl,omitempty"` + AIPlatformRef corev1.ObjectReference `json:"aiPlatformRef,omitempty"` + Replicas int32 `json:"replicas,omitempty"` + ServiceAccountName string `json:"serviceAccountName,omitempty"` + //Port specifies the default port for the service + Port int32 `json:"port,omitempty" default:"80"` + Env map[string]string `json:"env,omitempty"` + Tolerations []corev1.Toleration `json:"tolerations,omitempty"` + // node affinity configuraiton + Affinity corev1.Affinity `json:"affinity,omitempty"` + // resources k8s resources cpu, memory + Resources corev1.ResourceRequirements `json:"resources,omitempty"` + // metrics configuration + Metrics MetricsConfig `json:"metrics,omitempty"` + // mtls configuration + MTLS MTLSConfig `json:"mtls,omitempty"` + // ServiceTemplate is a template used to create Kubernetes services + ServiceTemplate corev1.Service `json:"serviceTemplate"` +} + +type MetricsConfig struct { + // Enable scraping of SAIA metrics + Enabled bool `json:"enabled,omitempty"` + // Path under /metrics, default "/metrics" + Path string `json:"path,omitempty"` + // Port name or number, default "metrics" + Port int32 `json:"port,omitempty"` +} + +type MTLSConfig struct { + // Enable or disable mTLS on the SAIA service + Enabled bool `json:"enabled"` + // If Enabled, how to request the cert + IssuerRef cmmeta.ObjectReference `json:"issuerRef,omitempty"` + SecretName string `json:"secretName,omitempty"` + DNSNames []string `json:"dnsNames,omitempty"` + // Let users declare “I don’t want operator-managed TLS” even if Enabled=true, + // e.g. they’re on Istio and will terminate externally. + Termination string `json:"termination,omitempty"` // "operator" or "mesh" +} + +// AIServiceStatus defines the observed state of AIService +type AIServiceStatus struct { + SchemaJobId string `json:"schemaJobId,omitempty"` + VectorDbStatus string `json:"vectorDbStatus,omitempty"` + PlatformStatus string `json:"platformStatus,omitempty"` + Conditions []metav1.Condition `json:"conditions,omitempty"` + ObservedGeneration int64 `json:"observedGeneration,omitempty"` +} + +// AIService is the Schema for the aiservices API +// +k8s:openapi-gen=true +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:path=aiservices,scope=Namespaced,shortName=saia +// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +type AIService struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec AIServiceSpec `json:"spec,omitempty"` + Status AIServiceStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// AIServiceList contains a list of AIService +type AIServiceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []AIService `json:"items"` +} + +func init() { + SchemeBuilder.Register(&AIService{}, &AIServiceList{}) +} diff --git a/api/v4/zz_generated.deepcopy.go b/api/v4/zz_generated.deepcopy.go index 93e988463..d76ef900f 100644 --- a/api/v4/zz_generated.deepcopy.go +++ b/api/v4/zz_generated.deepcopy.go @@ -22,9 +22,249 @@ package v4 import ( "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 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 *AIPlatform) DeepCopyInto(out *AIPlatform) { + *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 AIPlatform. +func (in *AIPlatform) DeepCopy() *AIPlatform { + if in == nil { + return nil + } + out := new(AIPlatform) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AIPlatform) 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 *AIPlatformList) DeepCopyInto(out *AIPlatformList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]AIPlatform, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AIPlatformList. +func (in *AIPlatformList) DeepCopy() *AIPlatformList { + if in == nil { + return nil + } + out := new(AIPlatformList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AIPlatformList) 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 *AIPlatformSpec) DeepCopyInto(out *AIPlatformSpec) { + *out = *in + out.Storage = in.Storage + if in.Features != nil { + in, out := &in.Features, &out.Features + *out = make([]string, len(*in)) + copy(*out, *in) + } + out.Sidecars = in.Sidecars + out.SplunkConfiguration = in.SplunkConfiguration + in.Weaviate.DeepCopyInto(&out.Weaviate) + in.SchedulingSpec.DeepCopyInto(&out.SchedulingSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AIPlatformSpec. +func (in *AIPlatformSpec) DeepCopy() *AIPlatformSpec { + if in == nil { + return nil + } + out := new(AIPlatformSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AIPlatformStatus) DeepCopyInto(out *AIPlatformStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AIPlatformStatus. +func (in *AIPlatformStatus) DeepCopy() *AIPlatformStatus { + if in == nil { + return nil + } + out := new(AIPlatformStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AIService) DeepCopyInto(out *AIService) { + *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 AIService. +func (in *AIService) DeepCopy() *AIService { + if in == nil { + return nil + } + out := new(AIService) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AIService) 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 *AIServiceList) DeepCopyInto(out *AIServiceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]AIService, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AIServiceList. +func (in *AIServiceList) DeepCopy() *AIServiceList { + if in == nil { + return nil + } + out := new(AIServiceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AIServiceList) 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 *AIServiceSpec) DeepCopyInto(out *AIServiceSpec) { + *out = *in + out.TaskVolume = in.TaskVolume + out.SplunkConfiguration = in.SplunkConfiguration + out.AIPlatformRef = in.AIPlatformRef + if in.Env != nil { + in, out := &in.Env, &out.Env + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = make([]v1.Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + in.Affinity.DeepCopyInto(&out.Affinity) + in.Resources.DeepCopyInto(&out.Resources) + out.Metrics = in.Metrics + in.MTLS.DeepCopyInto(&out.MTLS) + in.ServiceTemplate.DeepCopyInto(&out.ServiceTemplate) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AIServiceSpec. +func (in *AIServiceSpec) DeepCopy() *AIServiceSpec { + if in == nil { + return nil + } + out := new(AIServiceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AIServiceStatus) DeepCopyInto(out *AIServiceStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AIServiceStatus. +func (in *AIServiceStatus) DeepCopy() *AIServiceStatus { + if in == nil { + return nil + } + out := new(AIServiceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AiVolumeSpec) DeepCopyInto(out *AiVolumeSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AiVolumeSpec. +func (in *AiVolumeSpec) DeepCopy() *AiVolumeSpec { + if in == nil { + return nil + } + out := new(AiVolumeSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AppDeploymentContext) DeepCopyInto(out *AppDeploymentContext) { *out = *in @@ -370,6 +610,38 @@ func (in *EsDefaults) DeepCopy() *EsDefaults { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GPUConfig) DeepCopyInto(out *GPUConfig) { + *out = *in + in.Resources.DeepCopyInto(&out.Resources) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GPUConfig. +func (in *GPUConfig) DeepCopy() *GPUConfig { + if in == nil { + return nil + } + out := new(GPUConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HeadGroupSpec) DeepCopyInto(out *HeadGroupSpec) { + *out = *in + in.SchedulingSpec.DeepCopyInto(&out.SchedulingSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HeadGroupSpec. +func (in *HeadGroupSpec) DeepCopy() *HeadGroupSpec { + if in == nil { + return nil + } + out := new(HeadGroupSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IndexAndCacheManagerCommonSpec) DeepCopyInto(out *IndexAndCacheManagerCommonSpec) { *out = *in @@ -647,6 +919,58 @@ func (in *LicenseManagerStatus) DeepCopy() *LicenseManagerStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MTLSConfig) DeepCopyInto(out *MTLSConfig) { + *out = *in + out.IssuerRef = in.IssuerRef + if in.DNSNames != nil { + in, out := &in.DNSNames, &out.DNSNames + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MTLSConfig. +func (in *MTLSConfig) DeepCopy() *MTLSConfig { + if in == nil { + return nil + } + out := new(MTLSConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MachineClass) DeepCopyInto(out *MachineClass) { + *out = *in + in.ResourceRequirements.DeepCopyInto(&out.ResourceRequirements) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineClass. +func (in *MachineClass) DeepCopy() *MachineClass { + if in == nil { + return nil + } + out := new(MachineClass) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MetricsConfig) DeepCopyInto(out *MetricsConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricsConfig. +func (in *MetricsConfig) DeepCopy() *MetricsConfig { + if in == nil { + return nil + } + out := new(MetricsConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MonitoringConsole) DeepCopyInto(out *MonitoringConsole) { *out = *in @@ -793,6 +1117,55 @@ func (in *Probe) DeepCopy() *Probe { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplicasSpec) DeepCopyInto(out *ReplicasSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicasSpec. +func (in *ReplicasSpec) DeepCopy() *ReplicasSpec { + if in == nil { + return nil + } + out := new(ReplicasSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SchedulingSpec) DeepCopyInto(out *SchedulingSpec) { + *out = *in + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = make([]v1.Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Affinity != nil { + in, out := &in.Affinity, &out.Affinity + *out = new(v1.Affinity) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SchedulingSpec. +func (in *SchedulingSpec) DeepCopy() *SchedulingSpec { + if in == nil { + return nil + } + out := new(SchedulingSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SearchHeadCluster) DeepCopyInto(out *SearchHeadCluster) { *out = *in @@ -928,6 +1301,21 @@ func (in *SearchHeadClusterStatus) DeepCopy() *SearchHeadClusterStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SidecarConfig) DeepCopyInto(out *SidecarConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SidecarConfig. +func (in *SidecarConfig) DeepCopy() *SidecarConfig { + if in == nil { + return nil + } + out := new(SidecarConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SmartStoreSpec) DeepCopyInto(out *SmartStoreSpec) { *out = *in @@ -987,6 +1375,22 @@ func (in *Spec) DeepCopy() *Spec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SplunkConfiguration) DeepCopyInto(out *SplunkConfiguration) { + *out = *in + out.SecretRef = in.SecretRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SplunkConfiguration. +func (in *SplunkConfiguration) DeepCopy() *SplunkConfiguration { + if in == nil { + return nil + } + out := new(SplunkConfiguration) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Standalone) DeepCopyInto(out *Standalone) { *out = *in @@ -1133,3 +1537,48 @@ func (in *VolumeSpec) DeepCopy() *VolumeSpec { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WeaviateSpec) DeepCopyInto(out *WeaviateSpec) { + *out = *in + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + *out = new(int32) + **out = **in + } + in.Resources.DeepCopyInto(&out.Resources) + in.SchedulingSpec.DeepCopyInto(&out.SchedulingSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WeaviateSpec. +func (in *WeaviateSpec) DeepCopy() *WeaviateSpec { + if in == nil { + return nil + } + out := new(WeaviateSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkerGroupSpec) DeepCopyInto(out *WorkerGroupSpec) { + *out = *in + if in.GPUConfigs != nil { + in, out := &in.GPUConfigs, &out.GPUConfigs + *out = make([]GPUConfig, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + in.SchedulingSpec.DeepCopyInto(&out.SchedulingSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkerGroupSpec. +func (in *WorkerGroupSpec) DeepCopy() *WorkerGroupSpec { + if in == nil { + return nil + } + out := new(WorkerGroupSpec) + in.DeepCopyInto(out) + return out +} diff --git a/config/crd/bases/enterprise.splunk.com_aiplatforms.yaml b/config/crd/bases/enterprise.splunk.com_aiplatforms.yaml new file mode 100644 index 000000000..fc1d3626c --- /dev/null +++ b/config/crd/bases/enterprise.splunk.com_aiplatforms.yaml @@ -0,0 +1,1885 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.1 + name: aiplatforms.enterprise.splunk.com +spec: + group: enterprise.splunk.com + names: + kind: AIPlatform + listKind: AIPlatformList + plural: aiplatforms + shortNames: + - spai + singular: aiplatform + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v4 + schema: + openAPIV3Schema: + description: AIPlatform is the Schema for the AIPlatform 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: AIPlatformSpec defines the desired state + properties: + affinity: + description: Affinity is a group of affinity scheduling rules. + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the + pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with the + corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the corresponding + nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + type: array + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate + this pod in the same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. + avoid putting this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + certificateRef: + description: cert-manager Certificate for mTLS + type: string + clusterDomain: + default: cluster.local + description: 'Cluster domain (default: cluster.local)' + type: string + defaultAcceleratorType: + description: |- + HeadGroupSpec HeadGroupSpec `json:"headGroupSpec,omitempty"` + WorkerGroupSpec WorkerGroupSpec `json:"workerGroupSpec,omitempty"` + type: string + features: + items: + type: string + type: array + nodeSelector: + additionalProperties: + type: string + type: object + sidecars: + description: Which sidecars to inject + properties: + envoy: + default: true + type: boolean + fluentBit: + default: true + type: boolean + otel: + default: true + type: boolean + prometheusOperator: + default: true + type: boolean + type: object + splunkConfiguration: + description: SplunkConfiguration instance reference + properties: + crName: + description: Name of the SplunkConfiguration instance + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + crNamespace: + description: Namespace of the SplunkConfiguration instance + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + endpoint: + type: string + secretRef: + description: Splunk secret reference + properties: + name: + description: name is unique within a namespace to reference + a secret resource. + type: string + namespace: + description: namespace defines the space within which the + secret name must be unique. + type: string + type: object + x-kubernetes-map-type: atomic + token: + type: string + type: object + storage: + properties: + endpoint: + description: optional override endpoint (only really needed for + S3-compatible like MinIO) + type: string + path: + description: Remote volume URI in the format s3://bucketname/ + type: string + region: + description: Region of the remote storage volume where apps reside. + Used for aws, if provided. Not used for minio and azure. + type: string + secretRef: + description: Secret object name + type: string + type: object + tolerations: + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + weaviate: + properties: + affinity: + description: Affinity is a group of affinity scheduling rules. + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + type: array + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + nodeSelector: + additionalProperties: + type: string + type: object + replicas: + format: int32 + minimum: 1 + type: integer + resources: + description: Image string `json:"image"` + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + serviceAccountName: + type: string + tolerations: + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + status: + description: AIPlatformStatus defines observed state + properties: + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + observedGeneration: + format: int64 + type: integer + rayServiceName: + type: string + rayServiceStatus: + type: string + vectorDbServiceName: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/enterprise.splunk.com_aiservices.yaml b/config/crd/bases/enterprise.splunk.com_aiservices.yaml new file mode 100644 index 000000000..b10632400 --- /dev/null +++ b/config/crd/bases/enterprise.splunk.com_aiservices.yaml @@ -0,0 +1,1618 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.1 + name: aiservices.enterprise.splunk.com +spec: + group: enterprise.splunk.com + names: + kind: AIService + listKind: AIServiceList + plural: aiservices + shortNames: + - saia + singular: aiservice + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v4 + schema: + openAPIV3Schema: + description: AIService is the Schema for the aiservices 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: AIServiceSpec defines the desired state of AIService + properties: + affinity: + description: node affinity configuraiton + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the + pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with the + corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the corresponding + nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + type: array + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate + this pod in the same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. + avoid putting this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + aiPlatformRef: + description: ObjectReference contains enough information to let you + inspect or modify the referred object. + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic + aiPlatformUrl: + type: string + env: + additionalProperties: + type: string + type: object + metrics: + description: metrics configuration + properties: + enabled: + description: Enable scraping of SAIA metrics + type: boolean + path: + description: Path under /metrics, default "/metrics" + type: string + port: + description: Port name or number, default "metrics" + format: int32 + type: integer + type: object + mtls: + description: mtls configuration + properties: + dnsNames: + items: + type: string + type: array + enabled: + description: Enable or disable mTLS on the SAIA service + type: boolean + issuerRef: + description: If Enabled, how to request the cert + properties: + group: + description: Group of the resource being referred to. + type: string + kind: + description: Kind of the resource being referred to. + type: string + name: + description: Name of the resource being referred to. + type: string + required: + - name + type: object + secretName: + type: string + termination: + description: |- + Let users declare “I don’t want operator-managed TLS” even if Enabled=true, + e.g. they’re on Istio and will terminate externally. + type: string + type: object + port: + description: Port specifies the default port for the service + format: int32 + type: integer + replicas: + format: int32 + type: integer + resources: + description: resources k8s resources cpu, memory + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + serviceAccountName: + type: string + serviceTemplate: + description: ServiceTemplate is a template used to create Kubernetes + services + 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: + description: |- + Standard object's metadata. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + type: object + spec: + description: |- + Spec defines the behavior of a service. + https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + properties: + allocateLoadBalancerNodePorts: + description: |- + allocateLoadBalancerNodePorts defines if NodePorts will be automatically + allocated for services with type LoadBalancer. Default is "true". It + may be set to "false" if the cluster load-balancer does not rely on + NodePorts. If the caller requests specific NodePorts (by specifying a + value), those requests will be respected, regardless of this field. + This field may only be set for services with type LoadBalancer and will + be cleared if the type is changed to any other type. + type: boolean + clusterIP: + description: |- + clusterIP is the IP address of the service and is usually assigned + randomly. If an address is specified manually, is in-range (as per + system configuration), and is not in use, it will be allocated to the + service; otherwise creation of the service will fail. This field may not + be changed through updates unless the type field is also being changed + to ExternalName (which requires this field to be blank) or the type + field is being changed from ExternalName (in which case this field may + optionally be specified, as describe above). Valid values are "None", + empty string (""), or a valid IP address. Setting this to "None" makes a + "headless service" (no virtual IP), which is useful when direct endpoint + connections are preferred and proxying is not required. Only applies to + types ClusterIP, NodePort, and LoadBalancer. If this field is specified + when creating a Service of type ExternalName, creation will fail. This + field will be wiped when updating a Service to type ExternalName. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies + type: string + clusterIPs: + description: |- + ClusterIPs is a list of IP addresses assigned to this service, and are + usually assigned randomly. If an address is specified manually, is + in-range (as per system configuration), and is not in use, it will be + allocated to the service; otherwise creation of the service will fail. + This field may not be changed through updates unless the type field is + also being changed to ExternalName (which requires this field to be + empty) or the type field is being changed from ExternalName (in which + case this field may optionally be specified, as describe above). Valid + values are "None", empty string (""), or a valid IP address. Setting + this to "None" makes a "headless service" (no virtual IP), which is + useful when direct endpoint connections are preferred and proxying is + not required. Only applies to types ClusterIP, NodePort, and + LoadBalancer. If this field is specified when creating a Service of type + ExternalName, creation will fail. This field will be wiped when updating + a Service to type ExternalName. If this field is not specified, it will + be initialized from the clusterIP field. If this field is specified, + clients must ensure that clusterIPs[0] and clusterIP have the same + value. + + This field may hold a maximum of two entries (dual-stack IPs, in either order). + These IPs must correspond to the values of the ipFamilies field. Both + clusterIPs and ipFamilies are governed by the ipFamilyPolicy field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies + items: + type: string + type: array + x-kubernetes-list-type: atomic + externalIPs: + description: |- + externalIPs is a list of IP addresses for which nodes in the cluster + will also accept traffic for this service. These IPs are not managed by + Kubernetes. The user is responsible for ensuring that traffic arrives + at a node with this IP. A common example is external load-balancers + that are not part of the Kubernetes system. + items: + type: string + type: array + externalName: + description: |- + externalName is the external reference that discovery mechanisms will + return as an alias for this service (e.g. a DNS CNAME record). No + proxying will be involved. Must be a lowercase RFC-1123 hostname + (https://tools.ietf.org/html/rfc1123) and requires `type` to be "ExternalName". + type: string + externalTrafficPolicy: + description: |- + externalTrafficPolicy describes how nodes distribute service traffic they + receive on one of the Service's "externally-facing" addresses (NodePorts, + ExternalIPs, and LoadBalancer IPs). If set to "Local", the proxy will configure + the service in a way that assumes that external load balancers will take care + of balancing the service traffic between nodes, and so each node will deliver + traffic only to the node-local endpoints of the service, without masquerading + the client source IP. (Traffic mistakenly sent to a node with no endpoints will + be dropped.) The default value, "Cluster", uses the standard behavior of + routing to all endpoints evenly (possibly modified by topology and other + features). Note that traffic sent to an External IP or LoadBalancer IP from + within the cluster will always get "Cluster" semantics, but clients sending to + a NodePort from within the cluster may need to take traffic policy into account + when picking a node. + type: string + healthCheckNodePort: + description: |- + healthCheckNodePort specifies the healthcheck nodePort for the service. + This only applies when type is set to LoadBalancer and + externalTrafficPolicy is set to Local. If a value is specified, is + in-range, and is not in use, it will be used. If not specified, a value + will be automatically allocated. External systems (e.g. load-balancers) + can use this port to determine if a given node holds endpoints for this + service or not. If this field is specified when creating a Service + which does not need it, creation will fail. This field will be wiped + when updating a Service to no longer need it (e.g. changing type). + This field cannot be updated once set. + format: int32 + type: integer + internalTrafficPolicy: + description: |- + InternalTrafficPolicy describes how nodes distribute service traffic they + receive on the ClusterIP. If set to "Local", the proxy will assume that pods + only want to talk to endpoints of the service on the same node as the pod, + dropping the traffic if there are no local endpoints. The default value, + "Cluster", uses the standard behavior of routing to all endpoints evenly + (possibly modified by topology and other features). + type: string + ipFamilies: + description: |- + IPFamilies is a list of IP families (e.g. IPv4, IPv6) assigned to this + service. This field is usually assigned automatically based on cluster + configuration and the ipFamilyPolicy field. If this field is specified + manually, the requested family is available in the cluster, + and ipFamilyPolicy allows it, it will be used; otherwise creation of + the service will fail. This field is conditionally mutable: it allows + for adding or removing a secondary IP family, but it does not allow + changing the primary IP family of the Service. Valid values are "IPv4" + and "IPv6". This field only applies to Services of types ClusterIP, + NodePort, and LoadBalancer, and does apply to "headless" services. + This field will be wiped when updating a Service to type ExternalName. + + This field may hold a maximum of two entries (dual-stack families, in + either order). These families must correspond to the values of the + clusterIPs field, if specified. Both clusterIPs and ipFamilies are + governed by the ipFamilyPolicy field. + items: + description: |- + IPFamily represents the IP Family (IPv4 or IPv6). This type is used + to express the family of an IP expressed by a type (e.g. service.spec.ipFamilies). + type: string + type: array + x-kubernetes-list-type: atomic + ipFamilyPolicy: + description: |- + IPFamilyPolicy represents the dual-stack-ness requested or required by + this Service. If there is no value provided, then this field will be set + to SingleStack. Services can be "SingleStack" (a single IP family), + "PreferDualStack" (two IP families on dual-stack configured clusters or + a single IP family on single-stack clusters), or "RequireDualStack" + (two IP families on dual-stack configured clusters, otherwise fail). The + ipFamilies and clusterIPs fields depend on the value of this field. This + field will be wiped when updating a service to type ExternalName. + type: string + loadBalancerClass: + description: |- + loadBalancerClass is the class of the load balancer implementation this Service belongs to. + If specified, the value of this field must be a label-style identifier, with an optional prefix, + e.g. "internal-vip" or "example.com/internal-vip". Unprefixed names are reserved for end-users. + This field can only be set when the Service type is 'LoadBalancer'. If not set, the default load + balancer implementation is used, today this is typically done through the cloud provider integration, + but should apply for any default implementation. If set, it is assumed that a load balancer + implementation is watching for Services with a matching class. Any default load balancer + implementation (e.g. cloud providers) should ignore Services that set this field. + This field can only be set when creating or updating a Service to type 'LoadBalancer'. + Once set, it can not be changed. This field will be wiped when a service is updated to a non 'LoadBalancer' type. + type: string + loadBalancerIP: + description: |- + Only applies to Service Type: LoadBalancer. + This feature depends on whether the underlying cloud-provider supports specifying + the loadBalancerIP when a load balancer is created. + This field will be ignored if the cloud-provider does not support the feature. + Deprecated: This field was under-specified and its meaning varies across implementations, + and it cannot support dual-stack. + As of Kubernetes v1.24, users are encouraged to use implementation-specific annotations when available. + This field may be removed in a future API version. + type: string + loadBalancerSourceRanges: + description: |- + If specified and supported by the platform, this will restrict traffic through the cloud-provider + load-balancer will be restricted to the specified client IPs. This field will be ignored if the + cloud-provider does not support the feature." + More info: https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/ + items: + type: string + type: array + ports: + description: |- + The list of ports that are exposed by this service. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies + items: + description: ServicePort contains information on service's + port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This field follows standard Kubernetes label syntax. + Un-prefixed names are reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + Non-standard protocols should use prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + x-kubernetes-list-map-keys: + - port + - protocol + x-kubernetes-list-type: map + publishNotReadyAddresses: + description: |- + publishNotReadyAddresses indicates that any agent which deals with endpoints for this + Service should disregard any indications of ready/not-ready. + The primary use case for setting this field is for a StatefulSet's Headless Service to + propagate SRV DNS records for its Pods for the purpose of peer discovery. + The Kubernetes controllers that generate Endpoints and EndpointSlice resources for + Services interpret this to mean that all endpoints are considered "ready" even if the + Pods themselves are not. Agents which consume only Kubernetes generated endpoints + through the Endpoints or EndpointSlice resources can safely assume this behavior. + type: boolean + selector: + additionalProperties: + type: string + description: |- + Route service traffic to pods with label keys and values matching this + selector. If empty or not present, the service is assumed to have an + external process managing its endpoints, which Kubernetes will not + modify. Only applies to types ClusterIP, NodePort, and LoadBalancer. + Ignored if type is ExternalName. + More info: https://kubernetes.io/docs/concepts/services-networking/service/ + type: object + x-kubernetes-map-type: atomic + sessionAffinity: + description: |- + Supports "ClientIP" and "None". Used to maintain session affinity. + Enable client IP based session affinity. + Must be ClientIP or None. + Defaults to None. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies + type: string + sessionAffinityConfig: + description: sessionAffinityConfig contains the configurations + of session affinity. + properties: + clientIP: + description: clientIP contains the configurations of Client + IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + type: + description: |- + type determines how the Service is exposed. Defaults to ClusterIP. Valid + options are ExternalName, ClusterIP, NodePort, and LoadBalancer. + "ClusterIP" allocates a cluster-internal IP address for load-balancing + to endpoints. Endpoints are determined by the selector or if that is not + specified, by manual construction of an Endpoints object or + EndpointSlice objects. If clusterIP is "None", no virtual IP is + allocated and the endpoints are published as a set of endpoints rather + than a virtual IP. + "NodePort" builds on ClusterIP and allocates a port on every node which + routes to the same endpoints as the clusterIP. + "LoadBalancer" builds on NodePort and creates an external load-balancer + (if supported in the current cloud) which routes to the same endpoints + as the clusterIP. + "ExternalName" aliases this service to the specified externalName. + Several other fields do not apply to ExternalName services. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types + type: string + type: object + status: + description: |- + Most recently observed status of the service. + Populated by the system. + Read-only. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + properties: + conditions: + description: Current service state + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + loadBalancer: + description: |- + LoadBalancer contains the current status of the load-balancer, + if one is present. + properties: + ingress: + description: |- + Ingress is a list containing ingress points for the load-balancer. + Traffic intended for the service should be sent to these ingress points. + items: + description: |- + LoadBalancerIngress represents the status of a load-balancer ingress point: + traffic intended for the service should be sent to an ingress point. + properties: + hostname: + description: |- + Hostname is set for load-balancer ingress points that are DNS based + (typically AWS load-balancers) + type: string + ip: + description: |- + IP is set for load-balancer ingress points that are IP based + (typically GCE or OpenStack load-balancers) + type: string + ports: + description: |- + Ports is a list of records of service ports + If used, every port defined in the service should have an entry in it + items: + properties: + error: + description: |- + Error is to record the problem with the service port + The format of the error shall comply with the following rules: + - built-in error values shall be specified in this file and those shall use + CamelCase names + - cloud provider specific error values must have names that comply with the + format foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + port: + description: Port is the port number of the + service port of which status is recorded + here + format: int32 + type: integer + protocol: + description: |- + Protocol is the protocol of the service port of which status is recorded here + The supported values are: "TCP", "UDP", "SCTP" + type: string + required: + - error + - port + - protocol + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: array + type: object + type: object + type: object + splunkConfiguration: + properties: + crName: + description: Name of the SplunkConfiguration instance + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + crNamespace: + description: Namespace of the SplunkConfiguration instance + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + endpoint: + type: string + secretRef: + description: Splunk secret reference + properties: + name: + description: name is unique within a namespace to reference + a secret resource. + type: string + namespace: + description: namespace defines the space within which the + secret name must be unique. + type: string + type: object + x-kubernetes-map-type: atomic + token: + type: string + type: object + taskVolume: + description: SplunkConfiguration instance reference + properties: + endpoint: + description: optional override endpoint (only really needed for + S3-compatible like MinIO) + type: string + path: + description: Remote volume URI in the format s3://bucketname/ + type: string + region: + description: Region of the remote storage volume where apps reside. + Used for aws, if provided. Not used for minio and azure. + type: string + secretRef: + description: Secret object name + type: string + type: object + tolerations: + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + vectorDbUrl: + type: string + type: object + status: + description: AIServiceStatus defines the observed state of AIService + properties: + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + observedGeneration: + format: int64 + type: integer + platformStatus: + type: string + schemaJobId: + type: string + vectorDbStatus: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index dd0d870ec..0308c76b3 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -10,6 +10,8 @@ resources: - bases/enterprise.splunk.com_monitoringconsoles.yaml - bases/enterprise.splunk.com_searchheadclusters.yaml - bases/enterprise.splunk.com_standalones.yaml +- bases/enterprise.splunk.com_aiplatforms.yaml +- bases/enterprise.splunk.com_aiservices.yaml #+kubebuilder:scaffold:crdkustomizeresource @@ -25,6 +27,8 @@ patchesStrategicMerge: #- patches/webhook_in_monitoringconsoles.yaml #- patches/webhook_in_searchheadclusters.yaml #- patches/webhook_in_standalones.yaml +#- patches/webhook_in_aiplatforms.yaml +#- patches/webhook_in_aiservices.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. @@ -37,6 +41,8 @@ patchesStrategicMerge: #- patches/cainjection_in_monitoringconsoles.yaml #- patches/cainjection_in_searchheadclusters.yaml #- patches/cainjection_in_standalones.yaml +#- patches/cainjection_in_aiplatforms.yaml +#- patches/cainjection_in_aiservices.yaml #+kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/config/crd/patches/cainjection_in_aiplatforms.yaml b/config/crd/patches/cainjection_in_aiplatforms.yaml new file mode 100644 index 000000000..5f17b8c17 --- /dev/null +++ b/config/crd/patches/cainjection_in_aiplatforms.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: aiplatforms.enterprise.splunk.com diff --git a/config/crd/patches/cainjection_in_aiservices.yaml b/config/crd/patches/cainjection_in_aiservices.yaml new file mode 100644 index 000000000..033cb20bc --- /dev/null +++ b/config/crd/patches/cainjection_in_aiservices.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: aiservices.enterprise.splunk.com diff --git a/config/crd/patches/webhook_in_aiplatforms.yaml b/config/crd/patches/webhook_in_aiplatforms.yaml new file mode 100644 index 000000000..ee5c98a23 --- /dev/null +++ b/config/crd/patches/webhook_in_aiplatforms.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: aiplatforms.enterprise.splunk.com +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_aiservices.yaml b/config/crd/patches/webhook_in_aiservices.yaml new file mode 100644 index 000000000..1e4d657c4 --- /dev/null +++ b/config/crd/patches/webhook_in_aiservices.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: aiservices.enterprise.splunk.com +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/default/kustomization-cluster.yaml b/config/default/kustomization-cluster.yaml index fa36d8a21..ed80e1cff 100644 --- a/config/default/kustomization-cluster.yaml +++ b/config/default/kustomization-cluster.yaml @@ -130,4 +130,18 @@ patches: - name: POD_NAME valueFrom: fieldRef: - fieldPath: metadata.name \ No newline at end of file + fieldPath: metadata.name + - name: RELATED_IMAGE_RAY_HEAD + value: "667741767953.dkr.ecr.us-west-2.amazonaws.com/ml-platform/ray/ray-head:build-5" + - name: RELATED_IMAGE_RAY_WORKER + value: "667741767953.dkr.ecr.us-west-2.amazonaws.com/ml-platform/ray/ray-worker-gpu:build-6" + - name: RELATED_IMAGE_WEAVIATE + value: "semitechnologies/weaviate:stable-v1.28-007846a" + - name: RELATED_IMAGE_SAIA_API + value: "667741767953.dkr.ecr.us-west-2.amazonaws.com/vivek/ml-platform/saia/saia-api:build-6" + - name: RELATED_IMAGE_POST_INSTALL_HOOK + value: "667741767953.dkr.ecr.us-west-2.amazonaws.com/vivek/ml-platform/saia/ai-helm-post-hook:0.0.5" + - name: MODEL_VERSION + value: "v0.3.14-36-g1549f5a" + - name: RAY_VERSION + value: "2.44.0" \ No newline at end of file diff --git a/config/default/kustomization-namespace.yaml b/config/default/kustomization-namespace.yaml index c8d7cf58d..4d438e55d 100644 --- a/config/default/kustomization-namespace.yaml +++ b/config/default/kustomization-namespace.yaml @@ -132,4 +132,18 @@ patches: - name: POD_NAME valueFrom: fieldRef: - fieldPath: metadata.name \ No newline at end of file + fieldPath: metadata.name + - name: RELATED_IMAGE_RAY_HEAD + value: "667741767953.dkr.ecr.us-west-2.amazonaws.com/ml-platform/ray/ray-head:build-5" + - name: RELATED_IMAGE_RAY_WORKER + value: "667741767953.dkr.ecr.us-west-2.amazonaws.com/ml-platform/ray/ray-worker-gpu:build-6" + - name: RELATED_IMAGE_WEAVIATE + value: "semitechnologies/weaviate:stable-v1.28-007846a" + - name: RELATED_IMAGE_SAIA_API + value: "667741767953.dkr.ecr.us-west-2.amazonaws.com/vivek/ml-platform/saia/saia-api:build-6" + - name: RELATED_IMAGE_POST_INSTALL_HOOK + value: "667741767953.dkr.ecr.us-west-2.amazonaws.com/vivek/ml-platform/saia/ai-helm-post-hook:0.0.5" + - name: MODEL_VERSION + value: "v0.3.14-36-g1549f5a" + - name: RAY_VERSION + value: "2.44.0" \ No newline at end of file diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index fa36d8a21..ed80e1cff 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -130,4 +130,18 @@ patches: - name: POD_NAME valueFrom: fieldRef: - fieldPath: metadata.name \ No newline at end of file + fieldPath: metadata.name + - name: RELATED_IMAGE_RAY_HEAD + value: "667741767953.dkr.ecr.us-west-2.amazonaws.com/ml-platform/ray/ray-head:build-5" + - name: RELATED_IMAGE_RAY_WORKER + value: "667741767953.dkr.ecr.us-west-2.amazonaws.com/ml-platform/ray/ray-worker-gpu:build-6" + - name: RELATED_IMAGE_WEAVIATE + value: "semitechnologies/weaviate:stable-v1.28-007846a" + - name: RELATED_IMAGE_SAIA_API + value: "667741767953.dkr.ecr.us-west-2.amazonaws.com/vivek/ml-platform/saia/saia-api:build-6" + - name: RELATED_IMAGE_POST_INSTALL_HOOK + value: "667741767953.dkr.ecr.us-west-2.amazonaws.com/vivek/ml-platform/saia/ai-helm-post-hook:0.0.5" + - name: MODEL_VERSION + value: "v0.3.14-36-g1549f5a" + - name: RAY_VERSION + value: "2.44.0" \ No newline at end of file diff --git a/config/rbac/aiassistant_editor_role.yaml b/config/rbac/aiassistant_editor_role.yaml new file mode 100644 index 000000000..ed9dbe06a --- /dev/null +++ b/config/rbac/aiassistant_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit aiservices. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: aiservice-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: splunk-operator + app.kubernetes.io/part-of: splunk-operator + app.kubernetes.io/managed-by: kustomize + name: aiservice-editor-role +rules: +- apiGroups: + - enterprise.splunk.com + resources: + - aiservices + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - enterprise.splunk.com + resources: + - aiservices/status + verbs: + - get diff --git a/config/rbac/aiassistant_viewer_role.yaml b/config/rbac/aiassistant_viewer_role.yaml new file mode 100644 index 000000000..2fa2e0a22 --- /dev/null +++ b/config/rbac/aiassistant_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view aiservices. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: aiservice-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: splunk-operator + app.kubernetes.io/part-of: splunk-operator + app.kubernetes.io/managed-by: kustomize + name: aiservice-viewer-role +rules: +- apiGroups: + - enterprise.splunk.com + resources: + - aiservices + verbs: + - get + - list + - watch +- apiGroups: + - enterprise.splunk.com + resources: + - aiservices/status + verbs: + - get diff --git a/config/rbac/aiplatform_editor_role.yaml b/config/rbac/aiplatform_editor_role.yaml new file mode 100644 index 000000000..a700a3851 --- /dev/null +++ b/config/rbac/aiplatform_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit aiplatforms. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: aiplatform-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: splunk-operator + app.kubernetes.io/part-of: splunk-operator + app.kubernetes.io/managed-by: kustomize + name: aiplatform-editor-role +rules: +- apiGroups: + - enterprise.splunk.com + resources: + - aiplatforms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - enterprise.splunk.com + resources: + - aiplatforms/status + verbs: + - get diff --git a/config/rbac/aiplatform_viewer_role.yaml b/config/rbac/aiplatform_viewer_role.yaml new file mode 100644 index 000000000..65b25e3e8 --- /dev/null +++ b/config/rbac/aiplatform_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view aiplatforms. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: aiplatform-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: splunk-operator + app.kubernetes.io/part-of: splunk-operator + app.kubernetes.io/managed-by: kustomize + name: aiplatform-viewer-role +rules: +- apiGroups: + - enterprise.splunk.com + resources: + - aiplatforms + verbs: + - get + - list + - watch +- apiGroups: + - enterprise.splunk.com + resources: + - aiplatforms/status + verbs: + - get diff --git a/config/rbac/kustomization-cluster.yaml b/config/rbac/kustomization-cluster.yaml index 731832a6a..9a76690b6 100644 --- a/config/rbac/kustomization-cluster.yaml +++ b/config/rbac/kustomization-cluster.yaml @@ -16,3 +16,7 @@ resources: - auth_proxy_role.yaml - auth_proxy_role_binding.yaml - auth_proxy_client_clusterrole.yaml +#- aiplatform_editor_role.yaml +#- aiplatform_viewer_role.yaml +#- aiservice_editor_role.yaml +#- aiservice_viewer_role.yaml diff --git a/config/rbac/kustomization-namespace.yaml b/config/rbac/kustomization-namespace.yaml index 40bc8c2a1..8fa863a1b 100644 --- a/config/rbac/kustomization-namespace.yaml +++ b/config/rbac/kustomization-namespace.yaml @@ -16,3 +16,7 @@ resources: #- auth_proxy_role.yaml #- auth_proxy_role_binding.yaml #- auth_proxy_client_clusterrole.yaml +#- aiplatform_editor_role.yaml +#- aiplatform_viewer_role.yaml +#- aiservice_editor_role.yaml +#- aiservice_viewer_role.yaml diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml index 731832a6a..a611fb9bc 100644 --- a/config/rbac/kustomization.yaml +++ b/config/rbac/kustomization.yaml @@ -16,3 +16,7 @@ resources: - auth_proxy_role.yaml - auth_proxy_role_binding.yaml - auth_proxy_client_clusterrole.yaml +#- aiplatform_editor_role.yaml +#- aiplatform_viewer_role.yaml +#- aiservice_editor_role.yaml +#- aiservice_viewer_role.yaml \ No newline at end of file diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 2f9c5122c..a179b50c9 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -14,6 +14,7 @@ rules: - apiGroups: - apps resources: + - deployments - statefulsets verbs: - create @@ -23,6 +24,30 @@ rules: - patch - update - watch +- apiGroups: + - batch + resources: + - jobs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - cert-manager.io + resources: + - certificates + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - "" resources: @@ -34,7 +59,6 @@ rules: - pods/exec - secrets - serviceaccounts - - services - services/finalizers verbs: - create @@ -44,9 +68,24 @@ rules: - patch - update - watch +- apiGroups: + - "" + resources: + - services + verbs: + - create + - delete + - deleteR + - get + - list + - patch + - update + - watch - apiGroups: - enterprise.splunk.com resources: + - aiplatforms + - aiservices - clustermanagers - clustermasters - indexerclusters @@ -66,6 +105,8 @@ rules: - apiGroups: - enterprise.splunk.com resources: + - aiplatforms/finalizers + - aiservices/finalizers - clustermanagers/finalizers - clustermasters/finalizers - indexerclusters/finalizers @@ -79,6 +120,8 @@ rules: - apiGroups: - enterprise.splunk.com resources: + - aiplatforms/status + - aiservices/status - clustermanagers/status - clustermasters/status - indexerclusters/status @@ -91,3 +134,69 @@ rules: - get - patch - update +- apiGroups: + - jobs + resources: + - batch + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - monitoring.coreos.com + resources: + - podmonitors + - prometheusrules + - servicemonitors + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - opentelemetry.io + resources: + - opentelemetrycollectors + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - ray.io + resources: + - jobs + - rayclusters + - rayjobs + - rayservices + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - rbac.authorization.k8s.io + resources: + - rolebindings + - roles + verbs: + - create + - delete + - get + - list + - patch + - update + - watch diff --git a/config/samples/enterprise_v4_aiassistant.yaml b/config/samples/enterprise_v4_aiassistant.yaml new file mode 100644 index 000000000..14da77e5a --- /dev/null +++ b/config/samples/enterprise_v4_aiassistant.yaml @@ -0,0 +1,12 @@ +apiVersion: enterprise.splunk.com/v4 +kind: AIService +metadata: + labels: + app.kubernetes.io/name: aiservice + app.kubernetes.io/instance: aiservice-sample + app.kubernetes.io/part-of: splunk-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: splunk-operator + name: aiservice-sample +spec: + # TODO(user): Add fields here diff --git a/config/samples/enterprise_v4_aiplatform.yaml b/config/samples/enterprise_v4_aiplatform.yaml new file mode 100644 index 000000000..9437ecd38 --- /dev/null +++ b/config/samples/enterprise_v4_aiplatform.yaml @@ -0,0 +1,12 @@ +apiVersion: enterprise.splunk.com/v4 +kind: AIPlatform +metadata: + labels: + app.kubernetes.io/name: aiplatform + app.kubernetes.io/instance: aiplatform-sample + app.kubernetes.io/part-of: splunk-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: splunk-operator + name: aiplatform-sample +spec: + # TODO(user): Add fields here diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 73c6d3649..daeac210f 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -13,4 +13,7 @@ resources: - enterprise_v4_searchheadcluster.yaml - enterprise_v4_clustermanager.yaml - enterprise_v4_licensemanager.yaml +- enterprise_v4_ingestioncluster.yaml +- enterprise_v4_aiplatform.yaml +- enterprise_v4_aiservice.yaml #+kubebuilder:scaffold:manifestskustomizesamples diff --git a/controllers/ai_platform/certificate.go b/controllers/ai_platform/certificate.go new file mode 100644 index 000000000..ca8a7ad64 --- /dev/null +++ b/controllers/ai_platform/certificate.go @@ -0,0 +1,37 @@ +package ai_platform + +import ( + "context" + "fmt" + //appsv1 "k8s.io/api/apps/v1" + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + certmanagermeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + enterpriseApi "github.com/splunk/splunk-operator/api/v4" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +// --- 2️⃣ reconcileCertificate: issue a cert-manager Certificate for mTLS --- +func (r *AIPlatformReconciler) reconcileCertificate(ctx context.Context, p *enterpriseApi.AIPlatform) error { + certName := p.Name + "-tls" + cert := &certmanagerv1.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: certName, + Namespace: p.Namespace, + }, + } + _, err := controllerutil.CreateOrUpdate(ctx, r.Client, cert, func() error { + cert.Spec = certmanagerv1.CertificateSpec{ + SecretName: certName + "-secret", + IssuerRef: certmanagermeta.ObjectReference{ + Name: p.Spec.CertificateRef, + Kind: "ClusterIssuer", + }, + DNSNames: []string{ + fmt.Sprintf("%s.%s.svc.%s", p.Name, p.Namespace, p.Spec.ClusterDomain), + }, + } + return controllerutil.SetOwnerReference(p, cert, r.Scheme) + }) + return err +} diff --git a/controllers/ai_platform/helpers.go b/controllers/ai_platform/helpers.go new file mode 100644 index 000000000..1d19909ad --- /dev/null +++ b/controllers/ai_platform/helpers.go @@ -0,0 +1 @@ +package ai_platform diff --git a/controllers/ai_platform/raybuilder/builder.go b/controllers/ai_platform/raybuilder/builder.go new file mode 100644 index 000000000..3018693a3 --- /dev/null +++ b/controllers/ai_platform/raybuilder/builder.go @@ -0,0 +1,518 @@ +/* +File: controllers/raybuilder/builder.go +*/ +package raybuilder + +import ( + "context" + "fmt" + "os" + "strings" + + rayv1 "github.com/ray-project/kuberay/ray-operator/apis/ray/v1" + enterpriseApi "github.com/splunk/splunk-operator/api/v4" + "github.com/splunk/splunk-operator/controllers/ai_platform/sidecars" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + "k8s.io/client-go/util/retry" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + //"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + rbacv1 "k8s.io/api/rbac/v1" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +// Builder encapsulates RayService generation logic. +type Builder struct { + ai *enterpriseApi.AIPlatform + client.Client + Scheme *runtime.Scheme + Recorder record.EventRecorder +} + +// New returns a new Builder for the given AIPlatform instance. +func New(ai *enterpriseApi.AIPlatform, client client.Client, scheme *runtime.Scheme, recorder record.EventRecorder) *Builder { + return &Builder{ + ai: ai, + Client: client, + Scheme: scheme, + Recorder: recorder, + } +} + +// --- 7️⃣ ReconcileRayService: build & create/update the RayService CR --- +func (b *Builder) ReconcileRayService(ctx context.Context, p *enterpriseApi.AIPlatform) error { + logger := log.FromContext(ctx) // Define logger + rs := b.Build() + + // Fetch the ServeConfigMap + serveConfigMap := &corev1.ConfigMap{} + serveConfigMapKey := types.NamespacedName{Namespace: p.Namespace, Name: p.Name + "-serveconfig"} + if err := b.Client.Get(ctx, serveConfigMapKey, serveConfigMap); err != nil { + return err + } + + annotations, labels := buildHeadAnnotationsAndLabels(p) + rayService := &rayv1.RayService{ + ObjectMeta: metav1.ObjectMeta{ + Name: p.Name, + Namespace: p.Namespace, + }, + } + err := b.Client.Get(ctx, types.NamespacedName{Namespace: p.Namespace, Name: p.Name}, rayService) + if errors.IsNotFound(err) { + rayService = &rayv1.RayService{ + ObjectMeta: metav1.ObjectMeta{ + Name: p.Name, + Namespace: p.Namespace, + Annotations: annotations, + Labels: labels, + }, + } + } + + // Add ServeConfigMap to RayService annotations FIXME + if serveConfig, exists := serveConfigMap.Data["serveconfig.yaml"]; exists { + rs.Spec.ServeConfigV2 = serveConfig + } else { + logger.Error(fmt.Errorf("serveconfig.yaml not found"), "ServeConfigMap is missing serveconfig.yaml key") + return fmt.Errorf("serveconfig.yaml not found in ConfigMap %s", serveConfigMapKey.Name) + } + + rayService.Spec = rs.Spec + key := types.NamespacedName{Namespace: rayService.Namespace, Name: rayService.Name} + return retry.RetryOnConflict(retry.DefaultRetry, func() error { + var current rayv1.RayService + if err := b.Client.Get(ctx, key, ¤t); err != nil { + if errors.IsNotFound(err) { + controllerutil.SetOwnerReference(p, rayService, b.Scheme) + return b.Client.Create(ctx, rayService) + } + b.Recorder.Eventf(p, corev1.EventTypeWarning, "ReconcileFailed", "Failed to reconcile RayService %v", err) + return err + } + + // mutate current.Spec to match desired svc.Spec + current.Spec = rs.Spec + // now try update + controllerutil.SetOwnerReference(p, ¤t, b.Scheme) + return b.Client.Update(ctx, ¤t) + }) +} + +func (b *Builder) ReconcileRayAutoscalerRBAC(ctx context.Context, p *enterpriseApi.AIPlatform) error { + logger := log.FromContext(ctx) + saName := p.Spec.HeadGroupSpec.ServiceAccountName + if saName == "" { + logger.Info("No ServiceAccount specified for Ray head group, skipping RBAC reconciliation") + return nil + } + + role := &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ray-autoscaler", + Namespace: p.Namespace, + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"ray.io"}, + Resources: []string{"rayclusters", "rayservices", "rayjobs"}, + Verbs: []string{"get", "list", "watch", "patch", "update", "delete"}, + }, + }, + } + + if err := b.Client.Create(ctx, role); err != nil && !errors.IsAlreadyExists(err) { + return err + } + controllerutil.SetOwnerReference(p, role, b.Scheme) + + roleBinding := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ray-autoscaler-binding-" + p.Namespace + "-" + saName, + Namespace: p.Namespace, + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: saName, + Namespace: p.Namespace, + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: "ray-autoscaler", + }, + } + + if err := b.Client.Create(ctx, roleBinding); err != nil && !errors.IsAlreadyExists(err) { + return err + } + controllerutil.SetOwnerReference(p, roleBinding, b.Scheme) + return nil +} + +func (b *Builder) ReconcileRayServiceStatus( + ctx context.Context, + p *enterpriseApi.AIPlatform, +) error { + // 1️⃣ fetch the up-to-date RayService + rs := &rayv1.RayService{} + key := types.NamespacedName{Namespace: p.Namespace, Name: p.Name} + if err := b.Client.Get(ctx, key, rs); err != nil { + return err + } + + // 2️⃣ mirror its status into your CR + p.Status.RayServiceStatus = rs.Status.ServiceStatus + + // Add Ray head service name to status + p.Status.RayServiceName = fmt.Sprintf("%s-head-svc", p.Name) + + // 3️⃣ set a Condition based on whatever flag you like—e.g. the top-level Ready + ready := metav1.ConditionFalse + reason := "RayServiceStatus" + msg := "ray service is not yet ready" + if rs.Status.ServiceStatus == rayv1.Running { + ready = metav1.ConditionTrue + reason = "RayServiceReady" + msg = "ray service is running" + } + + cond := metav1.Condition{ + Type: "RayServiceReady", + Status: ready, + Reason: reason, + Message: msg, + LastTransitionTime: metav1.Now(), + } + meta.SetStatusCondition(&p.Status.Conditions, cond) + + return nil +} + +// Build constructs a RayService resource based on the AI CR. +func (b *Builder) Build() *rayv1.RayService { + rs := &rayv1.RayService{ + ObjectMeta: metav1.ObjectMeta{ + Name: b.ai.Name, + Namespace: b.ai.Namespace, + Annotations: b.ai.Annotations, + Labels: b.ai.Labels, + }, + Spec: rayv1.RayServiceSpec{ + RayClusterSpec: b.buildClusterConfig(), + }, + } + return rs +} + +func (b *Builder) buildClusterConfig() rayv1.RayClusterSpec { + annotations, labels := buildHeadAnnotationsAndLabels(b.ai) + head := rayv1.HeadGroupSpec{ + RayStartParams: map[string]string{ + "dashboard-host": "0.0.0.0", + "num-cpus": "0", + }, + HeadService: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: b.ai.Name + "-head-svc", + Namespace: b.ai.Namespace, + Annotations: annotations, + Labels: labels, + }, + }, + Template: b.makeHeadTemplate(), + } + + head.Template.ObjectMeta.Annotations = annotations + head.Template.ObjectMeta.Labels = labels + + var workers []rayv1.WorkerGroupSpec + for i, cfg := range b.ai.Spec.WorkerGroupSpec.GPUConfigs { + annotations, labels := buildWorkerAnnotationsAndLabels(b.ai, cfg) + wg := rayv1.WorkerGroupSpec{ + GroupName: cfg.Tier, + MinReplicas: &cfg.MinReplicas, + MaxReplicas: &cfg.MaxReplicas, + RayStartParams: map[string]string{ + "resources": fmt.Sprintf(`"{\"accelerator_type:%s\":1,\"gpu_count:%d\":%d}"`, b.ai.Spec.DefaultAcceleratorType, i, cfg.GPUsPerPod), + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: annotations, + Labels: labels, + }, + Spec: b.makeWorkerTemplate(cfg).Spec, + }, + } + workers = append(workers, wg) + } + + return rayv1.RayClusterSpec{ + RayVersion: os.Getenv("RAY_VERSION"), + EnableInTreeAutoscaling: boolPtr(true), + HeadGroupSpec: head, + WorkerGroupSpecs: workers, + } +} + +func (b *Builder) makeHeadTemplate() corev1.PodTemplateSpec { + spec := corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "ray-head", + Image: SetImageRegistry("RELATED_IMAGE_RAY_HEAD", b.ai.Spec.HeadGroupSpec.ImageRegistry), + Args: []string{ + "ulimit -n 65536; echo head; $KUBERAY_GEN_RAY_START_CMD", + }, + Command: []string{ + "/bin/bash", + "-lc", + "--", + }, + Env: []corev1.EnvVar{ + {Name: "DEFAULT_ACCELERATOR_TYPE", Value: b.ai.Spec.DefaultAcceleratorType}, + {Name: "CLUSTER_NAME", Value: os.Getenv("CLUSTER_NAME")}, + }, + Lifecycle: &corev1.Lifecycle{ + PreStop: &corev1.LifecycleHandler{ + Exec: &corev1.ExecAction{ + Command: []string{ + "/bin/sh", + "-c", + "ray stop", + }, + }, + }, + }, + Ports: []corev1.ContainerPort{ + { + ContainerPort: 6379, + Name: "gcs-server", + Protocol: corev1.ProtocolTCP, + }, + { + ContainerPort: 8265, + Name: "dashboard", + Protocol: corev1.ProtocolTCP, + }, + { + ContainerPort: 10001, + Name: "client", + Protocol: corev1.ProtocolTCP, + }, + { + ContainerPort: 8000, + Name: "serve", + Protocol: corev1.ProtocolTCP, + }, + }, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1"), + corev1.ResourceMemory: resource.MustParse("2Gi"), + corev1.ResourceEphemeralStorage: resource.MustParse("5Gi"), + "nvidia.com/gpu": resource.MustParse("0"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("4"), + corev1.ResourceMemory: resource.MustParse("8Gi"), + corev1.ResourceEphemeralStorage: resource.MustParse("10Gi"), + "nvidia.com/gpu": resource.MustParse("0"), + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + MountPath: "/tmp/ray", + Name: "ray-logs", + }, + }, + }}, + } + + spec.NodeSelector = b.ai.Spec.HeadGroupSpec.NodeSelector + spec.Tolerations = b.ai.Spec.HeadGroupSpec.Tolerations + spec.Affinity = b.ai.Spec.HeadGroupSpec.Affinity + spec.ServiceAccountName = b.ai.Spec.HeadGroupSpec.ServiceAccountName + // FIXME need to find better way to add sidecars + sidecars := sidecars.New(b.Client, b.Scheme, b.Recorder, b.ai) + sidecars.AddFluentBitSidecar(&spec) + return corev1.PodTemplateSpec{Spec: spec} +} + +func (b *Builder) makeWorkerTemplate(cfg enterpriseApi.GPUConfig) corev1.PodTemplateSpec { + rayCommand := fmt.Sprintf(`echo %s worker; + ulimit -n 65536; + export PATH="/home/ray/anaconda3/bin:$PATH"; + KUBERAY_GEN_RAY_START_CMD=$(echo $KUBERAY_GEN_RAY_START_CMD | sed -e 's/"{/{/g' -e 's/}"/}/g' -e 's/\\\"/"/g'); + $KUBERAY_GEN_RAY_START_CMD;`, cfg.Tier) + spec := corev1.PodSpec{ + ServiceAccountName: b.ai.Spec.WorkerGroupSpec.ServiceAccountName, + Containers: []corev1.Container{{ + Name: "ray-worker", + Image: SetImageRegistry("RELATED_IMAGE_RAY_WORKER", b.ai.Spec.WorkerGroupSpec.ImageRegistry), + ImagePullPolicy: corev1.PullAlways, + Command: []string{ + "/bin/bash", + "-lc", + "--", + }, + Args: []string{ + rayCommand, + }, + Env: []corev1.EnvVar{ + {Name: "DEFAULT_ACCELERATOR_TYPE", Value: b.ai.Spec.DefaultAcceleratorType}, + {Name: "RAY_HEAD_SERVICE_HOST", Value: fmt.Sprintf("%s.%s.svc.%s", b.ai.Name+"-head-svc", b.ai.Namespace, os.Getenv("CLUSTER_DOMAIN"))}, + {Name: "SERVICE_NAME", Value: b.ai.Name}, + {Name: "SERVICE_INTERNAL_NAME", Value: b.ai.Name}, + {Name: "USE_SYSTEM_PERMISSIONS", Value: "true"}, + {Name: "GPG_PUBLICKEY_PATH", Value: "kv-splunk/al-platform.ray-worker-sa/gpgkey"}, // FIXME + {Name: "GPU_TYPE", Value: "L40S"}, // FIXME + {Name: "NVIDIA_VISIBLE_DEVICES", Value: "all"}, + }, + Lifecycle: &corev1.Lifecycle{ + PreStop: &corev1.LifecycleHandler{ + Exec: &corev1.ExecAction{ + Command: []string{ + "/bin/sh", + "-c", + "ray stop", + }, + }, + }, + }, + Resources: cfg.Resources, + VolumeMounts: []corev1.VolumeMount{ + { + MountPath: "/tmp/ray", + Name: "ray-logs", + }, + }, + Ports: []corev1.ContainerPort{ + { + ContainerPort: 8080, + Name: "metrics", + Protocol: corev1.ProtocolTCP, + }, + }, + }}, + } + + // apply scheduling + spec.NodeSelector = b.ai.Spec.WorkerGroupSpec.NodeSelector + spec.Tolerations = b.ai.Spec.WorkerGroupSpec.Tolerations + spec.Affinity = b.ai.Spec.WorkerGroupSpec.Affinity + + found := false + for _, vol := range spec.Volumes { + if vol.Name == "ray-logs" { + found = true + break + } + } + + if !found { + spec.Volumes = append(spec.Volumes, corev1.Volume{ + Name: "ray-logs", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }) + } + // FIXME need to find better way to add sidecars + sidecars := sidecars.New(b.Client, b.Scheme, b.Recorder, b.ai) + sidecars.AddFluentBitSidecar(&spec) + + return corev1.PodTemplateSpec{Spec: spec} +} + +func SetImageRegistry(key, defaultValue string) string { + if val := os.Getenv(key); val != "" { + return val + } + return defaultValue +} + +func buildWorkerAnnotationsAndLabels(aiPlatform *enterpriseApi.AIPlatform, cfg enterpriseApi.GPUConfig) (map[string]string, map[string]string) { + annotations := make(map[string]string) + labels := make(map[string]string) + + // Example: propagate tier and GPU type as labels/annotations + annotations["gpu-tier"] = cfg.Tier + labels["gpu-tier"] = cfg.Tier + if aiPlatform.Annotations != nil { + for k, v := range aiPlatform.Annotations { + if strings.Contains(k, "last-applied-configuration") { + continue + } + annotations[k] = v + } + } + if aiPlatform.Labels != nil { + for k, v := range aiPlatform.Labels { + if strings.Contains(k, "last-applied-configuration") { + continue + } + labels[k] = v + } + } + annotations["prometheus.io/path"] = "/metrics" + annotations["prometheus.io/port"] = "8080" + annotations["prometheus.io/scheme"] = "http" + annotations["ray.io/overwrite-container-cmd"] = "true" + if aiPlatform.Spec.Sidecars.Otel { + annotations["sidecar.opentelemetry.io/inject"] = fmt.Sprintf("%s-otel-coll", aiPlatform.Name) + annotations["sidecar.opentelemetry.io/auto-instrument"] = "true" + } + + // Add any additional logic as needed + + return annotations, labels +} + +func buildHeadAnnotationsAndLabels(aiPlatform *enterpriseApi.AIPlatform) (map[string]string, map[string]string) { + annotations := make(map[string]string) + labels := make(map[string]string) + + // Example: propagate tier and GPU type as labels/annotations + if aiPlatform.Annotations != nil { + for k, v := range aiPlatform.Annotations { + if strings.Contains(k, "last-applied-configuration") { + continue + } + annotations[k] = v + } + } + if aiPlatform.Labels != nil { + for k, v := range aiPlatform.Labels { + if strings.Contains(k, "last-applied-configuration") { + continue + } + labels[k] = v + } + } + annotations["prometheus.io/path"] = "/metrics" + annotations["prometheus.io/port"] = "8080" + annotations["prometheus.io/scheme"] = "http" + annotations["ray.io/overwrite-container-cmd"] = "true" + + if aiPlatform.Spec.Sidecars.Otel { + annotations["sidecar.opentelemetry.io/inject"] = fmt.Sprintf("%s-otel-coll", aiPlatform.Name) + annotations["sidecar.opentelemetry.io/auto-instrument"] = "true" + } + + return annotations, labels +} + +// boolPtr returns a pointer to the given boolean value. +func boolPtr(b bool) *bool { + return &b +} diff --git a/controllers/ai_platform/raybuilder/configmap_apps.go b/controllers/ai_platform/raybuilder/configmap_apps.go new file mode 100644 index 000000000..c85810819 --- /dev/null +++ b/controllers/ai_platform/raybuilder/configmap_apps.go @@ -0,0 +1,515 @@ +package raybuilder + +import ( + "context" + "os" + "path" + + enterpriseApi "github.com/splunk/splunk-operator/api/v4" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +const DefaultApplicationsYaml = `applications: + - name: uae_large + args: + deployment_configs: + EncoderDeployment: + env_options_override: + prod: + autoscaling_config: + min_replicas: 1 + options: + autoscaling_config: + max_replicas: 12 + min_replicas: 3 + target_ongoing_requests: 3 + ray_actor_options: + num_gpus: 0.05 + deployment_type: encoder_deployment + model_definition: + model_id: uae_large + model_loader: + s3_artifact: + s3_key_prefix: model_artifacts/uae-large + model_type: sentence_transformer +` + +const TestDefaultApplicationsYaml = `applications: + - name: deepseek_r1_distill_llama_8b + args: + deployment_configs: + LLMDeployment: + options: + ray_actor_options: + num_gpus: 1 + deployment_type: vllm_text_gen_model_deployment + model_definition: + model_config: + engine_args: + tensor_parallel_size: 1 + model_id: deepseek_r1_distill_llama_8b + model_loader: + hugging_face_id: + hf_model_id: deepseek-ai/DeepSeek-R1-Distill-Llama-8B + runtime_env: + env_vars: + VLLM_WORKER_MULTIPROC_METHOD: spawn + + - name: deepseek_r1_distill_llama_70b_awq + args: + deployment_configs: + LLMDeployment: + options: + autoscaling_config: + target_ongoing_requests: 3 + max_ongoing_requests: 4 + ray_actor_options: + num_gpus: 2 + deployment_type: vllm_text_gen_model_deployment + model_definition: + model_config: + engine_args: + gpu_memory_utilization: 0.95 + tensor_parallel_size: 2 + model_id: deepseek_r1_distill_llama_70b_awq + model_loader: + hugging_face_id: + hf_model_id: casperhansen/deepseek-r1-distill-llama-70b-awq + runtime_env: + env_vars: + VLLM_WORKER_MULTIPROC_METHOD: spawn + + - name: uae_large + args: + deployment_configs: + EncoderDeployment: + env_options_override: + prod: + autoscaling_config: + min_replicas: 1 + options: + autoscaling_config: + max_replicas: 12 + min_replicas: 3 + target_ongoing_requests: 3 + ray_actor_options: + num_gpus: 0.05 + deployment_type: encoder_deployment + model_definition: + model_id: uae_large + model_loader: + s3_artifact: + s3_key_prefix: model_artifacts/uae-large + model_type: sentence_transformer + + - name: llava15_13b + args: + custom_deployment_import_path: llava15_13b:VLLMGenerativeModelDeployment + deployment_configs: + VLLMGenerativeModelDeployment: + options: + autoscaling_config: + max_replicas: 1 + min_replicas: 0 + ray_actor_options: + num_gpus: 1 + deployment_type: custom_deployment + runtime_env: + env_vars: + VLLM_WORKER_MULTIPROC_METHOD: spawn + pip: + - python-multipart + + - name: mistral + args: + deployment_configs: + LLMDeployment: + options: + ray_actor_options: + num_gpus: 1 + deployment_type: vllm_text_gen_model_deployment + model_definition: + model_config: + engine_args: + tensor_parallel_size: 1 + model_id: mistral + model_loader: + s3_artifact: + s3_key_prefix: model_artifacts/mistral + tokenizer_definition: + model_id: mistral + model_loader: + s3_artifact: + artifacts_list: + - tokenizer_config.json + - tokenizer.model + s3_key_prefix: model_artifacts/mistral + runtime_env: + env_vars: + VLLM_WORKER_MULTIPROC_METHOD: spawn + + - name: all_minilm_l6_v2 + args: + deployment_configs: + EncoderDeployment: + options: + autoscaling_config: + max_replicas: 12 + min_replicas: 1 + target_ongoing_requests: 3 + ray_actor_options: + num_gpus: 0.01 + deployment_type: encoder_deployment + model_definition: + model_id: all_minilm_l6_v2 + model_loader: + s3_artifact: + s3_key_prefix: model_artifacts/all-minilm-l6-v2 + model_type: sentence_transformer + + - name: bi_encoder + args: + deployment_type: encoder_deployment + model_definition: + model_id: bi_encoder + model_loader: + s3_artifact: + s3_key_prefix: model_artifacts/bi-encoder + model_type: sentence_transformer + + - name: mbart_translator + args: + custom_deployment_import_path: mbart_translator:MbartTranslatorDeployment + deployment_configs: + MbartTranslatorDeployment: + options: + ray_actor_options: + num_gpus: 0.1 + deployment_type: custom_deployment + + - name: spacy_di + args: + custom_deployment_import_path: spacy_di:SpacyDiDeployment + deployment_configs: + SpacyDiDeployment: + options: + ray_actor_options: + num_gpus: 0.01 + deployment_type: custom_deployment + + - name: phi4 + args: + deployment_configs: + LLMDeployment: + options: + ray_actor_options: + num_gpus: 1 + deployment_type: vllm_text_gen_model_deployment + model_definition: + model_config: + engine_args: + tensor_parallel_size: 1 + model_id: phi4 + model_loader: + hugging_face_id: + hf_model_id: NyxKrage/Microsoft_Phi-4 + runtime_env: + env_vars: + VLLM_WORKER_MULTIPROC_METHOD: spawn + + - name: xlm_roberta_language_classifier + args: + deployment_type: classifier_deployment + model_definition: + model_id: xlm_roberta_language_classifier + model_loader: + s3_artifact: + s3_key_prefix: model_artifacts/xlm-roberta-language-classifier + + - name: prompt_injection_tfidf + args: + custom_deployment_import_path: prompt_injection_tfidf:PromptInjectionTfidfDeployment + deployment_type: custom_deployment + + - name: cross_encoder + args: + deployment_configs: + EncoderDeployment: + options: + ray_actor_options: + num_gpus: 0.01 + deployment_type: encoder_deployment + model_definition: + model_id: cross_encoder + model_loader: + s3_artifact: + s3_key_prefix: model_artifacts/cross-encoder + model_type: sentence_transformer_cross_encoder + + - name: pii_classifier + args: + custom_deployment_import_path: pii_classifier:PIIClassifierDeployment + deployment_configs: + PIIClassifierDeployment: + options: + ray_actor_options: + num_cpus: 4 + deployment_type: custom_deployment + + - name: llama31_instruct + args: + deployment_configs: + LLMDeployment: + env_options_override: + prod: + autoscaling_config: + min_replicas: 1 + gpu_type_options_override: + A10G: + ray_actor_options: + num_gpus: 2 + L40S: + ray_actor_options: + num_gpus: 1 + T4: + ray_actor_options: + num_gpus: 4 + options: + autoscaling_config: + min_replicas: 2 + deployment_type: vllm_text_gen_model_deployment + model_definition: + gpu_type_model_config_override: + A10G: + engine_args: + max_model_len: 14190 + tensor_parallel_size: 2 + L40S: + engine_args: + max_model_len: 14190 + tensor_parallel_size: 1 + T4: + engine_args: + dtype: half + max_model_len: 14190 + tensor_parallel_size: 4 + model_id: llama31_instruct + model_loader: + s3_artifact: + s3_key_prefix: model_artifacts/llama31-8b-instruct + tokenizer_definition: + model_id: llama31_instruct + model_loader: + s3_artifact: + artifacts_list: + - tokenizer_config.json + - tokenizer.json + s3_key_prefix: model_artifacts/llama31-8b-instruct + runtime_env: + env_vars: + VLLM_WORKER_MULTIPROC_METHOD: spawn + + - name: e5_language_classifier + args: + deployment_type: classifier_deployment + model_definition: + model_id: e5_language_classifier + model_loader: + s3_artifact: + s3_key_prefix: model_artifacts/e5-language-classifier + + - name: llama31_70b_instruct_awq + args: + deployment_configs: + LLMDeployment: + env_options_override: + dev: + autoscaling_config: + min_replicas: 2 + gpu_type_options_override: + A10G: + ray_actor_options: + num_gpus: 4 + L40S: + ray_actor_options: + num_gpus: 2 + T4: + ray_actor_options: + num_gpus: 8 + options: + autoscaling_config: + min_replicas: 1 + target_ongoing_requests: 3 + max_ongoing_requests: 4 + deployment_type: vllm_text_gen_model_deployment + model_definition: + gpu_type_model_config_override: + A10G: + engine_args: + gpu_memory_utilization: 0.95 + max_model_len: 28930 + tensor_parallel_size: 4 + L40S: + engine_args: + gpu_memory_utilization: 0.95 + max_model_len: 28930 + tensor_parallel_size: 2 + T4: + engine_args: + dtype: half + max_model_len: 28930 + tensor_parallel_size: 8 + model_id: llama31_70b_instruct_awq + model_loader: + s3_artifact: + s3_key_prefix: model_artifacts/llama31-70b-instruct-awq + tokenizer_definition: + model_id: llama31_70b_instruct_awq + model_loader: + s3_artifact: + artifacts_list: + - tokenizer_config.json + - tokenizer.json + s3_key_prefix: model_artifacts/llama31-70b-instruct-awq + runtime_env: + env_vars: + VLLM_WORKER_MULTIPROC_METHOD: spawn + + - name: prompt_injection_cross_encoder + args: + deployment_configs: + EncoderDeployment: + options: + ray_actor_options: + num_gpus: 0.01 + deployment_type: encoder_deployment + model_definition: + model_id: prompt_injection_cross_encoder + model_loader: + s3_artifact: + s3_key_prefix: model_artifacts/prompt-injection-cross-encoder-1114 + model_type: sentence_transformer_cross_encoder + + - name: prompt_injection_classifier + args: + deployment_type: classifier_deployment + model_definition: + custom_model_import_path: prompt_injection_classifier:PromptInjectionClassifier + model_id: prompt_injection_classifier + model_loader: + s3_artifact: + s3_key_prefix: model_artifacts/prompt-injection-classifier-01052025 + model_type: custom_model + + - name: bert_logparser_winlogbeat + args: + deployment_type: encoder_deployment + model_definition: + custom_model_import_path: bert_logparser_winlogbeat:BertLogparserWinlogbeatEncoder + model_id: bert_logparser_winlogbeat + model_loader: + s3_artifact: + s3_key_prefix: model_artifacts/bert-logparser-winlogbeat + model_type: custom_model + + - name: llama33_70b_instruct_awq + args: + deployment_configs: + LLMDeployment: + gpu_type_options_override: + A10G: + ray_actor_options: + num_gpus: 4 + L40S: + ray_actor_options: + num_gpus: 2 + options: + autoscaling_config: + min_replicas: 1 + target_ongoing_requests: 3 + max_ongoing_requests: 4 + deployment_type: vllm_text_gen_model_deployment + model_definition: + gpu_type_model_config_override: + A10G: + engine_args: + gpu_memory_utilization: 0.95 + tensor_parallel_size: 4 + openai_serving_config: + enable_auto_tools: true + tool_parser: llama3_json + L40S: + engine_args: + gpu_memory_utilization: 0.95 + tensor_parallel_size: 2 + openai_serving_config: + enable_auto_tools: true + tool_parser: llama3_json + model_id: llama33_70b_instruct_awq + model_loader: + hugging_face_id: + hf_model_id: casperhansen/llama-3.3-70b-instruct-awq + runtime_env: + env_vars: + VLLM_WORKER_MULTIPROC_METHOD: spawn + + - name: qwq_32b + args: + deployment_configs: + LLMDeployment: + options: + autoscaling_config: + target_ongoing_requests: 1 + max_ongoing_requests: 2 + ray_actor_options: + num_gpus: 4 + VLLMTextGenModelDeployment: + options: + ray_actor_options: + num_cpus: 0.25 + deployment_type: vllm_text_gen_model_deployment + model_definition: + model_config: + engine_args: + tensor_parallel_size: 4 + model_id: qwq_32b + model_loader: + hugging_face_id: + hf_model_id: Qwen/QwQ-32B + runtime_env: + env_vars: + VLLM_WORKER_MULTIPROC_METHOD: spawn + + - name: openai_proxy + import_path: main:SERVE_APP + runtime_env: + env_vars: + ENABLE_AUTHZ: 'true' + OPENAI_URL: '{{ .Values.openai.baseUrl }}' + SECRETS_FILE_PATH: /home/ray/secrets.json` + +// --- 5️⃣ ReconcileApplicationsConfigMap: bootstrap user‐editable apps fragment --- +func (b *Builder) ReconcileApplicationsConfigMap(ctx context.Context, p *enterpriseApi.AIPlatform) error { + cm := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{ + Name: p.Name + "-applications", + Namespace: p.Namespace, + }} + _, err := controllerutil.CreateOrUpdate(ctx, b.Client, cm, func() error { + if cm.Data == nil { + cm.Data = map[string]string{} + } + if _, exists := cm.Data["applications.yaml"]; !exists { + home := os.Getenv("HOME") + content, err := os.ReadFile(path.Join(home, "applications.yaml")) + if err != nil { + return err + } + cm.Data["applications.yaml"] = string(content) + //cm.Data["applications.yaml"] = DefaultApplicationsYaml + } + return controllerutil.SetOwnerReference(p, cm, b.Scheme) + }) + return err +} diff --git a/controllers/ai_platform/raybuilder/configmap_models.go b/controllers/ai_platform/raybuilder/configmap_models.go new file mode 100644 index 000000000..0dc36c42f --- /dev/null +++ b/controllers/ai_platform/raybuilder/configmap_models.go @@ -0,0 +1 @@ +package raybuilder diff --git a/controllers/ai_platform/raybuilder/configmap_serve.go b/controllers/ai_platform/raybuilder/configmap_serve.go new file mode 100644 index 000000000..b3fc468f2 --- /dev/null +++ b/controllers/ai_platform/raybuilder/configmap_serve.go @@ -0,0 +1,187 @@ +package raybuilder + +import ( + "context" + "fmt" + "os" + "regexp" + "strings" + "unicode" + + enterpriseApi "github.com/splunk/splunk-operator/api/v4" + "github.com/splunk/splunk-operator/controllers/storage" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" + syaml "sigs.k8s.io/yaml" +) + +func (b *Builder) ReconcileServeConfigMap(ctx context.Context, p *enterpriseApi.AIPlatform) error { + log := log.FromContext(ctx) // Define logger + + // 2️⃣ List actual artifacts in storage + storCli, err := storage.NewStorageClient(b.Client, p.Namespace, p.Spec.AppsVolume) + if err != nil { + log.Error(err, "failed to create storage client") + return err + } + + // 2️⃣ List actual artifacts in storage + artfCli, err := storage.NewStorageClient(b.Client, p.Namespace, p.Spec.ArtifactsVolume) + if err != nil { + log.Error(err, "failed to create storage client") + return err + } + + // 4️⃣ Load the user’s apps fragment + appsCM := &corev1.ConfigMap{} + if err := b.Client.Get(ctx, client.ObjectKey{Namespace: p.Namespace, Name: p.Name + "-applications"}, appsCM); err != nil { + log.Error(err, "failed to get applications configmap") + return err + } + + var config Config + err = syaml.Unmarshal([]byte(appsCM.Data["applications.yaml"]), &config) + if err != nil { + log.Error(err, "failed to unmarshal applications.yaml") + return fmt.Errorf("failed to unmarshal applications.yaml: %w", err) + } + + // 2) Prepare to filter in-place + version := os.Getenv("MODEL_VERSION") + apps := config.RayService.Applications[:0] // reset length to 0, keep capacity + + // 3) Walk the original list and keep only the ones that exist + for _, a := range config.RayService.Applications { + // build the key your Exists method expects + // e.g. "appName-modelVersion.zip" + key := fmt.Sprintf("%s-%s.zip", a.Name, version) + + ok, err := storCli.Exists(ctx, key) + if err != nil { + return fmt.Errorf("checking existence of %s: %w", key, err) + } + if !ok { + // skip it — ZIP not in th bucket/container + continue + } + + // keep it + apps = append(apps, a) + } + + // 4) Assign the filtered slice back + config.RayService.Applications = apps + + // 6️⃣ Build dynamic entries + for i := range config.RayService.Applications { + app := &config.RayService.Applications[i] + fileName := fmt.Sprintf("%s-%s.zip", app.Name, os.Getenv("MODEL_VERSION")) + wd := storCli.BuildWorkingDir(fileName) + appName := app.Name + if app.ImportPath == "" { + app.ImportPath = "main:create_serve_app" + } + if app.RoutePrefix == "" { + app.RoutePrefix = "/" + strings.ToLower(appName) + } + if app.RuntimeEnv != nil { + if app.RuntimeEnv.WorkingDir == "" { + app.RuntimeEnv.WorkingDir = wd + } + if app.RuntimeEnv.EnvVars == nil { + app.RuntimeEnv.EnvVars = map[string]string{} + } + app.RuntimeEnv.EnvVars["SERVICE_NAME"] = sanitizeMetricName(p.Name) + app.RuntimeEnv.EnvVars["SERVICE_INTERNAL_NAME"] = sanitizeMetricName(p.Name) + app.RuntimeEnv.EnvVars["USE_SYSTEM_PERMISSIONS"] = "true" + app.RuntimeEnv.EnvVars["APPLICATION_NAME"] = appName + app.RuntimeEnv.EnvVars["ARTIFACTS_PROVIDER"] = artfCli.GetProvider() + app.RuntimeEnv.EnvVars["ARTIFACTS_BUCKET"] = artfCli.GetBucket() + + } else { + app.RuntimeEnv = new(RuntimeEnv) + app.RuntimeEnv.WorkingDir = wd + app.RuntimeEnv.EnvVars = map[string]string{ + "SERVICE_NAME": sanitizeMetricName(p.Name), + "SERVICE_INTERNAL_NAME": sanitizeMetricName(p.Name), + "USE_SYSTEM_PERMISSIONS": "true", + "APPLICATION_NAME": appName, + "ARTIFACTS_PROVIDER": artfCli.GetProvider(), + "ARTIFACTS_BUCKET": artfCli.GetBucket(), + } + } + if app.RuntimeEnv.Pip == nil { + app.RuntimeEnv.Pip = []string{ + "azure-storage-blob", + "google-cloud-storage", + } + } + app.Name = toCamelCase(app.Name) + } + + serveConfig := config.RayService + serveConfig.HTTPOptions = HTTPOptions{ + KeepAliveTimeoutS: 10, + } + serveConfig.LoggingConfig = LoggingConfig{ + LogLevel: "INFO", + LogsDir: "/tmp/ray/logs", + Encoding: "JSON", + EnableAccessLog: true, + } + out, err := syaml.Marshal(serveConfig) + if err != nil { + log.Error(err, "failed to marshal serveConfig") + return fmt.Errorf("failed to marshal serveConfig: %w", err) + } + + cm := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{ + Name: p.Name + "-serveconfig", + Namespace: p.Namespace, + }, + } + + _, err = controllerutil.CreateOrUpdate(ctx, b.Client, cm, func() error { + if cm.Data == nil { + cm.Data = map[string]string{} + } + cm.Data["serveconfig.yaml"] = string(out) + return controllerutil.SetOwnerReference(p, cm, b.Scheme) + }) + return err +} + +func toCamelCase(s string) string { + words := strings.Split(s, "_") + result := "" + for i, word := range words { + if i == 0 { + result += word + continue + } + runes := []rune(word) + runes[0] = unicode.ToUpper(runes[0]) + result += string(runes) + } + return result +} + +// metricNameRE matches valid Prometheus metric name characters +var metricNameRE = regexp.MustCompile(`[^A-Za-z0-9_:]`) + +// sanitizeMetricName replaces all invalid chars (including “-”) with “_” +// and ensures the first character is a letter or underscore. +func sanitizeMetricName(s string) string { + // 1) replace anything not [A-Za-z0-9_:] with “_” + m := metricNameRE.ReplaceAllString(s, "_") + + // 2) make sure it starts with [A-Za-z_:] + if len(m) == 0 || !((m[0] >= 'A' && m[0] <= 'Z') || + (m[0] >= 'a' && m[0] <= 'z') || m[0] == '_' || m[0] == ':') { + m = "_" + m + } + return m +} diff --git a/controllers/ai_platform/raybuilder/types.go b/controllers/ai_platform/raybuilder/types.go new file mode 100644 index 000000000..deaf5405d --- /dev/null +++ b/controllers/ai_platform/raybuilder/types.go @@ -0,0 +1,54 @@ +package raybuilder + +// types.go +type ServeConfig struct { + ProxyLocation string `json:"proxy_location,omitempty"` + HTTPOptions HTTPOptions `json:"http_options"` + GRPCOptions GRPCOptions `json:"grpc_options"` + LoggingConfig LoggingConfig `json:"logging_config"` + Applications []Application `json:"applications"` +} + +type HTTPOptions struct { + Host string `json:"host,omitempty"` + Port int `json:"port,omitempty"` + RequestTimeoutS int `json:"request_timeout_s,omitempty"` + KeepAliveTimeoutS int `json:"keep_alive_timeout_s"` +} + +type GRPCOptions struct { + Port int `json:"port,omitempty"` + GRPCServicerFunctions []string `json:"grpc_servicer_functions,omitempty"` + RequestTimeoutS int `json:"request_timeout_s,omitempty"` +} + +type LoggingConfig struct { + LogLevel string `json:"log_level,omitempty"` + LogsDir string `json:"logs_dir,omitempty"` + Encoding string `json:"encoding,omitempty"` + EnableAccessLog bool `json:"enable_access_log,omitempty"` +} + +// Application mirrors one rayService application +type Application struct { + Name string `json:"name"` + ImportPath string `json:"import_path,omitempty"` + RoutePrefix string `json:"route_prefix,omitempty"` + Args map[string]interface{} `json:"args,omitempty"` + RuntimeEnv *RuntimeEnv `json:"runtime_env,omitempty"` + + // catch any unmodeled keys: + //Extras map[string]interface{} `json:",inline"` +} + +// RuntimeEnv mirrors the runtime_env field in rayService +type RuntimeEnv struct { + WorkingDir string `json:"working_dir,omitempty"` + EnvVars map[string]string `json:"env_vars,omitempty"` + Pip []string `json:"pip,omitempty"` +} + +// Config is just a thin wrapper around rayService.applications +type Config struct { + RayService ServeConfig `json:"rayService"` +} diff --git a/controllers/ai_platform/rayservice.go b/controllers/ai_platform/rayservice.go new file mode 100644 index 000000000..1d19909ad --- /dev/null +++ b/controllers/ai_platform/rayservice.go @@ -0,0 +1 @@ +package ai_platform diff --git a/controllers/ai_platform/reconciler.go b/controllers/ai_platform/reconciler.go new file mode 100644 index 000000000..1b7068224 --- /dev/null +++ b/controllers/ai_platform/reconciler.go @@ -0,0 +1,101 @@ +package ai_platform + +import ( + "context" + + enterpriseApi "github.com/splunk/splunk-operator/api/v4" + "github.com/splunk/splunk-operator/controllers/ai_platform/raybuilder" + "github.com/splunk/splunk-operator/controllers/ai_platform/sidecars" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + //corev1 "k8s.io/api/core/v1" +) + +type AIPlatformReconciler struct { + p *enterpriseApi.AIPlatform + client.Client + Scheme *runtime.Scheme + Recorder record.EventRecorder +} + +func New(p *enterpriseApi.AIPlatform, client client.Client, scheme *runtime.Scheme, recorder record.EventRecorder) *AIPlatformReconciler { + return &AIPlatformReconciler{ + p: p, + Client: client, + Scheme: scheme, + Recorder: recorder, + } +} + +func (r *AIPlatformReconciler) Reconcile(ctx context.Context, p *enterpriseApi.AIPlatform) (reconcile.Result, error) { + + var conditions []metav1.Condition + defer func() { + // Fetch the latest version of the CR before updating the status + latest := &enterpriseApi.AIPlatform{} + namespacedName := client.ObjectKey{Namespace: p.Namespace, Name: p.Name} + if err := r.Get(ctx, namespacedName, latest); err != nil { + log.FromContext(ctx).Error(err, "failed to fetch latest CR") + return + } + latest.Status = p.Status + latest.Status.Conditions = conditions + latest.Status.ObservedGeneration = p.Generation + _ = r.Status().Update(ctx, latest) + }() + raybuilder := raybuilder.New(r.p, r.Client, r.Scheme, r.Recorder) + sidecarBuilder := sidecars.New(r.Client, r.Scheme, r.Recorder, r.p) + + stages := []struct { + name string + fn func(context.Context, *enterpriseApi.AIPlatform) error + }{ + {"Validate", r.validate}, + {"ApplicationsConfigMap", raybuilder.ReconcileApplicationsConfigMap}, + {"ServeConfigMap", raybuilder.ReconcileServeConfigMap}, + {"Sidecars", sidecarBuilder.Reconcile}, + {"rayAutoscalerRBAC", raybuilder.ReconcileRayAutoscalerRBAC}, + {"RayService", raybuilder.ReconcileRayService}, + {"WeaviateDatabase", r.ReconcileWeaviateDatabase}, + {"RayServiceStatus", raybuilder.ReconcileRayServiceStatus}, + {"WeaviateDatabaseStatus", r.ReconcileWeaviateDatabaseStatus}, + } + + for _, stage := range stages { + err := stage.fn(ctx, p) + cond := metav1.Condition{ + Type: stage.name + "Ready", + Status: metav1.ConditionTrue, + Reason: "Reconciled", + Message: "stage succeeded", + LastTransitionTime: metav1.Now(), + } + if err != nil { + cond.Status = metav1.ConditionFalse + cond.Reason = "Error" + cond.Message = err.Error() + //r.Recorder.Event(p, corev1.EventTypeWarning, stage.name+"Failed", err.Error()) + } else { + //r.Recorder.Event(p, corev1.EventTypeNormal, stage.name+"Succeeded", "stage succeeded") + } + conditions = append(conditions, cond) + if err != nil { + return reconcile.Result{}, err + } + } + + // all done + conditions = append(conditions, metav1.Condition{ + Type: "Ready", + Status: metav1.ConditionTrue, + Reason: "AllReconciled", + Message: "all stages completed successfully", + LastTransitionTime: metav1.Now(), + }) + + return reconcile.Result{}, nil +} diff --git a/controllers/ai_platform/sidecars/builder.go b/controllers/ai_platform/sidecars/builder.go new file mode 100644 index 000000000..d00067971 --- /dev/null +++ b/controllers/ai_platform/sidecars/builder.go @@ -0,0 +1,570 @@ +package sidecars + +import ( + "context" + "encoding/json" + "fmt" + "reflect" + + enterpriseApi "github.com/splunk/splunk-operator/api/v4" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" + syaml "sigs.k8s.io/yaml" +) + +// Sidecar encapsulates RayService generation logic. +type Builder struct { + client.Client + Scheme *runtime.Scheme + Recorder record.EventRecorder + ai *enterpriseApi.AIPlatform +} + +// New returns a new Builder for the given AIPlatform instance. +func New(client client.Client, scheme *runtime.Scheme, recorder record.EventRecorder, ai *enterpriseApi.AIPlatform) *Builder { + return &Builder{ + Client: client, + ai: ai, + Scheme: scheme, + Recorder: recorder, + } +} + +// Reconcile orchestrates individual sidecar reconcilers +func (s *Builder) Reconcile(ctx context.Context, p *enterpriseApi.AIPlatform) error { + if err := s.reconcileFluentBitConfig(ctx, p); err != nil { + return err + } + if err := s.reconcileEnvoyConfig(ctx, p); err != nil { + return err + } + if err := s.reconcileOpenTelemetryCollector(ctx, p); err != nil { + return err + } + if err := s.reconcilePrometheusRule(ctx, p); err != nil { + return err + } + if err := s.reconcilePodMonitor(ctx, p); err != nil { + return err + } + + return nil +} + +// reconcileFluentBitConfig ensures the FluentBit sidecar ConfigMap exists and is up-to-date +func (r *Builder) reconcileFluentBitConfig(ctx context.Context, p *enterpriseApi.AIPlatform) error { + if !p.Spec.Sidecars.FluentBit { + return nil + } + // Retrieve the secret reference from SplunkConfiguration + secret := &corev1.Secret{} + secretKey := types.NamespacedName{ + Name: p.Spec.SplunkConfiguration.SecretRef.Name, + Namespace: p.Namespace, + } + if err := r.Get(ctx, secretKey, secret); err != nil { + return fmt.Errorf("failed to retrieve secret %q: %w", secretKey.Name, err) + } + + // Extract the HEC token from the secret + hecToken, exists := secret.Data["hec_token"] + if !exists { + return fmt.Errorf("hec_token not found in secret %q", secretKey.Name) + } + + // Retrieve the endpoint from SplunkConfiguration + endpoint := r.ai.Spec.SplunkConfiguration.Endpoint + if endpoint == "" { + return fmt.Errorf("endpoint is not specified in SplunkConfiguration") + } + + fluentbitConfig := fmt.Sprintf(renderFluentBitConf(), endpoint, string(hecToken)) + // Update FluentBit configuration with the retrieved values + data := map[string]string{ + "fluent-bit.conf": fluentbitConfig, + "parser.conf": renderParserConf(), + } + + cmName := fmt.Sprintf("%s-fluentbit-config", r.ai.Name) + err := r.createOrUpdateConfigMap(ctx, cmName, data) + if err != nil { + return err + } + + // Validate the ConfigMap before returning + found := &corev1.ConfigMap{} + err = r.Get(ctx, types.NamespacedName{Name: cmName, Namespace: r.ai.Namespace}, found) + if err != nil { + return fmt.Errorf("failed to validate ConfigMap %q: %w", cmName, err) + } + return nil +} + +func (s *Builder) AddFluentBitSidecar(podSpec *corev1.PodSpec) { + // Add FluentBit sidecar if enabled and not already present + if s.ai.Spec.Sidecars.FluentBit { + found := false + for _, container := range podSpec.Containers { + if container.Name == "fluentbit" { + found = true + break + } + } + if !found { + podSpec.Containers = append(podSpec.Containers, corev1.Container{ + Name: "fluentbit", + Image: "fluent/fluent-bit:1.9.6", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + MountPath: "/tmp/ray", + Name: "ray-logs", + }, + { + MountPath: "/fluent-bit/etc/parser.conf", + SubPath: "parser.conf", + Name: "fluentbit-config", + }, + { + MountPath: "/fluent-bit/etc/fluent-bit.conf", + SubPath: "fluent-bit.conf", + Name: "fluentbit-config", + }, + }, + }) + + } + found = false + for _, volume := range podSpec.Volumes { + if volume.Name == "fluentbit-config" { + found = true + break + } + } + if !found { + podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{ + Name: "fluentbit-config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: fmt.Sprintf("%s-fluentbit-config", s.ai.Name), + }, + }, + }, + }) + } + } + +} + +// createOrUpdateConfigMap is a helper to create or patch a ConfigMap owned by the RayService +func (s *Builder) createOrUpdateConfigMap( + ctx context.Context, + name string, + data map[string]string, +) error { + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: s.ai.Namespace, + }, + Data: data, + } + if err := controllerutil.SetControllerReference(s.ai, cm, s.Scheme); err != nil { + return err + } + + found := &corev1.ConfigMap{} + err := s.Get(ctx, types.NamespacedName{Name: name, Namespace: s.ai.Namespace}, found) + if apierrors.IsNotFound(err) { + return s.Create(ctx, cm) + } else if err != nil { + return err + } + + if !reflect.DeepEqual(found.Data, data) { + found.Data = data + return s.Update(ctx, found) + } + return nil +} + +// reconcileEnvoyConfig ensures the Envoy sidecar ConfigMap exists and is up-to-date +func (s *Builder) reconcileEnvoyConfig(ctx context.Context, p *enterpriseApi.AIPlatform) error { + if !p.Spec.Sidecars.Envoy { + return nil + } + + cmName := fmt.Sprintf("%s-envoy-config", p.Name) + data := map[string]string{ + "envoy.yaml": renderEnvoyConf(), + } + return s.createOrUpdateConfigMap(ctx, cmName, data) +} + +// reconcileOpenTelemetryCollector ensures an Otel Collector CR exists in sidecar mode +// using YAML→JSON conversion and CreateOrUpdate for idempotency. +func (s *Builder) reconcileOpenTelemetryCollector(ctx context.Context, p *enterpriseApi.AIPlatform) error { + if !p.Spec.Sidecars.Otel { + return nil + } + + // seed or update the ConfigMap + if err := s.reconcileOtelConfigMap(ctx, p); err != nil { + return fmt.Errorf("reconcile otel configmap: %w", err) + } + + // load raw YAML + cm := &corev1.ConfigMap{} + name := fmt.Sprintf("%s-otel-config", p.Name) + if err := s.Client.Get(ctx, types.NamespacedName{Name: name, Namespace: p.Namespace}, cm); err != nil { + return fmt.Errorf("get ConfigMap %q: %w", name, err) + } + raw := []byte(cm.Data["otel-config.yaml"]) + + // YAML → JSON + jsonBytes, err := syaml.YAMLToJSON(raw) + if err != nil { + return fmt.Errorf("yaml to json conversion: %w", err) + } + + // unmarshal into map[string]interface{} + var cfg map[string]interface{} + if err := json.Unmarshal(jsonBytes, &cfg); err != nil { + return fmt.Errorf("json unmarshal: %w", err) + } + + // construct spec + specMap := map[string]interface{}{ + "mode": "sidecar", + "image": "otel/opentelemetry-collector-contrib:0.122.1", + "env": []map[string]interface{}{ + {"name": "SPLUNK_ACCESS_TOKEN", "valueFrom": map[string]interface{}{"secretKeyRef": map[string]interface{}{"name": s.ai.Spec.SplunkConfiguration.SecretRef.Name, "key": "hec_token"}}}, + {"name": "POD_NAME", "valueFrom": map[string]interface{}{"fieldRef": map[string]interface{}{"fieldPath": "metadata.name"}}}, + {"name": "NAMESPACE", "valueFrom": map[string]interface{}{"fieldRef": map[string]interface{}{"fieldPath": "metadata.namespace"}}}, + {"name": "CLUSTER_NAME", "value": s.ai.Spec.ClusterDomain}, + }, + "config": cfg, + } + + // CreateOrUpdate the Collector + u := &unstructured.Unstructured{} + u.SetGroupVersionKind(schema.GroupVersionKind{Group: "opentelemetry.io", Version: "v1beta1", Kind: "OpenTelemetryCollector"}) + u.SetName(s.ai.Name + "-otel-coll") + u.SetNamespace(s.ai.Namespace) + + _, err = controllerutil.CreateOrUpdate(ctx, s.Client, u, func() error { + u.Object["spec"] = specMap + u.SetLabels(map[string]string{"app": s.ai.Name + "-ray"}) + if len(u.GetFinalizers()) == 0 { + u.SetFinalizers([]string{"opentelemetrycollector.opentelemetry.io/finalizer"}) + } + return controllerutil.SetOwnerReference(s.ai, u, s.Scheme) + }) + if err != nil { + return fmt.Errorf("create/update OpenTelemetryCollector: %w", err) + } + return nil +} + +// reconcileOtelConfigMap bootstraps a `-otel-config` ConfigMap on first create. +// If the user edits the ConfigMap later, those changes are preserved. +func (s *Builder) reconcileOtelConfigMap(ctx context.Context, p *enterpriseApi.AIPlatform) error { + logger := log.FromContext(ctx) + logger.Info("Reconciling OpenTelemetry ConfigMap") + + cmName := fmt.Sprintf("%s-otel-config", p.Name) + cm := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: cmName, Namespace: p.Namespace}} + + _, err := controllerutil.CreateOrUpdate(ctx, s.Client, cm, func() error { + if cm.Data == nil { + cm.Data = map[string]string{} + } + if _, exists := cm.Data["otel-config.yaml"]; !exists { + content := s.renderOtelConf(ctx, p) + yamlBytes, err := syaml.Marshal(content) + if err != nil { + return fmt.Errorf("marshaling otel config: %w", err) + } + cm.Data["otel-config.yaml"] = string(yamlBytes) + } + return controllerutil.SetOwnerReference(p, cm, s.Scheme) + }) + if err != nil { + return fmt.Errorf("create/update otel-config ConfigMap: %w", err) + } + return nil +} + +// renderOtelConf builds the OpenTelemetry Collector config map data. +func (s *Builder) renderOtelConf(ctx context.Context, cr *enterpriseApi.AIPlatform) map[string]interface{} { + secret := &corev1.Secret{} + key := types.NamespacedName{Name: cr.Spec.SplunkConfiguration.SecretRef.Name, Namespace: cr.Namespace} + if err := s.Client.Get(ctx, key, secret); err != nil { + return map[string]interface{}{"error": fmt.Sprintf("loading secret %q: %v", key.Name, err)} + } + + token, ok := secret.Data["hec_token"] + if !ok { + return map[string]interface{}{"error": "hec_token field not found in secret"} + } + + endpoint := fmt.Sprintf("%s/services/collector", cr.Spec.SplunkConfiguration.Endpoint) + return map[string]interface{}{ + "exporters": map[string]interface{}{ + "splunk_hec": map[string]interface{}{ + "token": string(token), + "endpoint": endpoint, + "source": "otel", + "sourcetype": "otel", + "index": "metrics", + "disable_compression": false, + "timeout": "10s", + "tls": map[string]interface{}{"insecure_skip_verify": true}, + "splunk_app_name": "OpenTelemetry-Collector Splunk Exporter", + "splunk_app_version": "v0.0.1", + "heartbeat": map[string]interface{}{"interval": "30s"}, + "telemetry": map[string]interface{}{ + "enabled": true, + "extra_attributes": map[string]interface{}{ + "custom_key": "custom_value", + "dataset_name": "SplunkCloudBeaverStack", + }, + "override_metrics_names": map[string]interface{}{ + "otelcol_exporter_splunkhec_heartbeats_failed": "app_heartbeats_failed_total", + "otelcol_exporter_splunkhec_heartbeats_sent": "app_heartbeats_success_total", + }, + }, + }, + }, + "processors": map[string]interface{}{"batch": map[string]interface{}{}}, + "receivers": map[string]interface{}{ + "prometheus": map[string]interface{}{ + "config": map[string]interface{}{ + "scrape_configs": []map[string]interface{}{ + { + "job_name": fmt.Sprintf("%s-job", cr.Name), + "scrape_interval": "30s", + "metrics_path": "/metrics", + "static_configs": []map[string]interface{}{{ + "targets": []string{"localhost:8080"}, + "labels": map[string]string{"pod": "${POD_NAME}", "namespace": "${NAMESPACE}"}, + }}, + }, + }, + }, + }, + }, + "service": map[string]interface{}{ + "pipelines": map[string]interface{}{ + "metrics": map[string]interface{}{ + "exporters": []string{"splunk_hec"}, + "processors": []string{"batch"}, + "receivers": []string{"prometheus"}, + }, + }, + "telemetry": map[string]interface{}{ + "metrics": map[string]interface{}{ + "readers": []map[string]interface{}{{"pull": map[string]interface{}{"exporter": map[string]interface{}{"prometheus": map[string]interface{}{"host": "0.0.0.0", "port": 8888}}}}}, + }, + }, + }, + } +} + +// renderFluentBitConf generates the FluentBit configuration for the given RayService. +func renderFluentBitConf() string { + return ` + [SERVICE] + Parsers_File /fluent-bit/etc/parser.conf + [INPUT] + Name tail + Path /tmp/ray/session_latest/logs/*, /tmp/ray/session_latest/logs/*/* + Tag ray + Path_Key source_log_file_path + Refresh_Interval 5 + Parser colon_prefix_parser + [FILTER] + Name modify + Match ray + Add application_name NONE + Add deployment_name NONE + [OUTPUT] + Name stdout + Format json_lines + Match * + [OUTPUT] + Name splunk + Match * + Host "%s" + Splunk_Token %s + TLS On + TLS.verify Off +` +} + +// renderParserConf generates the parser configuration for FluentBit. +func renderParserConf() string { + return ` + [PARSER] + Name colon_prefix_parser + Format regex + Regex :actor_name:ServeReplica:(?[a-zA-Z0-9_-]+):(?[a-zA-Z0-9_-]+) + Time_Key time + Time_Format %Y-%m-%dT%H:%M:%S +` +} + +// renderEnvoyConf generates the Envoy configuration for the given AIPlatform. +func renderEnvoyConf() string { + return ` + static_resources: + clusters: + - name: sais_backend + connect_timeout: 0.25s + type: STRICT_DNS + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: sais_backend + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 # Backend service address + port_value: 8080 # Backend service port + + listeners: + - name: listener_0 + address: + socket_address: { address: 0.0.0.0, port_value: 10000 } + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: AUTO + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["*"] + routes: + - match: { prefix: "/" } + route: + cluster: sais_backend + timeout: 0s + - match: { prefix: "/testtenant" } + route: + cluster: sais_backend + timeout: 0s + typed_per_filter_config: + envoy.filters.http.lua: + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute + source_code: + inline_string: | + function envoy_on_request(request_handle) + -- Log request info + local authorizationHeader = request_handle:headers():get("Authorization") + + -- Check if the Authorization header is missing + if authorizationHeader == nil or authorizationHeader == "" then + request_handle:logErr("Authorization header is missing") + -- Send a 400 Bad Request response using correct syntax + request_handle:respond( + { [":status"] = "400", ["content-type"] = "text/plain" }, + "Bad Request: Authorization header is missing" + ) + return + end + + -- Extract pass4SymmKey by removing the 'Splunk ' prefix + local prefix = "Splunk " + if string.sub(authorizationHeader, 1, string.len(prefix)) == prefix then + local pass4SymmKeyHeader = string.sub(authorizationHeader, string.len(prefix) + 1) + local pass4SymmKeyEnv = os.getenv("PASS4SYMMKEY") + + -- Compare the extracted key with the expected key + if pass4SymmKeyHeader ~= pass4SymmKeyEnv then + request_handle:logErr("Invalid pass4SymmKey") + -- Send a 401 Unauthorized response using correct syntax + request_handle:respond( + { [":status"] = "401", ["content-type"] = "text/plain" }, + "Unauthorized: Invalid pass4SymmKey" + ) + return + end + else + request_handle:logErr("Invalid Authorization header format") + -- Send a 400 Bad Request response using correct syntax + request_handle:respond( + { [":status"] = "400", ["content-type"] = "text/plain" }, + "Bad Request: Invalid Authorization header format" + ) + return + end + end + + function envoy_on_response(response_handle) + -- Log when the response is sent back from /testtenant + response_handle:logInfo("Goodbye from /testtenant.") + end + + http_filters: + - name: envoy.filters.http.lua + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua + default_source_code: + inline_string: | + function envoy_on_request(request_handle) + -- Check if the request is from sais_service (you can use custom headers to identify) + local serviceName = request_handle:headers():get("X-Service-Name") + if serviceName == "sais_service" then + -- Add the pass4SymmKey to the Authorization header + local pass4SymmKeyEnv = os.getenv("PASS4SYMMKEY") + if pass4SymmKeyEnv then + local authorizationHeader = "Splunk " .. pass4SymmKeyEnv + request_handle:headers():add("Authorization", authorizationHeader) + request_handle:logInfo("Authorization header added to request from sais_service") + else + request_handle:logErr("pass4SymmKey environment variable is not set") + end + end + end + source_codes: + hello.lua: + inline_string: | + function envoy_on_request(request_handle) + request_handle:logInfo("Hello World.") + end + bye.lua: + inline_string: | + function envoy_on_response(response_handle) + response_handle:logInfo("Bye Bye.") + end + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + +` +} diff --git a/controllers/ai_platform/sidecars/monitoring.go b/controllers/ai_platform/sidecars/monitoring.go new file mode 100644 index 000000000..811da74ca --- /dev/null +++ b/controllers/ai_platform/sidecars/monitoring.go @@ -0,0 +1,184 @@ +package sidecars + +import ( + "context" + "fmt" + + enterpriseApi "github.com/splunk/splunk-operator/api/v4" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +// ReconcilePodMonitor ensures PodMonitors for head and worker pods are created or updated +func (s *Builder) reconcilePodMonitor(ctx context.Context, p *enterpriseApi.AIPlatform) error { + if !p.Spec.Sidecars.PrometheusOperator { + return nil + } + + // Reconcile PodMonitor for head + headMonitor := &unstructured.Unstructured{} + headMonitor.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "monitoring.coreos.com", + Version: "v1", + Kind: "PodMonitor", + }) + headMonitor.SetName(fmt.Sprintf("%s-ray-head-monitor", p.Name)) + headMonitor.SetNamespace(p.Namespace) + + _, err := controllerutil.CreateOrUpdate(ctx, s.Client, headMonitor, func() error { + headMonitor.Object["spec"] = map[string]interface{}{ + "jobLabel": "ray-head", + "namespaceSelector": map[string]interface{}{ + "matchNames": []string{p.Namespace}, + }, + "selector": map[string]interface{}{ + "matchLabels": map[string]string{ + "app": p.Name, + "ray.io/node-type": "head", + }, + }, + "podMetricsEndpoints": []map[string]interface{}{ + { + "port": "metrics", + "relabelings": []map[string]interface{}{ + { + "action": "replace", + "sourceLabels": []string{"__meta_kubernetes_pod_label_ray_io_cluster"}, + "targetLabel": "ray_io_cluster", + }, + }, + }, + { + "port": "as-metrics", + "relabelings": []map[string]interface{}{ + { + "action": "replace", + "sourceLabels": []string{"__meta_kubernetes_pod_label_ray_io_cluster"}, + "targetLabel": "ray_io_cluster", + }, + }, + }, + { + "port": "dash-metrics", + "relabelings": []map[string]interface{}{ + { + "action": "replace", + "sourceLabels": []string{"__meta_kubernetes_pod_label_ray_io_cluster"}, + "targetLabel": "ray_io_cluster", + }, + }, + }, + }, + } + return controllerutil.SetOwnerReference(p, headMonitor, s.Scheme) + }) + if err != nil { + return fmt.Errorf("failed to reconcile head PodMonitor: %w", err) + } + + // Reconcile PodMonitor for workers + workerMonitor := &unstructured.Unstructured{} + workerMonitor.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "monitoring.coreos.com", + Version: "v1", + Kind: "PodMonitor", + }) + workerMonitor.SetName(fmt.Sprintf("%s-ray-workers-monitor", p.Name)) + workerMonitor.SetNamespace(p.Namespace) + + _, err = controllerutil.CreateOrUpdate(ctx, s.Client, workerMonitor, func() error { + workerMonitor.Object["spec"] = map[string]interface{}{ + "jobLabel": "ray-workers", + "namespaceSelector": map[string]interface{}{ + "matchNames": []string{p.Namespace}, + }, + "selector": map[string]interface{}{ + "matchLabels": map[string]string{ + "app": p.Name, + "ray.io/node-type": "worker", + }, + }, + "podMetricsEndpoints": []map[string]interface{}{ + { + "port": "metrics", + "relabelings": []map[string]interface{}{ + { + "sourceLabels": []string{"__meta_kubernetes_pod_label_ray_io_cluster"}, + "targetLabel": "ray_io_cluster", + }, + }, + }, + }, + } + return controllerutil.SetOwnerReference(p, workerMonitor, s.Scheme) + }) + if err != nil { + return fmt.Errorf("failed to reconcile worker PodMonitor: %w", err) + } + + return nil +} + +// reconcilePrometheusRule ensures the PrometheusRule CR is created or updated +func (s *Builder) reconcilePrometheusRule(ctx context.Context, p *enterpriseApi.AIPlatform) error { + promRule := &unstructured.Unstructured{} + promRule.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "monitoring.coreos.com", + Version: "v1", + Kind: "PrometheusRule", + }) + promRule.SetName(fmt.Sprintf("%s-alerts", p.Name)) + promRule.SetNamespace(p.Namespace) + + _, err := controllerutil.CreateOrUpdate(ctx, s.Client, promRule, func() error { + promRule.Object["spec"] = map[string]interface{}{ + "groups": []map[string]interface{}{ + { + "name": fmt.Sprintf("%s.rules", p.Name), + "rules": []map[string]interface{}{ + { + "record": "ray:kube_pod_status_phase", + "expr": fmt.Sprintf( + `kube_pod_status_phase{namespace='%s', pod=~".*worker.*"} * on(pod, namespace) group_left(created_by_name) kube_pod_info{created_by_name=~'%s.*'}`, + p.Namespace, p.Name, + ), + }, + { + "record": "ray:kube_pod_container_status_restarts_total", + "expr": fmt.Sprintf( + `kube_pod_container_status_restarts_total{container=~"autoscaler|istio-proxy|ray-head|ray-worker", namespace='%s'} * on(pod, namespace) group_left(created_by_name) kube_pod_info{created_by_name=~'%s.*'}`, + p.Namespace, p.Name, + ), + }, + { + "record": "ray:ray_actors:sum", + "expr": fmt.Sprintf( + `sum(ray_actors{namespace='%s', app='%s'}) by (Name, State, app, namespace)`, + p.Namespace, p.Name, + ), + }, + { + "alert": "HeadNodeDown", + "expr": fmt.Sprintf( + `up{app='%s', namespace='%s', ray_io_node_type="head"} == 0`, + p.Name, p.Namespace, + ), + "for": "5m", + "annotations": map[string]interface{}{ + "summary": fmt.Sprintf("Head node for RayService %s in %s is down", p.Name, p.Namespace), + "description": fmt.Sprintf("Head node for RayService %s in %s has been down for 5 mins", p.Name, p.Namespace), + }, + "labels": map[string]interface{}{ + "namespace": p.Namespace, + "severity": "critical", + }, + }, + }, + }, + }, + } + return controllerutil.SetOwnerReference(p, promRule, s.Scheme) + }) + return err +} diff --git a/controllers/ai_platform/types.go b/controllers/ai_platform/types.go new file mode 100644 index 000000000..fadc8ded6 --- /dev/null +++ b/controllers/ai_platform/types.go @@ -0,0 +1,54 @@ +package ai_platform + +// types.go +type ServeConfig struct { + ProxyLocation string `json:"proxy_location,omitempty"` + HTTPOptions HTTPOptions `json:"http_options"` + GRPCOptions GRPCOptions `json:"grpc_options"` + LoggingConfig LoggingConfig `json:"logging_config"` + Applications []Application `json:"applications"` +} + +type HTTPOptions struct { + Host string `json:"host,omitempty"` + Port int `json:"port,omitempty"` + RequestTimeoutS int `json:"request_timeout_s,omitempty"` + KeepAliveTimeoutS int `json:"keep_alive_timeout_s"` +} + +type GRPCOptions struct { + Port int `json:"port,omitempty"` + GRPCServicerFunctions []string `json:"grpc_servicer_functions,omitempty"` + RequestTimeoutS int `json:"request_timeout_s,omitempty"` +} + +type LoggingConfig struct { + LogLevel string `json:"log_level,omitempty"` + LogsDir string `json:"logs_dir,omitempty"` + Encoding string `json:"encoding,omitempty"` + EnableAccessLog bool `json:"enable_access_log,omitempty"` +} + +// Application mirrors one rayService application +type Application struct { + Name string `json:"name"` + ImportPath string `json:"import_path,omitempty"` + RoutePrefix string `json:"route_prefix,omitempty"` + Args map[string]interface{} `json:"args,omitempty"` + RuntimeEnv *RuntimeEnv `json:"runtime_env,omitempty"` + + // catch any unmodeled keys: + //Extras map[string]interface{} `json:",inline"` +} + +// RuntimeEnv mirrors the runtime_env field in rayService +type RuntimeEnv struct { + WorkingDir string `json:"working_dir,omitempty"` + EnvVars map[string]string `json:"env_vars,omitempty"` + Pip []string `json:"pip,omitempty"` +} + +// Config is just a thin wrapper around rayService.applications +type Config struct { + RayService ServeConfig `json:"rayService"` +} diff --git a/controllers/ai_platform/validate_defaults.go b/controllers/ai_platform/validate_defaults.go new file mode 100644 index 000000000..8e0c17c83 --- /dev/null +++ b/controllers/ai_platform/validate_defaults.go @@ -0,0 +1,91 @@ +/* +File: controllers/validate.go +*/ +package ai_platform + +import ( + "context" + "fmt" + "os" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + + enterpriseApi "github.com/splunk/splunk-operator/api/v4" +) + +// Validate checks required fields and backfills defaults on the AIPlatform spec. +func (r *AIPlatformReconciler) validate(ctx context.Context, p *enterpriseApi.AIPlatform) error { + // Required volume paths + if p.Spec.AppsVolume.Path == "" { + return fmt.Errorf("AppsVolume.Path is required") + } + if p.Spec.ArtifactsVolume.Path == "" { + return fmt.Errorf("ArtifactsVolume.Path is required") + } + + // Default image registries + if p.Spec.HeadGroupSpec.ImageRegistry == "" { + p.Spec.HeadGroupSpec.ImageRegistry = SetImageRegistry("RELATED_RAY_HEAD_IMAGE", "667741767953.dkr.ecr.us-west-2.amazonaws.com/ml-platform/ray:latest") // FIXME + } + if p.Spec.WorkerGroupSpec.ImageRegistry == "" { + p.Spec.WorkerGroupSpec.ImageRegistry = SetImageRegistry("RELATED_RAY_WORKER_IMAGE", "667741767953.dkr.ecr.us-west-2.amazonaws.com/ml-platform/ray:latest") //FIXME + } + + // Ensure at least one GPUConfig exists + if len(p.Spec.WorkerGroupSpec.GPUConfigs) == 0 { + p.Spec.WorkerGroupSpec.GPUConfigs = []enterpriseApi.GPUConfig{{ + Tier: "default", + MinReplicas: 1, + MaxReplicas: 10, + GPUsPerPod: 4, + }} + } + + if p.Spec.DefaultAcceleratorType == "" { + p.Spec.DefaultAcceleratorType = "L40S" + } + // Per-tier defaults + for i := range p.Spec.WorkerGroupSpec.GPUConfigs { + cfg := &p.Spec.WorkerGroupSpec.GPUConfigs[i] + if cfg.MinReplicas == 0 { + cfg.MinReplicas = 1 + } + if cfg.MaxReplicas == 0 { + cfg.MaxReplicas = 10 + } + if cfg.GPUsPerPod == 0 { + cfg.GPUsPerPod = 4 + } + if p.Spec.DefaultAcceleratorType == "" { + p.Spec.DefaultAcceleratorType = "L40S" + } + + // Default resource requests/limits for workers + if cfg.Resources.Requests == nil { + cfg.Resources.Requests = corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1"), + corev1.ResourceMemory: resource.MustParse("16Gi"), + corev1.ResourceEphemeralStorage: resource.MustParse("50Gi"), + "nvidia.com/gpu": resource.MustParse(fmt.Sprintf("%d", cfg.GPUsPerPod)), + } + } + if cfg.Resources.Limits == nil { + cfg.Resources.Limits = corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("16"), + corev1.ResourceMemory: resource.MustParse("16Gi"), + corev1.ResourceEphemeralStorage: resource.MustParse("50Gi"), + "nvidia.com/gpu": resource.MustParse(fmt.Sprintf("%d", cfg.GPUsPerPod)), + } + } + } + + return nil +} + +func SetImageRegistry(key, defaultValue string) string { + if val := os.Getenv(key); val != "" { + return val + } + return defaultValue +} diff --git a/controllers/ai_platform/weaviate.go b/controllers/ai_platform/weaviate.go new file mode 100644 index 000000000..a3b282aee --- /dev/null +++ b/controllers/ai_platform/weaviate.go @@ -0,0 +1,150 @@ +package ai_platform + +import ( + "context" + "fmt" + "os" + + enterpriseApi "github.com/splunk/splunk-operator/api/v4" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + meta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +func (r *AIPlatformReconciler) ReconcileWeaviateDatabaseStatus(ctx context.Context, p *enterpriseApi.AIPlatform) error { + // 1️⃣ Fetch the up-to-date StatefulSet for Weaviate + sts := &appsv1.StatefulSet{} + key := types.NamespacedName{Namespace: p.Namespace, Name: fmt.Sprintf("%s-weaviate", p.Name)} + if err := r.Get(ctx, key, sts); err != nil { + return err + } + + // 2️⃣ Update the status based on StatefulSet readiness + ready := metav1.ConditionFalse + reason := "WeaviateNotReady" + msg := "Weaviate database is not ready" + if sts.Status.ReadyReplicas == *sts.Spec.Replicas { + ready = metav1.ConditionTrue + reason = "WeaviateReady" + msg = "Weaviate database is ready" + } + + cond := metav1.Condition{ + Type: "WeaviateReady", + Status: ready, + Reason: reason, + Message: msg, + LastTransitionTime: metav1.Now(), + } + meta.SetStatusCondition(&p.Status.Conditions, cond) + + // 3️⃣ Add Weaviate service name to status + p.Status.VectorDbServiceName = fmt.Sprintf("%s-weaviate", p.Name) + + return nil +} + +// ReconcileWeaviateDatabase manages ServiceAccount, StatefulSet, and Service for Weaviate +func (r *AIPlatformReconciler) ReconcileWeaviateDatabase(ctx context.Context, instance *enterpriseApi.AIPlatform) error { + // Resolve Weaviate image from env + weaviateImage := os.Getenv("RELATED_IMAGE_WEAVIATE") + if weaviateImage == "" { + return fmt.Errorf("RELATED_IMAGE_WEAVIATE environment variable is required") + } + + // Derive default values + name := fmt.Sprintf("%s-weaviate", instance.Name) + defaultReplicas := int32(1) + defaultSA := name + + // Apply spec or defaults + spec := instance.Spec.Weaviate + replicas := spec.Replicas + if replicas == nil { + replicas = &defaultReplicas + } + saName := spec.ServiceAccountName + if saName == "" { + saName = defaultSA + } + resources := spec.Resources + + labels := map[string]string{"app": name} + + // 1) Ensure ServiceAccount + sa := &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: saName, + Namespace: instance.Namespace, + }, + } + if err := controllerutil.SetControllerReference(instance, sa, r.Scheme); err != nil { + return err + } + if _, err := controllerutil.CreateOrUpdate(ctx, r.Client, sa, func() error { return nil }); err != nil { + return err + } + + // 2) Ensure StatefulSet + sts := &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: instance.Namespace, + }, + } + if err := controllerutil.SetControllerReference(instance, sts, r.Scheme); err != nil { + return err + } + if _, err := controllerutil.CreateOrUpdate(ctx, r.Client, sts, func() error { + sts.Spec.Selector = &metav1.LabelSelector{MatchLabels: labels} + sts.Spec.ServiceName = name + sts.Spec.Replicas = replicas + sts.Spec.Template.ObjectMeta.Labels = labels + sts.Spec.Template.Spec.ServiceAccountName = saName + sts.Spec.Template.Spec.Affinity = instance.Spec.Weaviate.Affinity + sts.Spec.Template.Spec.Tolerations = instance.Spec.Weaviate.Tolerations + sts.Spec.Template.Spec.NodeSelector = instance.Spec.Weaviate.NodeSelector + + // Container definition + sts.Spec.Template.Spec.Containers = []corev1.Container{{ + Name: "weaviate", + Image: weaviateImage, + Resources: resources, + Ports: []corev1.ContainerPort{{ + Name: "http", + ContainerPort: 8080, + }}, + }} + return nil + }); err != nil { + return err + } + + // 3) Ensure Service + svc := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: instance.Namespace, + }, + } + if err := controllerutil.SetControllerReference(instance, svc, r.Scheme); err != nil { + return err + } + if _, err := controllerutil.CreateOrUpdate(ctx, r.Client, svc, func() error { + svc.Spec.Selector = labels + svc.Spec.Ports = []corev1.ServicePort{{ + Name: "http", + Port: 80, + TargetPort: intstr.FromInt(8080), + }} + return nil + }); err != nil { + return err + } + + return nil +} diff --git a/controllers/aiplatform_controller.go b/controllers/aiplatform_controller.go new file mode 100644 index 000000000..4bf486a35 --- /dev/null +++ b/controllers/aiplatform_controller.go @@ -0,0 +1,72 @@ +// controllers/aiplatform_controller.go + +// SPDX-License-Identifier: Apache-2.0 +package controllers + +import ( + "context" + + enterpriseApi "github.com/splunk/splunk-operator/api/v4" + aiplatform "github.com/splunk/splunk-operator/controllers/ai_platform" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// +kubebuilder:rbac:groups=enterprise.splunk.com,resources=aiplatforms,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=enterprise.splunk.com,resources=aiplatforms/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=enterprise.splunk.com,resources=aiplatforms/finalizers,verbs=update +// +kubebuilder:rbac:groups=cert-manager.io,resources=certificates,verbs=get;list;watch; +// +kubebuilder:rbac:groups=opentelemetry.io,resources=opentelemetrycollectors,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=monitoring.coreos.com,resources=prometheusrules,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=monitoring.coreos.com,resources=podmonitors,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=ray.io,resources=rayservices,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=ray.io,resources=rayclusters,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=ray.io,resources=rayjobs,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=ray.io,resources=jobs,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=core,resources=serviceaccounts,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=jobs,resources=batch,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;deleteR +// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles,verbs=create;get;list;watch;update;patch;delete +// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=create;get;list;watch;update;patch;delete + +// AIPlatformReconciler reconciles a AIPlatform +type AIPlatformReconciler struct { + client.Client + Scheme *runtime.Scheme + Recorder record.EventRecorder +} + +func (r *AIPlatformReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + p := &enterpriseApi.AIPlatform{} + if err := r.Get(ctx, req.NamespacedName, p); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + aiplatform := aiplatform.New(p, r.Client, r.Scheme, r.Recorder) + return aiplatform.Reconcile(ctx, p) +} + +// --- 8️⃣ reconcileStatus: update CR status/conditions --- +func (r *AIPlatformReconciler) reconcileStatus(ctx context.Context, p *enterpriseApi.AIPlatform) error { + p.Status.ObservedGeneration = p.Generation + cond := metav1.Condition{ + Type: "Ready", + Status: metav1.ConditionTrue, + Reason: "Reconciled", + Message: "All resources are up-to-date", + LastTransitionTime: metav1.Now(), + } + p.Status.Conditions = []metav1.Condition{cond} + return r.Status().Update(ctx, p) +} + +// SetupWithManager sets up the controller with the Manager. +func (r *AIPlatformReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&enterpriseApi.AIPlatform{}). + Complete(r) +} diff --git a/controllers/aiservice_controller.go b/controllers/aiservice_controller.go new file mode 100644 index 000000000..27119be3b --- /dev/null +++ b/controllers/aiservice_controller.go @@ -0,0 +1,789 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +... (license text omitted for brevity) ... +*/ + +package controllers + +import ( + "context" + "fmt" + "os" + "reflect" + "sort" + "time" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/pointer" + + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + "github.com/prometheus/client_golang/prometheus" + appsv1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/metrics" + + enterpriseApi "github.com/splunk/splunk-operator/api/v4" + //"github.com/splunk/splunk-operator/controllers/ai_platform/sidecars" +) + +var ( + reconcileHistogram = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "splunk_ai_assistant_reconcile_duration_seconds", + Help: "Duration of reconcile stages", + }, + []string{"stage"}, + ) +) + +func init() { + metrics.Registry.MustRegister(reconcileHistogram) +} + +// +kubebuilder:rbac:groups=enterprise.splunk.com,resources=aiservices,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=enterprise.splunk.com,resources=aiservices/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=enterprise.splunk.com,resources=aiservices/finalizers,verbs=update +// +kubebuilder:rbac:groups=cert-manager.io,resources=certificates,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=opentelemetry.io,resources=opentelemetrycollectors,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=monitoring.coreos.com,resources=prometheusrules,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=monitoring.coreos.com,resources=podmonitors,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=core,resources=serviceaccounts,verbs=get;list;watch;create;update;patch;delete + +// AIServiceReconciler reconciles a AIService object +type AIServiceReconciler struct { + client.Client + Scheme *runtime.Scheme + Recorder record.EventRecorder +} + +// Reconcile runs reconciliation stages for the CR. +func (r *AIServiceReconciler) Reconcile( + ctx context.Context, + req ctrl.Request, +) (ctrl.Result, error) { + log := log.FromContext(ctx) + ai := &enterpriseApi.AIService{} + if err := r.Get(ctx, req.NamespacedName, ai); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + var conditions []metav1.Condition + defer func() { + ai.Status.Conditions = conditions + ai.Status.ObservedGeneration = ai.Generation + _ = r.Status().Update(ctx, ai) + }() + + stages := []struct { + name string + fn func(context.Context, *enterpriseApi.AIService) error + }{ + {"Validate", r.validateAIService}, + {"ServiceAccount", r.reconcileServiceAccount}, + {"FluentBitConfig", r.reconcileFluentBitConfig}, + {"Certificate", r.reconcileCertificate}, + {"PostInstallHook", r.reconcilePostInstallHook}, + {"SAIADeployment", r.reconcileSAIADeployment}, + {"SAIAService", r.reconcileSAIAService}, + {"ServiceMonitor", r.reconcileServiceMonitor}, + } + + for _, stage := range stages { + start := time.Now() + err := stage.fn(ctx, ai) + reconcileHistogram.WithLabelValues(stage.name).Observe( + time.Since(start).Seconds(), + ) + + cond := metav1.Condition{ + Type: stage.name + "Ready", + Status: metav1.ConditionTrue, + Reason: "Reconciled", + Message: "stage succeeded", + LastTransitionTime: metav1.Now(), + } + if err != nil { + cond.Status = metav1.ConditionFalse + cond.Reason = "Error" + cond.Message = err.Error() + //r.Recorder.Event(ai, corev1.EventTypeWarning, stage.name+"Failed", err.Error()) + } else { + // r.Recorder.Event(ai, corev1.EventTypeNormal, stage.name+"Succeeded", "stage succeeded") + } + conditions = append(conditions, cond) + if err != nil { + log.Error(err, "stage failed", "stage", stage.name) + return ctrl.Result{}, err + } + } + + conditions = append(conditions, metav1.Condition{ + Type: "Ready", + Status: metav1.ConditionTrue, + Reason: "AllReconciled", + Message: "all stages completed successfully", + LastTransitionTime: metav1.Now(), + }) + + return ctrl.Result{}, nil +} + +// SetupWithManager registers the controller and owned resources. +func (r *AIServiceReconciler) SetupWithManager( + mgr ctrl.Manager, +) error { + return ctrl.NewControllerManagedBy(mgr). + For(&enterpriseApi.AIService{}). + Owns(&corev1.ServiceAccount{}). + Owns(&certmanagerv1.Certificate{}). + Owns(&batchv1.Job{}). + Owns(&appsv1.Deployment{}). + Owns(&corev1.Service{}). + Owns(&monitoringv1.ServiceMonitor{}). + Complete(r) +} + +// validateAIService ensures required fields are set and defaults. +func (r *AIServiceReconciler) validateAIService( + ctx context.Context, + ai *enterpriseApi.AIService, +) error { + if os.Getenv("RELATED_IMAGE_POST_INSTALL_HOOK") == "" { + return fmt.Errorf("RELATED_IMAGE_POST_INSTALL_HOOK must be set") + } + // Populate URLs from AIPlatformRef if provided + if ai.Spec.AIPlatformRef.Name != "" { + plat := &enterpriseApi.AIPlatform{} + if err := r.Get( + ctx, + client.ObjectKey{Namespace: ai.Namespace, Name: ai.Spec.AIPlatformRef.Name}, + plat, + ); err != nil { + return fmt.Errorf("fetching AIPlatform: %w", err) + } + ai.Spec.AIPlatformUrl = fmt.Sprintf("%s.%s.svc.%s:8000", plat.Status.RayServiceName, ai.Spec.AIPlatformRef.Namespace, "cluster.local") + ai.Spec.VectorDbUrl = fmt.Sprintf("%s.%s.svc.%s", plat.Status.VectorDbServiceName, ai.Spec.AIPlatformRef.Namespace, "cluster.local") + } + if ai.Spec.AIPlatformRef.Name == "" && ai.Spec.AIPlatformUrl == "" { + return fmt.Errorf( + "either AIPlatformRef.Name or AIPlatformUrl must be set", + ) + } + if ai.Spec.AIPlatformUrl == "" && ai.Spec.VectorDbUrl == "" { + return fmt.Errorf( + "either AIPlatformUrl or VectorDbUrl must be set", + ) + } + // Default resources + if ai.Spec.Resources.Requests == nil { + ai.Spec.Resources.Requests = corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("500m"), + corev1.ResourceMemory: resource.MustParse("1Gi"), + } + } + if ai.Spec.Resources.Limits == nil { + ai.Spec.Resources.Limits = corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1"), + corev1.ResourceMemory: resource.MustParse("2Gi"), + } + } + if ai.Spec.TaskVolume.Path == "" { + return fmt.Errorf("task volume path must be set") + } + if ai.Spec.Replicas == 0 { + ai.Spec.Replicas = 1 + } + return nil +} + +// reconcileServiceAccount creates or reuses a ServiceAccount. +func (r *AIServiceReconciler) reconcileServiceAccount( + ctx context.Context, + ai *enterpriseApi.AIService, +) error { + if ai.Spec.ServiceAccountName == "" { + + ai.Spec.ServiceAccountName = ai.Name + "-sa" + if err := r.Update(ctx, ai); err != nil { + return fmt.Errorf("updating SA name in spec: %w", err) + } + sa := &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: ai.Spec.ServiceAccountName, + Namespace: ai.Namespace, + }, + } + if err := controllerutil.SetControllerReference(ai, sa, r.Scheme); err != nil { + return fmt.Errorf("ownerref on SA: %w", err) + } + if _, err := controllerutil.CreateOrUpdate(ctx, r.Client, sa, func() error { + return nil + }); err != nil { + return fmt.Errorf("create/update SA: %w", err) + } + } + return nil +} + +// reconcileCertificate manages cert-manager Certificate for mTLS. +func (r *AIServiceReconciler) reconcileCertificate( + ctx context.Context, + ai *enterpriseApi.AIService, +) error { + if !ai.Spec.MTLS.Enabled || ai.Spec.MTLS.Termination != "operator" { + return nil + } + cert := &certmanagerv1.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: ai.Name + "-tls", + Namespace: ai.Namespace, + }, + Spec: certmanagerv1.CertificateSpec{ + SecretName: ai.Spec.MTLS.SecretName, + IssuerRef: ai.Spec.MTLS.IssuerRef, + DNSNames: ai.Spec.MTLS.DNSNames, + Usages: []certmanagerv1.KeyUsage{ + certmanagerv1.UsageServerAuth, + certmanagerv1.UsageClientAuth, + }, + }, + } + if err := controllerutil.SetControllerReference(ai, cert, r.Scheme); err != nil { + return fmt.Errorf("ownerref on Certificate: %w", err) + } + if _, err := controllerutil.CreateOrUpdate(ctx, r.Client, cert, func() error { + return nil + }); err != nil { + return fmt.Errorf("create/update Certificate: %w", err) + } + // Wait until Certificate is Ready + for _, cond := range cert.Status.Conditions { + if cond.Type == certmanagerv1.CertificateConditionReady && cond.Status == cmmeta.ConditionTrue { + return nil + } + } + return fmt.Errorf("waiting for Certificate %q to become Ready", cert.Name) +} + +// reconcilePostInstallHook creates and watches the schema setup Job. +func (r *AIServiceReconciler) reconcilePostInstallHook( + ctx context.Context, + ai *enterpriseApi.AIService, +) error { + hookImage := os.Getenv("RELATED_IMAGE_POST_INSTALL_HOOK") + if ai.Spec.VectorDbUrl == "" { + return nil + } + if ai.Status.SchemaJobId != "" { + job := &batchv1.Job{} + err := r.Get( + ctx, + client.ObjectKey{Namespace: ai.Namespace, Name: ai.Status.SchemaJobId}, + job, + ) + if apierrors.IsNotFound(err) { + ai.Status.SchemaJobId = "" + } else if err != nil { + return fmt.Errorf("fetching Job: %w", err) + } else { + for _, c := range job.Status.Conditions { + if c.Type == batchv1.JobComplete && c.Status == corev1.ConditionTrue { + return nil + } + if c.Type == batchv1.JobFailed && c.Status == corev1.ConditionTrue { + return fmt.Errorf("Job %q failed", job.Name) + } + } + return fmt.Errorf("job %q is still running", job.Name) + } + } + uri := fmt.Sprintf("http://%s:80", ai.Spec.VectorDbUrl) + job := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: ai.Name + "-vector-db-setup-posthook", + Namespace: ai.Namespace, + }, + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + RestartPolicy: corev1.RestartPolicyNever, + Containers: []corev1.Container{ + { + Name: "vector-db-setup-container", + Image: hookImage, + ImagePullPolicy: corev1.PullAlways, + Env: []corev1.EnvVar{ + {Name: "VECTOR_DB_URL", Value: uri}, + }, + }, + }, + Tolerations: ai.Spec.Tolerations, + Affinity: &ai.Spec.Affinity, + }, + }, + }, + } + if err := controllerutil.SetControllerReference(ai, job, r.Scheme); err != nil { + return fmt.Errorf("ownerref on Job: %w", err) + } + if _, err := controllerutil.CreateOrUpdate(ctx, r.Client, job, func() error { return nil }); err != nil { + return fmt.Errorf("create/update Job: %w", err) + } + ai.Status.SchemaJobId = job.Name + return fmt.Errorf("created Job %q, waiting for completion", job.Name) +} + +// reconcileSAIADeployment ensures the main Deployment exists and is configured. +func (r *AIServiceReconciler) reconcileSAIADeployment( + ctx context.Context, + ai *enterpriseApi.AIService, +) error { + // Build volumes + volumes := []corev1.Volume{ + { + Name: "config-volume", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{Name: "features-config"}, + Optional: pointer.BoolPtr(true), + }, + }, + }, + } + // Build ports and env + ports := []corev1.ContainerPort{ + {Name: "http", ContainerPort: 8080}, + {Name: "metrics", ContainerPort: 8088}, + } + mounts := []corev1.VolumeMount{ + {Name: "config-volume", MountPath: "/etc/config"}, + } + env := []corev1.EnvVar{{Name: "LOG_LEVEL", Value: "info"}} + if ai.Spec.MTLS.Enabled && ai.Spec.MTLS.Termination == "operator" { + volumes = append(volumes, + corev1.Volume{ + Name: "tls", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{SecretName: ai.Spec.MTLS.SecretName}, + }, + }, + ) + mounts = append(mounts, corev1.VolumeMount{Name: "tls", MountPath: "/etc/tls", ReadOnly: true}) + env = append(env, + corev1.EnvVar{Name: "TLS_CERT_FILE", Value: "/etc/tls/tls.crt"}, + corev1.EnvVar{Name: "TLS_KEY_FILE", Value: "/etc/tls/tls.key"}, + ) + ports = append(ports, corev1.ContainerPort{Name: "https", ContainerPort: 8443}) + } else { + env = append(env, corev1.EnvVar{Name: "TLS_DISABLED", Value: "true"}) + } + + // Add required env variables + extraEnv := []corev1.EnvVar{ + {Name: "IAC_URL", Value: "test.iac.url"}, //FIXME remove this + {Name: "API_GATEWAY_HOST", Value: "test.api.gateway.host"}, //FIXME remove this + {Name: "SCPAUTH_SECRET_PATH", Value: "stest-secret-path"}, //FIXME remove this + {Name: "AUTH_PROVIDER", Value: "scp"}, // FIXME remove this + {Name: "ENABLE_AUTHZ", Value: "false"}, //FIXME remove this + {Name: "FEATURE_CONFIG_FILE_LOCATION", Value: "/etc/config/features_config.yaml"}, + {Name: "PLATFORM_URL", Value: ai.Spec.AIPlatformUrl}, + {Name: "PLATFORM_VERSION", Value: "0.3.0"}, // TODO : make this configurable + {Name: "SAIA_API_VERSION", Value: "0.3.1"}, // TODO : make this configurable + {Name: "TASK_RUNNER_BACKUP_ENABLED", Value: "false"}, // TODO : make this configurable + {Name: "TELEMETRY_ENV", Value: "prod"}, // TODO: make this configurable + {Name: "TELEMETRY_REGION", Value: "region-us-west-2"}, // TODO: make this configurable + {Name: "TELEMETRY_TENANT", Value: "test"}, // TODO: make this configurable + {Name: "TELEMETRY_URL", Value: "https://telemetry-splkmobile.dataeng.splunk.com/2.0/events"}, // TODO: make this configurable + {Name: "WEAVIATE_API_URL", Value: ai.Spec.VectorDbUrl}, + {Name: "STORAGE_URL", Value: ai.Spec.TaskVolume.Path}, // TODO : make this configurable , not sure why we need this + {Name: "S3_BUCKET", Value: ai.Spec.TaskVolume.Path}, // TODO : make this configurable , not sure why we need this + } + env = append(env, extraEnv...) + // Sort env variables by Name for deterministic ordering + sort.Slice(env, func(i, j int) bool { + return env[i].Name < env[j].Name + }) + + deployment := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: ai.Name + "-saia-deployment", + Namespace: ai.Namespace, + Labels: map[string]string{ + "app": ai.Name, + "component": ai.Name, + "area": "ml", + "team": "ml", + }, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: &ai.Spec.Replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": ai.Name, "component": ai.Name}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"app": ai.Name, "component": ai.Name}, + Annotations: map[string]string{ + "prometheus.io/port": "8088", //FIXME only enable when metrics are enabled + "prometheus.io/path": "/metrics", + "prometheus.io/scheme": "http", + }, + }, + Spec: corev1.PodSpec{ + ServiceAccountName: ai.Spec.ServiceAccountName, + Containers: []corev1.Container{{ + Name: ai.Name, + Image: os.Getenv("RELATED_IMAGE_SAIA_API"), + ImagePullPolicy: corev1.PullAlways, + Ports: ports, + VolumeMounts: mounts, + Resources: ai.Spec.Resources, + Env: env, + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{Path: "/health", Port: intstr.FromInt(8080)}, + }, + PeriodSeconds: 30, + FailureThreshold: 5, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{Path: "/health", Port: intstr.FromInt(8080)}, + }, + PeriodSeconds: 30, + FailureThreshold: 5, + }, + StartupProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{Path: "/health", Port: intstr.FromInt(8080)}, + }, + InitialDelaySeconds: 10, + PeriodSeconds: 30, + FailureThreshold: 5, + }, + }}, + Volumes: volumes, + Affinity: &ai.Spec.Affinity, + Tolerations: ai.Spec.Tolerations, + }, + }, + }, + } + // Merge ai.Labels into deployment.ObjectMeta.Labels + for k, v := range ai.Labels { + deployment.ObjectMeta.Labels[k] = v + } + for k, v := range ai.Annotations { + if k == "kubectl.kubernetes.io/last-applied-configuration" { + continue + } // Ignore last-applied-configuration annotation + if k == "kubectl.kubernetes.io/restartedAt" { + continue + } // Ignore restartedAt annotation + deployment.ObjectMeta.Annotations[k] = v + } + // FIXME need to find better way to add sidecars + // sidecars arguments need to be changed to take abstract class + //sidecars := sidecars.New(r.Client, r.Scheme, r.Recorder, ai) + r.AddFluentBitSidecar(&deployment.Spec.Template.Spec, ai) + + if err := controllerutil.SetControllerReference(ai, deployment, r.Scheme); err != nil { + return fmt.Errorf("ownerref on Deployment: %w", err) + } + if _, err := controllerutil.CreateOrUpdate(ctx, r.Client, deployment, func() error { return nil }); err != nil { + return fmt.Errorf("create/update Deployment: %w", err) + } + return nil +} + +// reconcileSAIAService ensures the Service for SAIA is created/updated. // remove me +func (r *AIServiceReconciler) reconcileSAIAService( + ctx context.Context, + ai *enterpriseApi.AIService, +) error { + ports := []corev1.ServicePort{ + {Name: "http", Port: 8080, TargetPort: intstr.FromInt(8080)}, + {Name: "metrics", Port: 8088, TargetPort: intstr.FromInt(8088)}, + } + if ai.Spec.MTLS.Enabled && ai.Spec.MTLS.Termination == "operator" { + ports = append(ports, corev1.ServicePort{ + Name: "https", Port: 8443, TargetPort: intstr.FromInt(8443), + }) + } + svc := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: ai.Name + "-saia-service", + Namespace: ai.Namespace, + Labels: map[string]string{"app": ai.Name}, + }, + Spec: corev1.ServiceSpec{ + Selector: map[string]string{"app": ai.Name, "component": ai.Name}, + Ports: ports, + Type: corev1.ServiceTypeClusterIP, + }, + } + for k, v := range ai.Labels { + svc.ObjectMeta.Labels[k] = v + } + for k, v := range ai.Annotations { + if k == "kubectl.kubernetes.io/last-applied-configuration" { + continue + } // Ignore last-applied-configuration annotation + if k == "kubectl.kubernetes.io/restartedAt" { + continue + } // Ignore restartedAt annotation + svc.ObjectMeta.Annotations[k] = v + } + + if ai.Spec.ServiceTemplate.Spec.Type == corev1.ServiceTypeLoadBalancer { + svc.Spec.Type = corev1.ServiceTypeLoadBalancer + } else if ai.Spec.ServiceTemplate.Spec.Type == corev1.ServiceTypeNodePort { + svc.Spec.Type = corev1.ServiceTypeNodePort + // If NodePort values are specified, set them + for i, port := range svc.Spec.Ports { + for _, tplPort := range ai.Spec.ServiceTemplate.Spec.Ports { + if port.Name == tplPort.Name && tplPort.NodePort != 0 { + svc.Spec.Ports[i].NodePort = tplPort.NodePort + } + } + } + } else { + svc.Spec.Type = corev1.ServiceTypeClusterIP + } + + if err := controllerutil.SetControllerReference(ai, svc, r.Scheme); err != nil { + return fmt.Errorf("ownerref on Service: %w", err) + } + if _, err := controllerutil.CreateOrUpdate(ctx, r.Client, svc, func() error { return nil }); err != nil { + return fmt.Errorf("create/update Service: %w", err) + } + return nil +} + +// reconcileServiceMonitor creates a Prometheus ServiceMonitor if metrics are enabled. +func (r *AIServiceReconciler) reconcileServiceMonitor( + ctx context.Context, + ai *enterpriseApi.AIService, +) error { + if !ai.Spec.Metrics.Enabled { + return nil + } + sm := &monitoringv1.ServiceMonitor{ + ObjectMeta: metav1.ObjectMeta{Name: ai.Name + "-metrics", Namespace: ai.Namespace}, + Spec: monitoringv1.ServiceMonitorSpec{ + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": ai.Name, "component": ai.Name}, + }, + Endpoints: []monitoringv1.Endpoint{ + {Port: "metrics", Path: ai.Spec.Metrics.Path, Scheme: "http"}, + }, + }, + } + if err := controllerutil.SetControllerReference(ai, sm, r.Scheme); err != nil { + return err + } + _, err := controllerutil.CreateOrUpdate(ctx, r.Client, sm, func() error { return nil }) + return err +} + +// reconcileFluentBitConfig ensures the FluentBit sidecar ConfigMap exists and is up-to-date // remove me +func (r *AIServiceReconciler) reconcileFluentBitConfig(ctx context.Context, p *enterpriseApi.AIService) error { + // Retrieve the secret reference from SplunkConfiguration + secret := &corev1.Secret{} + secretKey := types.NamespacedName{ + Name: p.Spec.SplunkConfiguration.SecretRef.Name, + Namespace: p.Namespace, + } + if err := r.Get(ctx, secretKey, secret); err != nil { + return fmt.Errorf("failed to retrieve secret %q: %w", secretKey.Name, err) + } + + // Extract the HEC token from the secret + hecToken, exists := secret.Data["hec_token"] + if !exists { + return fmt.Errorf("hec_token not found in secret %q", secretKey.Name) + } + + // Retrieve the endpoint from SplunkConfiguration + endpoint := p.Spec.SplunkConfiguration.Endpoint + if endpoint == "" { + return fmt.Errorf("endpoint is not specified in SplunkConfiguration") + } + + fluentbitConfig := fmt.Sprintf(renderFluentBitConf(), endpoint, string(hecToken)) + // Update FluentBit configuration with the retrieved values + data := map[string]string{ + "fluent-bit.conf": fluentbitConfig, + "parser.conf": renderParserConf(), + } + + cmName := fmt.Sprintf("%s-fluentbit-config", p.Name) + err := r.createOrUpdateConfigMap(ctx, cmName, data, p) + if err != nil { + return err + } + + // Validate the ConfigMap before returning + found := &corev1.ConfigMap{} + err = r.Get(ctx, types.NamespacedName{Name: cmName, Namespace: p.Namespace}, found) + if err != nil { + return fmt.Errorf("failed to validate ConfigMap %q: %w", cmName, err) + } + return nil +} + +func (r *AIServiceReconciler) AddFluentBitSidecar(podSpec *corev1.PodSpec, ai *enterpriseApi.AIService) { + // Add FluentBit sidecar if enabled and not already present + + found := false + for _, container := range podSpec.Containers { + if container.Name == "fluentbit" { + found = true + break + } + } + if !found { + podSpec.Containers = append(podSpec.Containers, corev1.Container{ + Name: "fluentbit", + Image: "fluent/fluent-bit:1.9.6", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + MountPath: "/fluent-bit/etc/parser.conf", + SubPath: "parser.conf", + Name: "fluentbit-config", + }, + { + MountPath: "/fluent-bit/etc/fluent-bit.conf", + SubPath: "fluent-bit.conf", + Name: "fluentbit-config", + }, + }, + }) + + } + found = false + for _, volume := range podSpec.Volumes { + if volume.Name == "fluentbit-config" { + found = true + break + } + } + if !found { + podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{ + Name: "fluentbit-config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: fmt.Sprintf("%s-fluentbit-config", ai.Name), + }, + }, + }, + }) + } +} + +// createOrUpdateConfigMap is a helper to create or patch a ConfigMap // remove me +func (r *AIServiceReconciler) createOrUpdateConfigMap( + ctx context.Context, + name string, + data map[string]string, + ai *enterpriseApi.AIService, +) error { + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ai.Namespace, + }, + Data: data, + } + if err := controllerutil.SetControllerReference(ai, cm, r.Scheme); err != nil { + return err + } + + found := &corev1.ConfigMap{} + err := r.Get(ctx, types.NamespacedName{Name: name, Namespace: ai.Namespace}, found) + if apierrors.IsNotFound(err) { + return r.Create(ctx, cm) + } else if err != nil { + return err + } + + if !reflect.DeepEqual(found.Data, data) { + found.Data = data + return r.Update(ctx, found) + } + return nil +} + +// renderFluentBitConf generates the FluentBit configuration for the given RayService. +func renderFluentBitConf() string { + return ` + [SERVICE] + Parsers_File /fluent-bit/etc/parser.conf + [INPUT] + Name tail + Path /tmp/ray/session_latest/logs/*, /tmp/ray/session_latest/logs/*/* + Tag ray + Path_Key source_log_file_path + Refresh_Interval 5 + Parser colon_prefix_parser + [FILTER] + Name modify + Match ray + Add application_name NONE + Add deployment_name NONE + [OUTPUT] + Name stdout + Format json_lines + Match * + [OUTPUT] + Name splunk + Match * + Host "%s" + Splunk_Token %s + TLS On + TLS.verify Off +` +} + +// renderParserConf generates the parser configuration for FluentBit. +func renderParserConf() string { + return ` + [PARSER] + Name colon_prefix_parser + Format regex + Regex :actor_name:ServeReplica:(?[a-zA-Z0-9_-]+):(?[a-zA-Z0-9_-]+) + Time_Key time + Time_Format %Y-%m-%dT%H:%M:%S +` +} diff --git a/controllers/storage/aws.go b/controllers/storage/aws.go new file mode 100644 index 000000000..73d75e044 --- /dev/null +++ b/controllers/storage/aws.go @@ -0,0 +1,135 @@ +// controllers/storage/s3.go +package storage + +import ( + "context" + "fmt" + "path" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" + ai "github.com/splunk/splunk-operator/api/v4" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// s3Client implements StorageClient for AWS S3. +type s3Client struct { + cli *s3.S3 + bucket string + prefix string +} + +func NewS3Client( + k8sClient client.Client, + namespace, bucket, prefix string, + vs ai.AiVolumeSpec, +) (StorageClient, error) { + awsCfg := &aws.Config{ + Endpoint: aws.String(vs.Endpoint), + Region: aws.String(vs.Region), + S3ForcePathStyle: aws.Bool(true), + } + + if vs.SecretRef != "" { + // static creds from k8s secret + secret := &corev1.Secret{} + if err := k8sClient.Get(context.TODO(), + client.ObjectKey{Namespace: namespace, Name: vs.SecretRef}, + secret, + ); err != nil { + return nil, err + } + awsCfg.Credentials = credentials.NewStaticCredentials( + string(secret.Data["s3_access_key"]), + string(secret.Data["s3_secret_key"]), + "", + ) + } + // if no SecretRef, AWS SDK will pick up IRSA / env / shared‐config by itself + + sess, err := session.NewSession(awsCfg) + if err != nil { + return nil, err + } + return &s3Client{cli: s3.New(sess), bucket: bucket, prefix: prefix}, nil +} + +// ListObjects returns all object keys under the configured prefix, across all pages. +func (c *s3Client) ListObjects(ctx context.Context) ([]string, error) { + input := &s3.ListObjectsV2Input{ + Bucket: aws.String(c.bucket), + Prefix: aws.String(c.prefix), + // Note: no Delimiter, so you get every key under that prefix + } + + var keys []string + // This will call your function once per page of up to 1000 objects + err := c.cli.ListObjectsV2PagesWithContext(ctx, input, + func(page *s3.ListObjectsV2Output, lastPage bool) bool { + for _, obj := range page.Contents { + keys = append(keys, aws.StringValue(obj.Key)) + } + // return true to keep paginating + return true + }, + ) + if err != nil { + return nil, err + } + return keys, nil +} + +func (c *s3Client) Exists(ctx context.Context, key string) (bool, error) { + filePath := fmt.Sprintf("%s/%s", c.prefix, key) + _, err := c.cli.HeadObjectWithContext(ctx, &s3.HeadObjectInput{ + Bucket: aws.String(c.bucket), + Key: aws.String(filePath), + }) + if err != nil { + // 404 → object missing + if aerr, ok := err.(awserr.RequestFailure); ok && aerr.StatusCode() == 404 { + return false, nil + } + return false, err + } + return true, nil +} + +// BuildLoaderBlock returns the `model_loader` YAML snippet for this URI. +func (c *s3Client) BuildLoaderBlock(uri string) string { + // uri is "s3://bucket/prefix/.../file" + trim := fmt.Sprintf("s3://%s/", c.bucket) + p := strings.TrimPrefix(uri, trim) + dir := path.Dir(p) + return fmt.Sprintf(` s3_artifact: + bucket: %s + s3_key_prefix: %s +`, c.bucket, dir) +} + +// BuildWorkingDir returns the working_dir URI for the application ZIP. +func (c *s3Client) BuildWorkingDir(modelName string) string { + if c.prefix == "" { + return fmt.Sprintf("s3://%s/%s", c.bucket, modelName) + } + return fmt.Sprintf("s3://%s/%s/%s", c.bucket, c.prefix, modelName) +} + +// BuildArtifactURI builds a “s3://bucket[/prefix]/key” URI. +func (c *s3Client) BuildArtifactURI(key string) string { + // strip any leading slash on key + k := strings.TrimPrefix(key, "/") + if c.prefix != "" { + return fmt.Sprintf("s3://%s/%s/%s", c.bucket, c.prefix, k) + } + return fmt.Sprintf("s3://%s/%s", c.bucket, k) +} + +func (c *s3Client) GetProvider() string { return "s3" } +func (c *s3Client) GetBucket() string { return c.bucket } +func (c *s3Client) GetPrefix() string { return c.prefix } diff --git a/controllers/storage/azure.go b/controllers/storage/azure.go new file mode 100644 index 000000000..c2d6c33d8 --- /dev/null +++ b/controllers/storage/azure.go @@ -0,0 +1,148 @@ +// controllers/storage/azure.go +package storage + +import ( + "context" + "errors" + "fmt" + "path" + "strings" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" + ai "github.com/splunk/splunk-operator/api/v4" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type azureClient struct { + cli *azblob.Client + endpoint string + container string + prefix string +} + +// NewAzureClient optionally reads client ID/secret/tenant from SecretRef. +// If SecretRef is empty, it uses DefaultAzureCredential (MSI/pod-identity). +func NewAzureClient( + k8sClient client.Client, + namespace, container, prefix string, + vs ai.AiVolumeSpec, +) (StorageClient, error) { + var cred azcore.TokenCredential + var err error + + if vs.SecretRef != "" { + secret := &corev1.Secret{} + if err := k8sClient.Get(context.TODO(), + client.ObjectKey{Namespace: namespace, Name: vs.SecretRef}, + secret, + ); err != nil { + return nil, fmt.Errorf("fetch Azure secret: %w", err) + } + + tenantID := string(secret.Data["azure_tenant_id"]) + clientID := string(secret.Data["azure_client_id"]) + clientSecret := string(secret.Data["azure_client_secret"]) + cred, err = azidentity.NewClientSecretCredential(tenantID, clientID, clientSecret, nil) + if err != nil { + return nil, fmt.Errorf("client-secret credential: %w", err) + } + } else { + cred, err = azidentity.NewDefaultAzureCredential(nil) + if err != nil { + return nil, fmt.Errorf("default azure credential: %w", err) + } + } + + cli, err := azblob.NewClient(vs.Endpoint, cred, nil) + if err != nil { + return nil, fmt.Errorf("new Azure blob client: %w", err) + } + return &azureClient{ + cli: cli, + endpoint: vs.Endpoint, + container: container, + prefix: prefix, + }, nil +} + +func (c *azureClient) ListObjects(ctx context.Context) ([]string, error) { + pager := c.cli.NewListBlobsFlatPager(c.container, &azblob.ListBlobsFlatOptions{Prefix: &c.prefix}) + var keys []string + for pager.More() { + page, err := pager.NextPage(ctx) + if err != nil { + return nil, err + } + for _, b := range page.Segment.BlobItems { + keys = append(keys, *b.Name) + } + } + return keys, nil +} + +func (c *azureClient) BuildLoaderBlock(uri string) string { + // uri e.g. "https://account.blob.core.windows.net/container/prefix/file.ext" + trim := fmt.Sprintf("%s/%s/", c.endpoint, c.container) + p := strings.TrimPrefix(uri, trim) + dir := path.Dir(p) + return fmt.Sprintf(` azure_blob: + url: %s + container: %s + blob_prefix: %s +`, c.endpoint, c.container, dir) +} + +func (c *azureClient) BuildWorkingDir(modelName string) string { + // assemble https://endpoint/container/prefix/applications/.zip + base := fmt.Sprintf("%s/%s", c.endpoint, c.container) + if c.prefix == "" { + return fmt.Sprintf("%s/%s", base, modelName) + } + return fmt.Sprintf("%s/%s/%s", base, c.prefix, modelName) +} + +// BuildArtifactURI constructs the full URI for an object key in Azure Blob Storage: +// e.g. https://account.blob.core.windows.net/container/prefix/key +func (c *azureClient) BuildArtifactURI(key string) string { + k := strings.TrimPrefix(key, "/") + if c.prefix != "" { + return fmt.Sprintf("%s/%s/%s/%s", c.endpoint, c.container, c.prefix, k) + } + return fmt.Sprintf("%s/%s/%s", c.endpoint, c.container, k) +} + +func (c *azureClient) Exists(ctx context.Context, blobName string) (bool, error) { + // If you have a prefix, stitch it back onto the blob name: + key := blobName + if c.prefix != "" { + key = path.Join(c.prefix, blobName) + } + + // 1. Turn your azblob.Client into a service client + svc := c.cli.ServiceClient() // (c) ServiceClient() :contentReference[oaicite:0]{index=0} + + // 2. From the service, grab the container client + containerClient := svc.NewContainerClient(c.container) // NewContainerClient :contentReference[oaicite:1]{index=1} + + // 3. From the container, grab a blob‐level client + blobClient := containerClient.NewBlobClient(key) // NewBlobClient :contentReference[oaicite:2]{index=2} + + // 4. Head the blob + _, err := blobClient.GetProperties(ctx, nil) // GetProperties :contentReference[oaicite:3]{index=3} + if err != nil { + var respErr *azcore.ResponseError + if errors.As(err, &respErr) && respErr.StatusCode == 404 { + // not found + return false, nil + } + return false, fmt.Errorf("blob GetProperties: %w", err) + } + return true, nil +} + +func (c *azureClient) GetProvider() string { return "azure" } +func (c *azureClient) GetBucket() string { return c.container } +func (c *azureClient) GetPrefix() string { return c.prefix } diff --git a/controllers/storage/gcs.go b/controllers/storage/gcs.go new file mode 100644 index 000000000..9791dffa8 --- /dev/null +++ b/controllers/storage/gcs.go @@ -0,0 +1,113 @@ +// controllers/storage/gcs.go +package storage + +import ( + "context" + "errors" + "fmt" + "path" + "strings" + + "google.golang.org/api/iterator" + + "cloud.google.com/go/storage" + ai "github.com/splunk/splunk-operator/api/v4" + "google.golang.org/api/option" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type gcsClient struct { + cli *storage.Client + bucket string + prefix string +} + +func NewGCSClient( + k8sClient client.Client, + namespace, bucket, prefix string, + vs ai.AiVolumeSpec, +) (StorageClient, error) { + opts := []option.ClientOption{} + + if vs.SecretRef != "" { + secret := &corev1.Secret{} + if err := k8sClient.Get(context.TODO(), + client.ObjectKey{Namespace: namespace, Name: vs.SecretRef}, + secret, + ); err != nil { + return nil, fmt.Errorf("fetch GCP secret: %w", err) + } + + keyJSON, ok := secret.Data["service_account.json"] + if !ok { + return nil, fmt.Errorf("secret %q missing key 'service_account.json'", vs.SecretRef) + } + opts = append(opts, option.WithCredentialsJSON(keyJSON)) + } + + cli, err := storage.NewClient(context.Background(), opts...) + if err != nil { + return nil, fmt.Errorf("new GCS client: %w", err) + } + return &gcsClient{cli: cli, bucket: bucket, prefix: prefix}, nil +} + +func (c *gcsClient) ListObjects(ctx context.Context) ([]string, error) { + it := c.cli.Bucket(c.bucket).Objects(ctx, &storage.Query{Prefix: c.prefix}) + var keys []string + for { + obj, err := it.Next() + if err == iterator.Done { + break + } + if err != nil { + return nil, err + } + keys = append(keys, obj.Name) + } + return keys, nil +} + +func (c *gcsClient) BuildLoaderBlock(uri string) string { + // uri e.g. "gs://bucket/prefix/file.ext" + trim := fmt.Sprintf("gs://%s/", c.bucket) + p := strings.TrimPrefix(uri, trim) + dir := path.Dir(p) + return fmt.Sprintf(` gcs_artifact: + bucket: %s + object_key: %s +`, c.bucket, dir) +} + +func (c *gcsClient) BuildWorkingDir(modelName string) string { + // assemble gs://bucket/prefix/applications/.zip + if c.prefix == "" { + return fmt.Sprintf("gs://%s/%s", c.bucket, modelName) + } + return fmt.Sprintf("gs://%s/%s/%s", c.bucket, c.prefix, modelName) +} + +func (c *gcsClient) BuildArtifactURI(key string) string { + // key is "prefix/file.ext" + trim := fmt.Sprintf("%s/", c.prefix) + p := strings.TrimPrefix(key, trim) + return fmt.Sprintf("gs://%s/%s", c.bucket, p) +} + +func (c *gcsClient) Exists(ctx context.Context, key string) (bool, error) { + filePath := fmt.Sprintf("%s/%s", c.prefix, key) + obj := c.cli.Bucket(c.bucket).Object(filePath) + _, err := obj.Attrs(ctx) + if err != nil { + if errors.Is(err, storage.ErrObjectNotExist) { + return false, nil + } + return false, fmt.Errorf("GCS HeadObject(%q): %w", filePath, err) + } + return true, nil +} + +func (c *gcsClient) GetProvider() string { return "gcs" } +func (c *gcsClient) GetBucket() string { return c.bucket } +func (c *gcsClient) GetPrefix() string { return c.prefix } diff --git a/controllers/storage/minio.go b/controllers/storage/minio.go new file mode 100644 index 000000000..3d6f7672b --- /dev/null +++ b/controllers/storage/minio.go @@ -0,0 +1,45 @@ +package storage + +import ( + "context" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" + ai "github.com/splunk/splunk-operator/api/v4" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func NewMinioClient( + k8sClient client.Client, + namespace, bucket, prefix string, + vs ai.AiVolumeSpec, +) (StorageClient, error) { + awsCfg := &aws.Config{ + Endpoint: aws.String(vs.Endpoint), + Region: aws.String(vs.Region), + S3ForcePathStyle: aws.Bool(true), + } + if vs.SecretRef != "" { + secret := &corev1.Secret{} + if err := k8sClient.Get(context.TODO(), + client.ObjectKey{Namespace: namespace, Name: vs.SecretRef}, + secret, + ); err != nil { + return nil, err + } + awsCfg.Credentials = credentials.NewStaticCredentials( + string(secret.Data["s3_access_key"]), + string(secret.Data["s3_secret_key"]), + "", + ) + } + // no SecretRef → AWS SDK default chain (IRSA, env, etc) + sess, err := session.NewSession(awsCfg) + if err != nil { + return nil, err + } + return &s3Client{cli: s3.New(sess), bucket: bucket, prefix: prefix}, nil +} diff --git a/controllers/storage/storageclient.go b/controllers/storage/storageclient.go new file mode 100644 index 000000000..2e63134a7 --- /dev/null +++ b/controllers/storage/storageclient.go @@ -0,0 +1,57 @@ +package storage + +import ( + "context" + "fmt" + "net/url" + "strings" + + ai "github.com/splunk/splunk-operator/api/v4" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// StorageClient abstracts listing objects and building loader/workingDir snippets. +type StorageClient interface { + // ListObjects returns all object keys under the volume’s prefix. + ListObjects(ctx context.Context) ([]string, error) + // BuildLoaderBlock returns the YAML snippet for model_loader based on the URI. + BuildLoaderBlock(uri string) string + // BuildWorkingDir returns the working_dir URI for an application package. + BuildWorkingDir(modelName string) string + // BuildArtifactURI builds the full URI (e.g. "s3://bucket/prefix/key") for an arbitrary object key. + BuildArtifactURI(key string) string + Exists(ctx context.Context, key string) (bool, error) + GetProvider() string + GetBucket() string + GetPrefix() string +} + +func NewStorageClient( + k8sClient client.Client, + namespace string, + vs ai.AiVolumeSpec, +) (StorageClient, error) { + u, err := url.Parse(vs.Path) + if err != nil { + return nil, fmt.Errorf("invalid volume URI %q: %w", vs.Path, err) + } + + // strip leading slash from path + // e.g. u.Path="/prefix/..." → "prefix/..." + prefix := strings.TrimPrefix(u.Path, "/") + + switch u.Scheme { + case "s3": + return NewS3Client(k8sClient, namespace, u.Host, prefix, vs) + case "gs", "gcs": + return NewGCSClient(k8sClient, namespace, u.Host, prefix, vs) + case "azure": + return NewAzureClient(k8sClient, namespace, u.Host, prefix, vs) + case "minio": + // everything after "//" is host (bucket) and path. We treat u.Host as bucket, + // vs.Endpoint *must* be set to your MinIO URL for this case. + return NewMinioClient(k8sClient, namespace, u.Host, prefix, vs) + default: + return nil, fmt.Errorf("unsupported storage scheme %q", u.Scheme) + } +} diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 37f4b5880..a9042ce07 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -92,8 +92,6 @@ var _ = BeforeSuite(func(ctx context.Context) { err = enterpriseApi.AddToScheme(clientgoscheme.Scheme) Expect(err).NotTo(HaveOccurred()) - //+kubebuilder:scaffold:scheme - // Create New Manager for controllers k8sManager, err = ctrl.NewManager(cfg, ctrl.Options{ Scheme: clientgoscheme.Scheme, diff --git a/docs/AIPlatform.md b/docs/AIPlatform.md new file mode 100644 index 000000000..ed7219849 --- /dev/null +++ b/docs/AIPlatform.md @@ -0,0 +1,555 @@ +## Splunk AI Platform & Assistant Deployment Guide + +**Audience:** This guide is written for Kubernetes users who are new to custom resources and the Splunk Operator. We explain key concepts, walk through each step clearly, and provide examples you can copy-and-paste. + +### What You Will Learn + +1. Key concepts and terminology +2. Prerequisites needed before you begin +3. How to install the Splunk Operator +4. Understanding the custom resources: **AIPlatform** and **AIService** +5. Field-by-field explanation of each resource spec +6. Sample YAML manifests you can use right away +7. Step-by-step deployment instructions +8. How to check if everything is working +9. Common issues and how to fix them + +--- + +### Glossary of Terms + +* **Kubernetes**: A system to run containerized applications. +* **Operator**: A Kubernetes controller that simplifies deployment and management of applications. +* **Custom Resource Definition (CRD)**: A way to extend Kubernetes with new object types, like `AIPlatform`. +* **Ray**: A framework for distributed computing (used here for AI workloads). +* **Weaviate**: A vector database for storing AI embeddings. +* **Sidecar**: An extra container that runs alongside your main application container to add functionality (logging, metrics, etc.). + +--- + +### 1. Prerequisites + +Before you start, make sure you have: + +* A Kubernetes cluster (version 1.22 or newer) +* `kubectl` installed and pointed at your cluster +* Helm (optional, version 3+) if you prefer using charts +* A Splunk Enterprise or Splunk Cloud instance +* Storage (like S3, GCS, or Azure Blob) to hold AI applications and artifacts +* (Optional) Cert-Manager installed if you want automated TLS certificates + +#### 1.1 Cloud Identity & Service Account Setup + +To grant pods access to your object storage buckets (`s3://`, `gcs://`, `azure://`), configure a Kubernetes ServiceAccount with the appropriate cloud IAM integration: + +* **AWS (IRSA)**: Annotate your ServiceAccount with an IAM role that has S3 permissions. For example: + + ```yaml + apiVersion: v1 + kind: ServiceAccount + metadata: + name: ai-platform-sa + namespace: test + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/S3AccessRole + ``` +* **GCP (Workload Identity)**: Annotate with your GCP service account: + + ```yaml + apiVersion: v1 + kind: ServiceAccount + metadata: + name: ai-platform-sa + namespace: test + annotations: + iam.gke.io/gcp-service-account: my-gcp-sa@project.iam.gserviceaccount.com + ``` +* **Azure (Pod Identity or MSI)**: Bind your identity to the pod via Azure Pod Identity or assign a Managed Identity. Example annotation for AAD Pod Identity: + + ```yaml + apiVersion: v1 + kind: ServiceAccount + metadata: + name: ai-platform-sa + namespace: test + annotations: + aadpodidentitybinding: my-azure-identity-binding + ``` + +Use this ServiceAccount in your `AIPlatform.spec.headGroupSpec.serviceAccountName` and `workerGroupSpec.serviceAccountName`, as well as in `AIService.spec.serviceAccountName`. + +--- + +### 2. Installing the Splunk Operator Installing the Splunk Operator + +The Splunk Operator (SOK) manages Splunk on Kubernetes, including our AI components. + +#### Using Helm + +```bash +helm repo add splunk-operator https://splunk.github.io/splunk-operator/ +helm repo update +helm install splunk-operator \ # Name of the release + splunk-operator/splunk-operator \ # Chart name + --namespace splunk-operator \ # Dedicated namespace + --create-namespace +``` + +#### Without Helm + +```bash +git clone https://github.com/splunk/splunk-operator.git +cd splunk-operator/config +kubectl apply -f all-in-one.yaml +``` + +Verify installation: + +```bash +kubectl get pods -n splunk-operator +kubectl get crds | grep splunkai +``` + +--- + +### 3. Custom Resources Overview + +We introduce two new CRDs: + +| CRD Name | Short Name | Purpose | +| ----------------- | ---------- | ---------------------------------------------------- | +| AIPlatform | spai | Runs distributed AI jobs on a Ray cluster + Weaviate | +| AIService | (none) | Middleware service between Splunk UI and AI Platform | + +These CRDs let you tell the operator how to create and manage AI workloads. + +--- + +### 4. AIPlatform Spec Explained + +Below is a simplified table of key fields. All fields are optional unless marked **required**. + +| Field | Required? | Description | +| ------------------------ | --------- | --------------------------------------------------------------------------- | +| `appsVolume.path` | yes | Storage URI (e.g., `s3://bucket/apps`) where your AI code lives | +| `artifactsVolume.path` | yes | Storage URI (e.g., `s3://bucket`) for model data just the bucket name | +| `headGroupSpec` | yes | Settings for the Ray head node (service account, node labels) | +| `workerGroupSpec` | yes | Settings for Ray worker nodes, including GPU counts and replica limits | +| `defaultAcceleratorType` | no | GPU type (for example `nvidia-tesla-v100`) | +| `Sidecars` | no | Booleans to enable Envoy, FluentBit, OpenTelemetry, and Prometheus sidecars | +| `SplunkConfiguration` | yes | Reference to your Splunk endpoint, token, and secret | +| `Weaviate.replicas` | yes | Number of Weaviate pods for your vector database | + +*(The full API spec contains more fields. Start simple and add more as you learn.)* + +--- + +### 5. AIService Spec Explained + +This CR connects the Splunk UI, AI Platform, and Weaviate. + +| Field | Required? | Description | +| --------------------- | --------- | ----------------------------------------------------------- | +| `taskVolume.path` | yes | Storage URI (e.g., `s3://bucket/tasks`) | +| `SplunkConfiguration` | yes | Splunk endpoint, token, and secret | +| `VectorDbUrl` | yes | URL to your Weaviate service (e.g., `http://weaviate:8080`) | +| `AIPlatformRef` | yes | Link to the `AIPlatform` resource that you created | +| `replicas` | no | Number of middleware pods (default: 1) | +| `serviceAccountName` | no | Kubernetes service account for the assistant pods | +| `Metrics.enabled` | no | Turn on Prometheus metrics scraping | +| `MTLS.enabled` | no | Enable mTLS (TLS within the cluster) | + +--- + +### 6. Quick-Start Examples + +Copy these manifests into files and apply them; then customize for your environment. + +#### 6.1 AIPlatform Example + +````yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: selfsigned-issuer +spec: + selfSigned: {} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: platform-issuer + namespace: test +spec: + isCA: true + commonName: my-selfsigned-ca + secretName: root-secret + privateKey: + algorithm: ECDSA + size: 256 + issuerRef: + name: selfsigned-issuer + kind: Issuer + group: cert-manager.io +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: my-ca-issuer + namespace: test +spec: + ca: + secretName: root-secret +--- +apiVersion: enterprise.splunk.com/v4 +kind: AIPlatform +metadata: + name: model-endpoints-vivekr + namespace: test + annotations: + prometheus.io/path: /metrics + prometheus.io/port: '8080' + prometheus.io/scheme: http + ray.io/overwrite-container-cmd: 'true' + labels: + app: model-endpoints-vivekr + area: ai-platform + team: ai-foundation +spec: + # Object storage volume for application bundles & model artifacts + appsVolume: + path: s3://ai-platform-dev-vivekr/ray-services/ai-platform/applications + region: us-west-2 + secretRef: s3-secret + + artifactsVolume: + path: s3://ai-platform-dev-vivekr/ray-services/ai-platform/artifacts + region: us-west-2 + defaultAcceleratorType: L40S + + headGroupSpec: + serviceAccountName: ray-head-sa + + + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 1 + preference: + matchExpressions: + - key: node.kubernetes.io/instance-type + operator: In + values: + - g6.24xlarge + tolerations: + - key: nvidia.com/gpu + operator: Exists + effect: NoSchedule + + workerGroupSpec: + serviceAccountName: ray-worker-sa + gpuConfigs: + - tier: g6.24xlarge-0-gpu + minReplicas: 0 + maxReplicas: 10 + gpusPerPod: 1 + resources: + requests: + cpu: 16 + memory: 32Gi + ephemeral-storage: 10Gi + nvidia.com/gpu: 1 + limits: + cpu: 16 + memory: 32Gi + ephemeral-storage: 10Gi + nvidia.com/gpu: 1 + + - tier: g6.24xlarge-1-gpu + minReplicas: 0 + maxReplicas: 10 + gpusPerPod: 2 + resources: + requests: + cpu: 16 + memory: 16Gi + ephemeral-storage: 50Gi + nvidia.com/gpu: 2 + limits: + cpu: 16 + memory: 16Gi + ephemeral-storage: 50Gi + nvidia.com/gpu: 2 + + - tier: g6.24xlarge-2-gpu + minReplicas: 0 + maxReplicas: 10 + gpusPerPod: 2 + resources: + requests: + cpu: 4 + memory: 32Gi + ephemeral-storage: 100Gi + nvidia.com/gpu: 2 + limits: + cpu: 4 + memory: 32Gi + ephemeral-storage: 100Gi + nvidia.com/gpu: 2 + + - tier: g6.24xlarge-4-gpu + minReplicas: 0 + maxReplicas: 10 + gpusPerPod: 4 + resources: + requests: + cpu: 8 + memory: 64Gi + ephemeral-storage: 200Gi + nvidia.com/gpu: 4 + limits: + cpu: 8 + memory: 64Gi + ephemeral-storage: 200Gi + nvidia.com/gpu: 4 + + tolerations: + - key: nvidia.com/gpu + operator: Exists + effect: NoSchedule + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node.kubernetes.io/instance-type + operator: In + values: + - g6.24xlarge + # sidecars + sidecars: + envoy: true + fluentBit: true + otel: true + prometheusOperator: true + + # cert-manager Issuer for mTLS certificates + certificateRef: platform-issuer + + # DNS suffix for in-cluster services + clusterDomain: cluster.local + splunkConfiguration: + endpoint: https://splunk-standalone-standalone-service.test.svc.cluster.local:8088 + secretRef: + name: splunk-test-secret + namespace: test + # Weaviate side-car or companion DB + weaviate: + replicas: 1 + resources: + requests: + cpu: 0.5 + memory: 1Gi + limits: + cpu: 1 + memory: 2Gi + tolerations: + - key: dedicated + operator: Equal + value: cpu + effect: NoSchedule +```yaml +apiVersion: v4.splunk.cloud/v1 +kind: AIPlatform +metadata: + name: my-ai-platform +spec: + appsVolume: + path: s3://my-bucket/apps + artifactsVolume: + path: s3://my-bucket/artifacts + headGroupSpec: + serviceAccountName: ai-head-sa + workerGroupSpec: + serviceAccountName: ai-worker-sa + gpuConfigs: + - tier: default + minReplicas: 1 + maxReplicas: 2 + gpusPerPod: 1 + SplunkConfiguration: + crName: splunk-config + crNamespace: splunk + secretRef: + name: splunk-secret + Weaviate: + replicas: 1 +```` + +#### 6.2 AIService Example + +````yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: test-issuer + namespace: test +spec: + selfSigned: {} + +--- +apiVersion: enterprise.splunk.com/v4 +kind: AIService +metadata: + name: my-ai-assistant + namespace: test +spec: + taskVolume: + path: ai-assistant-prod-1 + region: us-west-2 + + # 1) Reference an existing SplunkConfiguration CR + splunkConfiguration: + crName: standalone-sample + crNamespace: test + + # 2) Point to your AI platform (which also sets VectorDbUrl for you) + # aiPlatformRef: + # name: example-ai-platform + # namespace: test + aiPlatformUrl: model-endpoints-vivekr-head-svc.test.svc.cluster.local:8000 + vectorDbUrl: model-endpoints-vivekr-weaviate.test.svc.cluster.local + + # 3) Deploy two replicas + replicas: 1 + + # 4) Node scheduling constraints + tolerations: + - key: dedicated + operator: Equal + value: cpu + effect: NoSchedule + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node.kubernetes.io/instance-type + operator: In + values: + - m5.xlarge + + # 5) Enable Prometheus scraping via ServiceMonitor + metrics: + enabled: false + path: /metrics + port: 8088 + + # 6) Turn on operator-managed mTLS for the SAIA Service + mtls: + enabled: true + termination: operator + issuerRef: + name: test-issuer + kind: Issuer + secretName: my-ai-assistant-tls + dnsNames: + - my-ai-assistant.test.svc.cluster.local + + # 7) (optional) override the ServiceAccount name + serviceAccountName: ray-worker-sa + + # 8) (optional) pass in extra env vars + env: + LOG_LEVEL: DEBUG + FEATURE_FLAG: true + + +```yaml +apiVersion: v4.splunk.cloud/v1 +kind: AIService +metadata: + name: my-ai-assistant +spec: + taskVolume: + path: s3://my-bucket/tasks + SplunkConfiguration: + crName: splunk-config + crNamespace: splunk + secretRef: + name: splunk-secret + VectorDbUrl: http://my-ai-platform-ray-service:8080 + AIPlatformRef: + kind: AIPlatform + name: my-ai-platform + replicas: 1 +```` + +--- + +### 7. Deploying Your Resources + +1. **Apply CRDs** + + ```bash + kubectl apply -f config/crd/bases/v4.splunk.cloud_aiplatforms.yaml + kubectl apply -f config/crd/bases/v4.splunk.cloud_aiservices.yaml + ``` +2. **Create Secrets** for Splunk credentials and storage access +3. **Deploy the AI Platform** + + ```bash + kubectl apply -f my-ai-platform.yaml + ``` +4. **Wait for Status** + + ```bash + kubectl wait --for=condition=Ready spai/my-ai-platform --timeout=5m + ``` +5. **Deploy the AI Assistant** + + ```bash + kubectl apply -f my-ai-assistant.yaml + ``` +6. **Check Readiness** + + ```bash + kubectl wait --for=condition=Ready aiservice/my-ai-assistant --timeout=5m + ``` + +--- + +### 8. How to Check Status + +Use these commands to see pod status, logs, and conditions: + +* List pods: `kubectl get pods` +* View conditions in the CR status: + + ```bash + kubectl get spai/my-ai-platform -o yaml | grep -A3 conditions + ``` +* View logs: + + ```bash + kubectl logs deployment/my-ai-assistant + ``` + +--- + +### 9. Troubleshooting Tips + +* **Pod never starts**: Check your storage permissions and secret names. +* **CR stuck in Pending**: Ensure ray-operator and cert-manager are installed (or disabled in Helm). +* **Errors in logs**: Use `kubectl logs` on the pod and look for missing environment variables or incorrect URLs. + +--- + +### 10. Where to Go Next + +* Read the full API reference in `api/v4` +* Explore tuning Ray and Weaviate settings +* Learn more about sidecar logging and metrics +* Join the Splunk Operator community for tips and examples + +Happy deploying! Feel free to ask questions as you explore. diff --git a/go.mod b/go.mod index 4502793cd..3c3a978d0 100644 --- a/go.mod +++ b/go.mod @@ -1,123 +1,137 @@ module github.com/splunk/splunk-operator -go 1.23.0 +go 1.24 + +toolchain go1.24.2 require ( - cloud.google.com/go/storage v1.30.1 + cloud.google.com/go/storage v1.43.0 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1 github.com/aws/aws-sdk-go v1.47.11 + github.com/cert-manager/cert-manager v1.12.0-alpha.0 github.com/go-logr/logr v1.4.2 - github.com/google/go-cmp v0.6.0 + github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 github.com/joho/godotenv v1.5.1 github.com/minio/minio-go/v7 v7.0.16 - github.com/onsi/ginkgo/v2 v2.22.2 - github.com/onsi/gomega v1.36.2 + github.com/onsi/ginkgo/v2 v2.23.4 + github.com/onsi/gomega v1.37.0 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.14.0 - github.com/stretchr/testify v1.9.0 + github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.62.0 + github.com/prometheus/client_golang v1.20.5 + github.com/ray-project/kuberay/ray-operator v1.0.0 + github.com/stretchr/testify v1.10.0 github.com/wk8/go-ordered-map/v2 v2.1.7 - go.uber.org/zap v1.24.0 - google.golang.org/api v0.126.0 - k8s.io/api v0.26.2 - k8s.io/apiextensions-apiserver v0.26.2 - k8s.io/apimachinery v0.26.2 - k8s.io/client-go v0.26.2 - k8s.io/kubectl v0.26.2 - sigs.k8s.io/controller-runtime v0.14.5 + go.uber.org/zap v1.27.0 + google.golang.org/api v0.198.0 + k8s.io/api v0.26.1 + k8s.io/apiextensions-apiserver v0.26.1 + k8s.io/apimachinery v0.26.1 + k8s.io/client-go v0.26.1 + k8s.io/kubectl v0.26.1 ) require ( - cloud.google.com/go v0.110.4 // indirect - cloud.google.com/go/compute v1.21.0 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.1 // indirect + cloud.google.com/go/auth v0.9.4 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect + github.com/evanphx/json-patch v5.6.0+incompatible // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/google/gnostic v0.6.9 // indirect + github.com/imdario/mergo v0.3.12 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect + go.opentelemetry.io/otel v1.33.0 // indirect + go.opentelemetry.io/otel/metric v1.33.0 // indirect + go.opentelemetry.io/otel/trace v1.33.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + sigs.k8s.io/gateway-api v0.6.2 // indirect +) + +require ( + cloud.google.com/go v0.115.1 // indirect + cloud.google.com/go/compute/metadata v0.5.2 // indirect + cloud.google.com/go/iam v1.2.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dustin/go-humanize v1.0.0 // indirect - github.com/emicklei/go-restful/v3 v3.10.0 // indirect - github.com/evanphx/json-patch v4.12.0+incompatible // indirect - github.com/evanphx/json-patch/v5 v5.6.0 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-logr/zapr v1.2.3 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.20.0 // indirect - github.com/go-openapi/swag v0.19.14 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/gnostic v0.5.7-v3refs // indirect - github.com/google/gofuzz v1.1.0 // indirect - github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect - github.com/google/s2a-go v0.1.4 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect - github.com/googleapis/gax-go/v2 v2.11.0 // indirect - github.com/imdario/mergo v0.3.12 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect + github.com/google/s2a-go v0.1.8 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/googleapis/gax-go/v2 v2.13.0 // indirect + github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/jstemmer/go-junit-report v0.9.1 // indirect - github.com/klauspost/compress v1.13.5 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/cpuid v1.3.1 // indirect github.com/kylelemons/godebug v1.1.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/minio/md5-simd v1.1.0 // indirect github.com/minio/sha256-simd v0.1.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/moby/spdystream v0.2.0 // indirect + github.com/moby/spdystream v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.37.0 // indirect - github.com/prometheus/procfs v0.8.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/rs/xid v1.2.1 // indirect - github.com/sirupsen/logrus v1.8.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/stretchr/objx v0.5.2 // indirect go.opencensus.io v0.24.0 // indirect - go.uber.org/atomic v1.7.0 // indirect - go.uber.org/multierr v1.6.0 // indirect - golang.org/x/crypto v0.36.0 // indirect - golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect - golang.org/x/mod v0.22.0 // indirect - golang.org/x/net v0.38.0 // indirect - golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/term v0.30.0 // indirect - golang.org/x/text v0.23.0 // indirect - golang.org/x/time v0.6.0 // indirect - golang.org/x/tools v0.28.0 // indirect - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect + go.uber.org/automaxprocs v1.6.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.37.0 // indirect + golang.org/x/net v0.39.0 // indirect + golang.org/x/oauth2 v0.28.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/term v0.31.0 // indirect + golang.org/x/text v0.24.0 // indirect + golang.org/x/time v0.10.0 // indirect + golang.org/x/tools v0.31.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect - google.golang.org/grpc v1.58.3 // indirect + google.golang.org/grpc v1.69.2 // indirect google.golang.org/protobuf v1.36.5 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/ini.v1 v1.66.4 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/component-base v0.26.2 // indirect + k8s.io/component-base v0.26.1 // indirect k8s.io/klog/v2 v2.80.1 // indirect - k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect - k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect - sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect + k8s.io/kube-openapi v0.0.0-20230109183929-3758b55a6596 // indirect + k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 + sigs.k8s.io/controller-runtime v0.14.6 + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect + sigs.k8s.io/yaml v1.4.0 ) + +replace sigs.k8s.io/controller-runtime => sigs.k8s.io/controller-runtime v0.14.6 diff --git a/go.sum b/go.sum index a95c9487c..6a522305b 100644 --- a/go.sum +++ b/go.sum @@ -1,48 +1,20 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.110.4 h1:1JYyxKMN9hd5dR2MYTPWkGUgcoxVVhg0LKNKEo0qvmk= -cloud.google.com/go v0.110.4/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.21.0 h1:JNBsyXVoOoNJtTQcnEY5uYpZIbeCTYIeDe0Xh1bySMk= -cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y= -cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM= -cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ= +cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc= +cloud.google.com/go/auth v0.9.4 h1:DxF7imbEbiFu9+zdKC6cKBko1e8XeJnipNqIbWZ+kDI= +cloud.google.com/go/auth v0.9.4/go.mod h1:SHia8n6//Ya940F1rLimhJCjjx7KE17t0ctFEci3HkA= +cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= +cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= +cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= +cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= +cloud.google.com/go/iam v1.2.0 h1:kZKMKVNk/IsSSc/udOb83K0hL/Yh/Gcqpz+oAkoIFN8= +cloud.google.com/go/iam v1.2.0/go.mod h1:zITGuWgsLZxd8OwAlX+eMFgZDXzBm7icj1PVTYG766Q= +cloud.google.com/go/longrunning v0.6.0 h1:mM1ZmaNsQsnb+5n1DNPeL0KwQd9jQRqSqSDEkBZr+aI= +cloud.google.com/go/longrunning v0.6.0/go.mod h1:uHzSZqW89h7/pasCWNYdUpwGz3PcVWhrWupreVPYLts= +cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs= +cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0= +github.com/Azure/azure-sdk-for-go v67.3.0+incompatible h1:QEvenaO+Y9ShPeCWsSAtolzVUcb0T0tPeek5TDsovuM= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= @@ -56,12 +28,7 @@ github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1/go.mod h1:ap1dmS6vQK github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= @@ -69,111 +36,76 @@ github.com/aws/aws-sdk-go v1.47.11 h1:Dol+MA+hQblbnXUI3Vk9qvoekU6O1uDEuAItezjiWN github.com/aws/aws-sdk-go v1.47.11/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cert-manager/cert-manager v1.12.0-alpha.0 h1:YXz3X43L5YKYqR8LUFHoX88Wsyz9g85FwDk/0/p2cMk= +github.com/cert-manager/cert-manager v1.12.0-alpha.0/go.mod h1:FL6G0z9dhhLFrRw0fM8ihVUB/EFhq48Os0eKXu3SJrQ= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful/v3 v3.10.0 h1:X4gma4HM7hFm6WMeAsTfqA0GOfdNoCzBIkHGoRLGXuM= -github.com/emicklei/go-restful/v3 v3.10.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= -github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= -github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= -github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= -github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= -github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= -github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -184,97 +116,61 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= -github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= +github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= +github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= -github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= -github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= +github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= +github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 h1:liMMTbpW34dhU4az1GN0pTPADwNmvoRSeoZ6PItiqnY= +github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.13.5 h1:9O69jUPDcsT9fEm74W92rZL9FQY7rCdaXVneq+yyzl4= github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s= github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -285,14 +181,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM= -github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4= github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw= github.com/minio/minio-go/v7 v7.0.16 h1:GspaSBS8lOuEUCAqMe0W3UxSoyOA4b4F8PTspRVI+k4= @@ -301,8 +191,8 @@ github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKU github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= +github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -312,65 +202,48 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= -github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= -github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= -github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= +github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= +github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= +github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.62.0 h1:55138zTXw/yRYizPxZ672I/aDD7Yte3uYRAfUjWUu2M= +github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.62.0/go.mod h1:j51242bf6LQwvJ1JPKWApzTnifmCwcQq0i1p29ylWiM= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= -github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= -github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/ray-project/kuberay/ray-operator v1.0.0 h1:i69nvbV7az2FG41VHQgxrmhD+SUl8ca+ek4RPbSE2Q0= +github.com/ray-project/kuberay/ray-operator v1.0.0/go.mod h1:7C7ebIkxtkmOX8w1iiLrKM1j4hkZs/Guzm3WdePk/yg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= @@ -379,386 +252,155 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/wk8/go-ordered-map/v2 v2.1.7 h1:aUZ1xBMdbvY8wnNt77qqo4nyT3y0pX4Usat48Vm+hik= github.com/wk8/go-ordered-map/v2 v2.1.7/go.mod h1:9Xvgm2mV2kSq2SAm0Y608tBmu8akTzI7c2bz7/G7ZN4= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= +go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= +go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= +go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= +go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= +go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= +go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= +go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= +go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= +go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= -go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= -golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= -golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= -golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= -golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= +golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= +golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= -golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= +golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= -golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= +golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= +golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= -gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o= -google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/api v0.198.0 h1:OOH5fZatk57iN0A7tjJQzt6aPfYQ1JiWkt1yGseazks= +google.golang.org/api v0.198.0/go.mod h1:/Lblzl3/Xqqk9hw/yS97TImKTUwnf1bv89v7+OagJzc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 h1:hrbNEivu7Zn1pxvHk6MBrq9iE22woVILTHqexqBxe6I= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98/go.mod h1:S7mY02OqCJTD0E1OiQy1F72PWFB4bZJ87cAtLPYgDR0= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= +google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU= +google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4= +google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb h1:B7GIB7sr443wZ/EAEl7VZjmh1V6qzkt5V+RYcUYtS1U= +google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:E5//3O5ZIG2l71Xnt+P/CYUY8Bxs8E7WMoZ9tlcMbAY= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= -google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= +google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -767,76 +409,61 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= -google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= -gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.26.2 h1:dM3cinp3PGB6asOySalOZxEG4CZ0IAdJsrYZXE/ovGQ= -k8s.io/api v0.26.2/go.mod h1:1kjMQsFE+QHPfskEcVNgL3+Hp88B80uj0QtSOlj8itU= -k8s.io/apiextensions-apiserver v0.26.2 h1:/yTG2B9jGY2Q70iGskMf41qTLhL9XeNN2KhI0uDgwko= -k8s.io/apiextensions-apiserver v0.26.2/go.mod h1:Y7UPgch8nph8mGCuVk0SK83LnS8Esf3n6fUBgew8SH8= -k8s.io/apimachinery v0.26.2 h1:da1u3D5wfR5u2RpLhE/ZtZS2P7QvDgLZTi9wrNZl/tQ= -k8s.io/apimachinery v0.26.2/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= -k8s.io/client-go v0.26.2 h1:s1WkVujHX3kTp4Zn4yGNFK+dlDXy1bAAkIl+cFAiuYI= -k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU= -k8s.io/component-base v0.26.2 h1:IfWgCGUDzrD6wLLgXEstJKYZKAFS2kO+rBRi0p3LqcI= -k8s.io/component-base v0.26.2/go.mod h1:DxbuIe9M3IZPRxPIzhch2m1eT7uFrSBJUBuVCQEBivs= +k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ= +k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg= +k8s.io/apiextensions-apiserver v0.26.1 h1:cB8h1SRk6e/+i3NOrQgSFij1B2S0Y0wDoNl66bn8RMI= +k8s.io/apiextensions-apiserver v0.26.1/go.mod h1:AptjOSXDGuE0JICx/Em15PaoO7buLwTs0dGleIHixSM= +k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ= +k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= +k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU= +k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE= +k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= +k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU= k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= -k8s.io/kubectl v0.26.2 h1:SMPB4j48eVFxsYluBq3VLyqXtE6b72YnszkbTAtFye4= -k8s.io/kubectl v0.26.2/go.mod h1:KYWOXSwp2BrDn3kPeoU/uKzKtdqvhK1dgZGd0+no4cM= +k8s.io/kube-openapi v0.0.0-20230109183929-3758b55a6596 h1:8cNCQs+WqqnSpZ7y0LMQPKD+RZUHU17VqLPMW3qxnxc= +k8s.io/kube-openapi v0.0.0-20230109183929-3758b55a6596/go.mod h1:/BYxry62FuDzmI+i9B+X2pqfySRmSOW2ARmj5Zbqhj0= +k8s.io/kubectl v0.26.1 h1:K8A0Jjlwg8GqrxOXxAbjY5xtmXYeYjLU96cHp2WMQ7s= +k8s.io/kubectl v0.26.1/go.mod h1:miYFVzldVbdIiXMrHZYmL/EDWwJKM+F0sSsdxsATFPo= k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y= k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/controller-runtime v0.14.5 h1:6xaWFqzT5KuAQ9ufgUaj1G/+C4Y1GRkhrxl+BJ9i+5s= -sigs.k8s.io/controller-runtime v0.14.5/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/controller-runtime v0.14.6 h1:oxstGVvXGNnMvY7TAESYk+lzr6S3V5VFxQ6d92KcwQA= +sigs.k8s.io/controller-runtime v0.14.6/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= +sigs.k8s.io/gateway-api v0.6.2 h1:583XHiX2M2bKEA0SAdkoxL1nY73W1+/M+IAm8LJvbEA= +sigs.k8s.io/gateway-api v0.6.2/go.mod h1:EYJT+jlPWTeNskjV0JTki/03WX1cyAnBhwBJfYHpV/0= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016 h1:kXv6kKdoEtedwuqMmkqhbkgvYKeycVbC8+iPCP9j5kQ= +sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= +sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/helm-chart/splunk-aiplatform/Chart.yaml b/helm-chart/splunk-aiplatform/Chart.yaml new file mode 100644 index 000000000..2fb00181f --- /dev/null +++ b/helm-chart/splunk-aiplatform/Chart.yaml @@ -0,0 +1,23 @@ +# Chart.yaml +apiVersion: v2 +name: aiplatform +description: A Helm chart for deploying AIPlatform custom resources +type: application +version: 0.1.0 +appVersion: "v4.0.0" +icon: https://example.com/icon.png +keywords: + - splunk + - ai + - kuberay +home: https://github.com/splunk/splunk-operator +sources: + - https://github.com/splunk/splunk-operator +maintainers: + - name: Splunk AI Team + email: splunkai@cisco.com +dependencies: +- name: splunk-operator + version: "2.8.0" + repository: "file://splunk-operator/helm-chart/splunk-operator" + condition: splunk-operator.enabled \ No newline at end of file diff --git a/helm-chart/splunk-aiplatform/templates/Notes.txt b/helm-chart/splunk-aiplatform/templates/Notes.txt new file mode 100644 index 000000000..e72a373c0 --- /dev/null +++ b/helm-chart/splunk-aiplatform/templates/Notes.txt @@ -0,0 +1,8 @@ +# templates/NOTES.txt +1. Your AIPlatform custom resource has been created: + kubectl get aiplatform {{ include "aiplatform.fullname" . }} + +2. If you enabled Ingress: + * Host: {{ (index .Values.ingress.hosts 0).host }} + * Port: 80 / 443 + * Try: curl http://{{ (index .Values.ingress.hosts 0).host }} diff --git a/helm-chart/splunk-aiplatform/templates/_helpers.tpl b/helm-chart/splunk-aiplatform/templates/_helpers.tpl new file mode 100644 index 000000000..31bdfebb3 --- /dev/null +++ b/helm-chart/splunk-aiplatform/templates/_helpers.tpl @@ -0,0 +1,20 @@ +{{/* templates/_helpers.tpl */}} +{{- define "aiplatform.name" -}} +{{- default .Chart.Name .Values.nameOverride -}} +{{- end -}} + +{{- define "aiplatform.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" (include "aiplatform.name" .) .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{- define "aiplatform.labels" -}} +app.kubernetes.io/name: {{ include "aiplatform.name" . }} +helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/version: {{ .Chart.AppVersion }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} diff --git a/helm-chart/splunk-aiplatform/templates/aiplatform.yaml b/helm-chart/splunk-aiplatform/templates/aiplatform.yaml new file mode 100644 index 000000000..4f9d50d82 --- /dev/null +++ b/helm-chart/splunk-aiplatform/templates/aiplatform.yaml @@ -0,0 +1,47 @@ +# templates/aiplatform.yaml +{{- if .Capabilities.APIVersions.Has "v4.splunkai/v1" -}} +apiVersion: v4.splunkai/v1 +kind: AIPlatform +metadata: + name: {{ include "aiplatform.fullname" . }} + labels: + {{- include "aiplatform.labels" . | nindent 4 }} +spec: + appsVolume: + path: {{ .Values.appsVolume.path | quote }} + endpoint: {{ .Values.appsVolume.endpoint | quote }} + region: {{ .Values.appsVolume.region | quote }} + secretRef: {{ .Values.appsVolume.secretRef | quote }} + artifactsVolume: + path: {{ .Values.artifactsVolume.path | quote }} + endpoint: {{ .Values.artifactsVolume.endpoint | quote }} + region: {{ .Values.artifactsVolume.region | quote }} + secretRef: {{ .Values.artifactsVolume.secretRef | quote }} + headGroupSpec: + serviceAccountName: {{ .Values.headGroupSpec.serviceAccountName | quote }} + imageRegistry: {{ .Values.headGroupSpec.imageRegistry | quote }} + schedulingSpec: + nodeSelector: {{ toYaml .Values.headGroupSpec.schedulingSpec.nodeSelector | nindent 6 }} + tolerations: {{ toYaml .Values.headGroupSpec.schedulingSpec.tolerations | nindent 6 }} + affinity: {{ toYaml .Values.headGroupSpec.schedulingSpec.affinity | nindent 6 }} + workerGroupSpec: + serviceAccountName: {{ .Values.workerGroupSpec.serviceAccountName | quote }} + imageRegistry: {{ .Values.workerGroupSpec.imageRegistry | quote }} + gpuConfigs: {{ toYaml .Values.workerGroupSpec.gpuConfigs | nindent 4 }} + schedulingSpec: + nodeSelector: {{ toYaml .Values.workerGroupSpec.schedulingSpec.nodeSelector | nindent 6 }} + tolerations: {{ toYaml .Values.workerGroupSpec.schedulingSpec.tolerations | nindent 6 }} + affinity: {{ toYaml .Values.workerGroupSpec.schedulingSpec.affinity | nindent 6 }} + defaultAcceleratorType: {{ .Values.defaultAcceleratorType | quote }} + sidecars: {{ toYaml .Values.sidecars | nindent 4 }} + certificateRef: {{ .Values.certificateRef | quote }} + clusterDomain: {{ .Values.clusterDomain | quote }} + splunkConfiguration: {{ toYaml .Values.splunkConfiguration | nindent 4 }} + weaviate: + replicas: {{ .Values.weaviate.replicas }} + resources: {{ toYaml .Values.weaviate.resources | nindent 4 }} + serviceAccountName: {{ .Values.weaviate.serviceAccountName | quote }} + nodeSelector: {{ toYaml .Values.weaviate.nodeSelector | nindent 4 }} + tolerations: {{ toYaml .Values.weaviate.tolerations | nindent 4 }} + affinity: {{ toYaml .Values.weaviate.affinity | nindent 4 }} +{{- end -}} diff --git a/helm-chart/splunk-aiplatform/templates/ingress.yaml b/helm-chart/splunk-aiplatform/templates/ingress.yaml new file mode 100644 index 000000000..aad9e268c --- /dev/null +++ b/helm-chart/splunk-aiplatform/templates/ingress.yaml @@ -0,0 +1,37 @@ +# templates/ingress.yaml +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "aiplatform.fullname" . }}-ingress + labels: + {{- include "aiplatform.labels" . | nindent 4 }} + annotations: + {{- toYaml .Values.ingress.annotations | nindent 4 }} +spec: + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "aiplatform.fullname" $. }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName | quote }} + {{- end }} + {{- end }} +{{- end }} diff --git a/helm-chart/splunk-aiplatform/templates/services.yaml b/helm-chart/splunk-aiplatform/templates/services.yaml new file mode 100644 index 000000000..a2621cea1 --- /dev/null +++ b/helm-chart/splunk-aiplatform/templates/services.yaml @@ -0,0 +1,17 @@ +# templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: {{ include "aiplatform.fullname" . }} + labels: + {{- include "aiplatform.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: {{ .Values.service.port }} + protocol: TCP + name: http + selector: + app.kubernetes.io/name: {{ include "aiplatform.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} diff --git a/helm-chart/splunk-aiplatform/values.yaml b/helm-chart/splunk-aiplatform/values.yaml new file mode 100644 index 000000000..1ad373b41 --- /dev/null +++ b/helm-chart/splunk-aiplatform/values.yaml @@ -0,0 +1,78 @@ +# values.yaml +# Default values for aiplatform. +# Users can override these (e.g. via --values) to configure both the CR and the Ingress. + +# Metadata overrides: +nameOverride: "" +fullnameOverride: "" + +# --- AIPlatform CR spec --- +appsVolume: + path: "s3://my-bucket/apps" + region: "us-west-2" +artifactsVolume: + path: "s3://my-bucket" + region: "us-west-2" + + +headGroupSpec: + serviceAccountName: "default" + imageRegistry: "" + schedulingSpec: + nodeSelector: {} + tolerations: [] + affinity: {} + +workerGroupSpec: + serviceAccountName: "default" + imageRegistry: "" + gpuConfigs: [] # e.g. [{tier: "gpu", minReplicas: 1, ...}] + schedulingSpec: + nodeSelector: {} + tolerations: [] + affinity: {} + +defaultAcceleratorType: "LS04S" # e.g. "L40S", "LS04S", "A10G", "A100", "V100" + +sidecars: + envoy: true + fluentBit: true + otel: true + prometheusOperator: true + +certificateRef: "" +clusterDomain: "cluster.local" + +splunkConfiguration: + crName: "splunk-standalone" + crNamespace: default + secretRef: + name: "splunk-secret" + namespace: default + endpoint: "https://splunk.default.svc.cluster.local:8089" + token: "splunk-token" # Optional, if not using secretRef + +weaviate: + replicas: 1 + resources: {} + serviceAccountName: "default" + # inline SchedulingSpec: + nodeSelector: {} + tolerations: [] + affinity: {} + +# --- Service for exposing the AIPlatform API or UI --- +service: + type: ClusterIP + port: 8080 + +# --- Ingress settings (fully optional) --- +ingress: + enabled: false + annotations: {} + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] # e.g. [{ hosts: ["chart-example.local"], secretName: "tls-secret" }] diff --git a/main.go b/main.go index 4a425ff06..882ab068c 100644 --- a/main.go +++ b/main.go @@ -39,11 +39,16 @@ import ( enterpriseApiV3 "github.com/splunk/splunk-operator/api/v3" enterpriseApi "github.com/splunk/splunk-operator/api/v4" + enterprisev4 "github.com/splunk/splunk-operator/api/v4" "github.com/splunk/splunk-operator/controllers" debug "github.com/splunk/splunk-operator/controllers/debug" "github.com/splunk/splunk-operator/pkg/config" + //+kubebuilder:scaffold:imports //extapi "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + externalrayio "github.com/ray-project/kuberay/ray-operator/apis/ray/v1" ) var ( @@ -55,6 +60,10 @@ func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(enterpriseApi.AddToScheme(scheme)) utilruntime.Must(enterpriseApiV3.AddToScheme(scheme)) + utilruntime.Must(enterprisev4.AddToScheme(scheme)) + utilruntime.Must(externalrayio.AddToScheme(scheme)) + utilruntime.Must(certmanagerv1.AddToScheme(scheme)) + utilruntime.Must(monitoringv1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme //utilruntime.Must(extapi.AddToScheme(scheme)) } @@ -179,6 +188,20 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Standalone") os.Exit(1) } + if err = (&controllers.AIPlatformReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "AIPlatform") + os.Exit(1) + } + if err = (&controllers.AIServiceReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "AIService") + os.Exit(1) + } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/pkg/splunk/client/awss3client.go b/pkg/splunk/client/awss3client.go index 3d00746d3..194ea81f5 100644 --- a/pkg/splunk/client/awss3client.go +++ b/pkg/splunk/client/awss3client.go @@ -25,6 +25,7 @@ import ( "os" "regexp" "strings" + "sync" "time" "github.com/aws/aws-sdk-go/aws" @@ -37,6 +38,7 @@ import ( // blank assignment to verify that AWSS3Client implements RemoteDataClient var _ RemoteDataClient = &AWSS3Client{} +var mu = sync.Mutex{} // SplunkAWSS3Client is an interface to AWS S3 client type SplunkAWSS3Client interface { @@ -187,6 +189,8 @@ func NewAWSS3Client(ctx context.Context, bucketName string, accessKeyID string, // RegisterAWSS3Client will add the corresponding function pointer to the map func RegisterAWSS3Client() { + mu.Lock() + defer mu.Unlock() wrapperObject := GetRemoteDataClientWrapper{GetRemoteDataClient: NewAWSS3Client, GetInitFunc: InitAWSClientWrapper} RemoteDataClientsMap["aws"] = wrapperObject } diff --git a/pkg/splunk/common/util.go b/pkg/splunk/common/util.go index 42cf832d3..a9547a5e6 100644 --- a/pkg/splunk/common/util.go +++ b/pkg/splunk/common/util.go @@ -268,7 +268,7 @@ func GetLabels(component, name, instanceIdentifier string, partOfIdentifier stri labels[labelTypeMap["instance"]] = fmt.Sprintf("splunk-%s-%s", instanceIdentifier, name) } default: - err = fmt.Errorf(fmt.Sprintf("Incorrect label type %s", s)) + err = fmt.Errorf("incorrect label type %s", s) } } } diff --git a/pkg/splunk/controller/statefulset_test.go b/pkg/splunk/controller/statefulset_test.go index 79de63a9a..2d0b7d475 100644 --- a/pkg/splunk/controller/statefulset_test.go +++ b/pkg/splunk/controller/statefulset_test.go @@ -396,7 +396,7 @@ func TestGetStatefulSetByName(t *testing.T) { namespacedName := types.NamespacedName{Namespace: "test", Name: "splunk-test-monitoring-console"} _, err = GetStatefulSetByName(ctx, c, namespacedName) if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } } diff --git a/pkg/splunk/enterprise/afwscheduler_test.go b/pkg/splunk/enterprise/afwscheduler_test.go index 63521699d..3b04e96e3 100644 --- a/pkg/splunk/enterprise/afwscheduler_test.go +++ b/pkg/splunk/enterprise/afwscheduler_test.go @@ -1502,7 +1502,7 @@ func TestPipelineWorkerDownloadShouldPass(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } splclient.RegisterRemoteDataClient(ctx, "aws") @@ -1665,7 +1665,7 @@ func TestPipelineWorkerDownloadShouldFail(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } splclient.RegisterRemoteDataClient(ctx, "aws") diff --git a/pkg/splunk/enterprise/clustermanager_test.go b/pkg/splunk/enterprise/clustermanager_test.go index 807873a67..f819aba7c 100644 --- a/pkg/splunk/enterprise/clustermanager_test.go +++ b/pkg/splunk/enterprise/clustermanager_test.go @@ -616,14 +616,14 @@ func TestApplyClusterManagerWithSmartstore(t *testing.T) { // Create namespace scoped secret secret, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } secret.Data[s3AccessKey] = []byte("abcdJDckRkxhMEdmSk5FekFRRzBFOXV6bGNldzJSWE9IenhVUy80aa") secret.Data[s3SecretKey] = []byte("g4NVp0a29PTzlPdGczWk1vekVUcVBSa0o4NkhBWWMvR1NadDV4YVEy") _, err = splctrl.ApplySecret(ctx, client, secret) if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } smartstoreConfigMap := corev1.ConfigMap{ @@ -725,12 +725,12 @@ func TestPerformCmBundlePush(t *testing.T) { secret, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } _, err = splctrl.ApplySecret(ctx, client, secret) if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } smartstoreConfigMap := corev1.ConfigMap{ @@ -743,7 +743,7 @@ func TestPerformCmBundlePush(t *testing.T) { _, err = splctrl.ApplyConfigMap(ctx, client, &smartstoreConfigMap) if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } current.Status.BundlePushTracker.NeedToPushManagerApps = true @@ -805,12 +805,12 @@ func TestPushManagerAppsBundle(t *testing.T) { secret, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } _, err = splctrl.ApplySecret(ctx, client, secret) if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } err = PushManagerAppsBundle(ctx, client, ¤t) @@ -890,7 +890,7 @@ func TestAppFrameworkApplyClusterManagerShouldNotFail(t *testing.T) { // Create namespace scoped secret _, err = splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } cm.Kind = "ClusterManager" @@ -962,7 +962,7 @@ func TestApplyClusterManagerDeletion(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, c, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } // test deletion @@ -1059,7 +1059,7 @@ func TestClusterManagerGetAppsListForAWSS3ClientShouldNotFail(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } splclient.RegisterRemoteDataClient(ctx, "aws") @@ -1208,7 +1208,7 @@ func TestClusterManagerGetAppsListForAWSS3ClientShouldFail(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } splclient.RegisterRemoteDataClient(ctx, "aws") diff --git a/pkg/splunk/enterprise/clustermaster_test.go b/pkg/splunk/enterprise/clustermaster_test.go index 2cffd9b8e..0559d0ed6 100644 --- a/pkg/splunk/enterprise/clustermaster_test.go +++ b/pkg/splunk/enterprise/clustermaster_test.go @@ -314,14 +314,14 @@ func TestApplyClusterMasterWithSmartstore(t *testing.T) { // Create namespace scoped secret secret, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } secret.Data[s3AccessKey] = []byte("abcdJDckRkxhMEdmSk5FekFRRzBFOXV6bGNldzJSWE9IenhVUy80aa") secret.Data[s3SecretKey] = []byte("g4NVp0a29PTzlPdGczWk1vekVUcVBSa0o4NkhBWWMvR1NadDV4YVEy") _, err = splctrl.ApplySecret(ctx, client, secret) if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } smartstoreConfigMap := corev1.ConfigMap{ @@ -422,12 +422,12 @@ func TestPerformCmasterBundlePush(t *testing.T) { secret, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } _, err = splctrl.ApplySecret(ctx, client, secret) if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } smartstoreConfigMap := corev1.ConfigMap{ @@ -440,7 +440,7 @@ func TestPerformCmasterBundlePush(t *testing.T) { _, err = splctrl.ApplyConfigMap(ctx, client, &smartstoreConfigMap) if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } current.Status.BundlePushTracker.NeedToPushMasterApps = true @@ -495,12 +495,12 @@ func TestPushMasterAppsBundle(t *testing.T) { secret, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } _, err = splctrl.ApplySecret(ctx, client, secret) if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } err = PushMasterAppsBundle(ctx, client, ¤t) @@ -580,7 +580,7 @@ func TestAppFrameworkApplyClusterMasterShouldNotFail(t *testing.T) { // Create namespace scoped secret _, err = splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } cm.Kind = "ClusterMaster" @@ -652,7 +652,7 @@ func TestApplyCLusterMasterDeletion(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, c, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } // test deletion @@ -750,7 +750,7 @@ func TestClusterMasterGetAppsListForAWSS3ClientShouldNotFail(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } splclient.RegisterRemoteDataClient(ctx, "aws") @@ -899,7 +899,7 @@ func TestClusterMasterGetAppsListForAWSS3ClientShouldFail(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } splclient.RegisterRemoteDataClient(ctx, "aws") diff --git a/pkg/splunk/enterprise/configuration.go b/pkg/splunk/enterprise/configuration.go index 7d0302e1a..a75a175ad 100644 --- a/pkg/splunk/enterprise/configuration.go +++ b/pkg/splunk/enterprise/configuration.go @@ -867,6 +867,7 @@ func updateSplunkPodTemplateWithConfig(ctx context.Context, client splcommon.Con {Name: "SPLUNK_HOME_OWNERSHIP_ENFORCEMENT", Value: "false"}, {Name: "SPLUNK_ROLE", Value: role}, {Name: "SPLUNK_DECLARATIVE_ADMIN_PASSWORD", Value: "true"}, + {Name: "SPLUNK_GENERAL_TERMS", Value: "--accept-current-at-splunk-com"}, {Name: livenessProbeDriverPathEnv, Value: GetLivenessDriverFilePath()}, } diff --git a/pkg/splunk/enterprise/configuration_test.go b/pkg/splunk/enterprise/configuration_test.go index 01dbcd260..71bc04621 100644 --- a/pkg/splunk/enterprise/configuration_test.go +++ b/pkg/splunk/enterprise/configuration_test.go @@ -1269,12 +1269,12 @@ func TestAreRemoteVolumeKeysChanged(t *testing.T) { // Just to simplify the test, assume that the keys are stored as part of the splunk-test-scret secret, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } _, err = splctrl.ApplySecret(ctx, client, secret) if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } _ = AreRemoteVolumeKeysChanged(ctx, client, &cr, SplunkClusterManager, &cr.Spec.SmartStore, ResourceRev, &err) diff --git a/pkg/splunk/enterprise/cp_test.go b/pkg/splunk/enterprise/cp_test.go index e9c6fa0bb..8a5d5b255 100644 --- a/pkg/splunk/enterprise/cp_test.go +++ b/pkg/splunk/enterprise/cp_test.go @@ -40,11 +40,11 @@ func TestRecursiveTarEmptySrcDir(t *testing.T) { // prepare temporary files to tar operation if err := os.MkdirAll("/tmp/src1", os.ModePerm); err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } if err := os.MkdirAll("/tmp/dst/", os.ModePerm); err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } // test recursive directories copy @@ -57,11 +57,11 @@ func TestRecursiveTarEmptySrcDir(t *testing.T) { } if err := os.RemoveAll("/tmp/src1"); err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } if err := os.RemoveAll("/tmp/dst"); err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } } @@ -69,17 +69,17 @@ func TestRecursiveTarEmptySrcDir(t *testing.T) { func TestRecursiveTarSrcOneDir(t *testing.T) { // prepare temporary files to tar operation if err := os.MkdirAll("/tmp/src2", os.ModePerm); err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } for i := 0; i < 10; i++ { path := fmt.Sprintf("/tmp/src2/filename-%d.txt", i) err := os.WriteFile(path, []byte("Hello"), 0755) if err != nil { - t.Errorf(fmt.Sprintf("Unable to write file: %s", err.Error())) + t.Errorf("Unable to write file: %s", err.Error()) } } if err := os.MkdirAll("/tmp/dst/", os.ModePerm); err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } // test recursive directories copy @@ -92,11 +92,11 @@ func TestRecursiveTarSrcOneDir(t *testing.T) { } if err := os.RemoveAll("/tmp/src2"); err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } if err := os.RemoveAll("/tmp/dst"); err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } } @@ -105,28 +105,28 @@ func TestRecursiveTarSrcAllDir(t *testing.T) { // prepare temporary files to tar operation if err := os.MkdirAll("/tmp/src3/a/b/c/d/e", os.ModePerm); err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } for i := 0; i < 10; i++ { path := fmt.Sprintf("/tmp/src3/a/filename-%d.txt", i) err := os.WriteFile(path, []byte("Hello"), 0755) if err != nil { - t.Errorf(fmt.Sprintf("Unable to write file: %s", err.Error())) + t.Errorf("Unable to write file: %s", err.Error()) } path = fmt.Sprintf("/tmp/src3/a/b/filename-%d.txt", i) err = os.WriteFile(path, []byte("Hello"), 0755) if err != nil { - t.Errorf(fmt.Sprintf("Unable to write file: %v", err)) + t.Errorf("Unable to write file: %v", err) } path = fmt.Sprintf("/tmp/src3/a/b/c/filename-%d.txt", i) err = os.WriteFile(path, []byte("Hello"), 0755) if err != nil { - t.Errorf(fmt.Sprintf("Unable to write file: %v", err)) + t.Errorf("Unable to write file: %v", err) } path = fmt.Sprintf("/tmp/src3/a/b/c/d/filename-%d.txt", i) err = os.WriteFile(path, []byte("Hello"), 0755) if err != nil { - t.Errorf(fmt.Sprintf("Unable to write file: %v", err)) + t.Errorf("Unable to write file: %v", err) } } @@ -140,10 +140,10 @@ func TestRecursiveTarSrcAllDir(t *testing.T) { } if err := os.RemoveAll("/tmp/src3"); err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } if err := os.RemoveAll("/tmp/dst"); err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } } diff --git a/pkg/splunk/enterprise/licensemanager_test.go b/pkg/splunk/enterprise/licensemanager_test.go index 94c4e1307..ce46eafd3 100644 --- a/pkg/splunk/enterprise/licensemanager_test.go +++ b/pkg/splunk/enterprise/licensemanager_test.go @@ -239,7 +239,7 @@ func TestAppFrameworkApplyLicenseManagerShouldNotFail(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } // Create S3 secret @@ -331,7 +331,7 @@ func TestLicensemanagerGetAppsListForAWSS3ClientShouldNotFail(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } splclient.RegisterRemoteDataClient(ctx, "aws") @@ -475,7 +475,7 @@ func TestLicenseManagerGetAppsListForAWSS3ClientShouldFail(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } splclient.RegisterRemoteDataClient(ctx, "aws") @@ -660,7 +660,7 @@ func TestApplyLicenseManagerDeletion(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, c, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } // test deletion diff --git a/pkg/splunk/enterprise/licensemaster_test.go b/pkg/splunk/enterprise/licensemaster_test.go index 2c890e969..19fda16b5 100644 --- a/pkg/splunk/enterprise/licensemaster_test.go +++ b/pkg/splunk/enterprise/licensemaster_test.go @@ -251,7 +251,7 @@ func TestAppFrameworkApplyLicenseMasterShouldNotFail(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } // Create S3 secret @@ -346,7 +346,7 @@ func TestLicensemasterGetAppsListForAWSS3ClientShouldNotFail(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } splclient.RegisterRemoteDataClient(ctx, "aws") @@ -490,7 +490,7 @@ func TestLicenseMasterGetAppsListForAWSS3ClientShouldFail(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } splclient.RegisterRemoteDataClient(ctx, "aws") @@ -677,7 +677,7 @@ func TestApplyLicenseMasterDeletion(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, c, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } // test deletion diff --git a/pkg/splunk/enterprise/monitoringconsole_test.go b/pkg/splunk/enterprise/monitoringconsole_test.go index 86b724e3d..d822eb52c 100644 --- a/pkg/splunk/enterprise/monitoringconsole_test.go +++ b/pkg/splunk/enterprise/monitoringconsole_test.go @@ -464,7 +464,7 @@ func TestAppFrameworkApplyMonitoringConsoleShouldNotFail(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } // Create S3 secret @@ -546,7 +546,7 @@ func TestMonitoringConsoleGetAppsListForAWSS3ClientShouldNotFail(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } splclient.RegisterRemoteDataClient(ctx, "aws") @@ -690,7 +690,7 @@ func TestMonitoringConsoleGetAppsListForAWSS3ClientShouldFail(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } splclient.RegisterRemoteDataClient(ctx, "aws") @@ -1061,7 +1061,7 @@ func TestApplyMonitoringConsoleDeletion(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, c, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } // test deletion diff --git a/pkg/splunk/enterprise/searchheadcluster_test.go b/pkg/splunk/enterprise/searchheadcluster_test.go index 6afc18163..89abdd476 100644 --- a/pkg/splunk/enterprise/searchheadcluster_test.go +++ b/pkg/splunk/enterprise/searchheadcluster_test.go @@ -809,7 +809,7 @@ func TestAppFrameworkSearchHeadClusterShouldNotFail(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } // Create S3 secret @@ -899,7 +899,7 @@ func TestSHCGetAppsListForAWSS3ClientShouldNotFail(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } splclient.RegisterRemoteDataClient(ctx, "aws") @@ -1041,7 +1041,7 @@ func TestSHCGetAppsListForAWSS3ClientShouldFail(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } splclient.RegisterRemoteDataClient(ctx, "aws") @@ -1224,7 +1224,7 @@ func TestApplySearchHeadClusterDeletion(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, c, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } // test deletion diff --git a/pkg/splunk/enterprise/standalone_test.go b/pkg/splunk/enterprise/standalone_test.go index 1e3c112cf..1dd4b98bb 100644 --- a/pkg/splunk/enterprise/standalone_test.go +++ b/pkg/splunk/enterprise/standalone_test.go @@ -303,14 +303,14 @@ func TestApplyStandaloneWithSmartstore(t *testing.T) { // Create namespace scoped secret secret, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } secret.Data[s3AccessKey] = []byte("abcdJDckRkxhMEdmSk5FekFRRzBFOXV6bGNldzJSWE9IenhVUy80aa") secret.Data[s3SecretKey] = []byte("g4NVp0a29PTzlPdGczWk1vekVUcVBSa0o4NkhBWWMvR1NadDV4YVEy") _, err = splctrl.ApplySecret(ctx, client, secret) if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } revised := current.DeepCopy() @@ -425,14 +425,14 @@ func TestApplyStandaloneSmartstoreKeyChangeDetection(t *testing.T) { // Create namespace scoped secret secret, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } secret.Data[s3AccessKey] = []byte("abcdJDckRkxhMEdmSk5FekFRRzBFOXV6bGNldzJSWE9IenhVUy80aa") secret.Data[s3SecretKey] = []byte("g4NVp0a29PTzlPdGczWk1vekVUcVBSa0o4NkhBWWMvR1NadDV4YVEy") _, err = splctrl.ApplySecret(ctx, client, secret) if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } _, err = ApplyStandalone(context.Background(), client, ¤t) @@ -446,7 +446,7 @@ func TestApplyStandaloneSmartstoreKeyChangeDetection(t *testing.T) { _, err = splctrl.ApplySecret(ctx, client, secret) if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } changed := AreRemoteVolumeKeysChanged(ctx, client, ¤t, SplunkStandalone, ¤t.Spec.SmartStore, current.Status.ResourceRevMap, &err) @@ -503,7 +503,7 @@ func TestAppFrameworkApplyStandaloneShouldNotFail(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } // Create S3 secret @@ -573,7 +573,7 @@ func TestAppFrameworkApplyStandaloneScalingUpShouldNotFail(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } // Create S3 secret @@ -668,7 +668,7 @@ func TestStandaloneGetAppsListForAWSS3ClientShouldNotFail(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } splclient.RegisterRemoteDataClient(ctx, "aws") @@ -811,7 +811,7 @@ func TestStandaloneGetAppsListForAWSS3ClientShouldFail(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } splclient.RegisterRemoteDataClient(ctx, "aws") @@ -996,7 +996,7 @@ func TestApplyStandaloneDeletion(t *testing.T) { // Create namespace scoped secret _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, c, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } // test deletion diff --git a/pkg/splunk/enterprise/upgrade.go b/pkg/splunk/enterprise/upgrade.go index ddea34bde..c1bcd8dc6 100644 --- a/pkg/splunk/enterprise/upgrade.go +++ b/pkg/splunk/enterprise/upgrade.go @@ -277,7 +277,7 @@ MonitoringConsole: if cm.Spec.MonitoringConsoleRef.Name == cr.GetName() { if cm.Status.Phase != enterpriseApi.PhaseReady { message := fmt.Sprintf("cluster manager %s is not ready", cm.Name) - return false, fmt.Errorf(message) + return false, fmt.Errorf("%s", message) } } } @@ -296,7 +296,7 @@ MonitoringConsole: if shc.Spec.MonitoringConsoleRef.Name == cr.GetName() { if shc.Status.Phase != enterpriseApi.PhaseReady { message := fmt.Sprintf("search head %s is not ready", shc.Name) - return false, fmt.Errorf(message) + return false, fmt.Errorf("%s", message) } } } @@ -315,7 +315,7 @@ MonitoringConsole: if idx.Name == cr.GetName() { if idx.Status.Phase != enterpriseApi.PhaseReady { message := fmt.Sprintf("indexer %s is not ready", idx.Name) - return false, fmt.Errorf(message) + return false, fmt.Errorf("%s", message) } } } @@ -334,7 +334,7 @@ MonitoringConsole: if stdln.Name == cr.GetName() { if stdln.Status.Phase != enterpriseApi.PhaseReady { message := fmt.Sprintf("standalone %s is not ready", stdln.Name) - return false, fmt.Errorf(message) + return false, fmt.Errorf("%s", message) } } } diff --git a/pkg/splunk/enterprise/util_test.go b/pkg/splunk/enterprise/util_test.go index fa407c203..4630ea216 100644 --- a/pkg/splunk/enterprise/util_test.go +++ b/pkg/splunk/enterprise/util_test.go @@ -450,14 +450,14 @@ func TestApplySmartstoreConfigMap(t *testing.T) { // Create namespace scoped secret secret, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } secret.Data[s3AccessKey] = []byte("abcdJDckRkxhMEdmSk5FekFRRzBFOXV6bGNldzJSWE9IenhVUy80aa") secret.Data[s3SecretKey] = []byte("g4NVp0a29PTzlPdGczWk1vekVUcVBSa0o4NkhBWWMvR1NadDV4YVEy") _, err = splctrl.ApplySecret(ctx, client, secret) if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } test := func(client *spltest.MockClient, cr splcommon.MetaObject, smartstore *enterpriseApi.SmartStoreSpec, want string) { @@ -518,14 +518,14 @@ func TestRemoveOwenerReferencesForSecretObjectsReferredBySmartstoreVolumes(t *te // Create namespace scoped secret secret, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } secret.Data[s3AccessKey] = []byte("abcdJDckRkxhMEdmSk5FekFRRzBFOXV6bGNldzJSWE9IenhVUy80aa") secret.Data[s3SecretKey] = []byte("g4NVp0a29PTzlPdGczWk1vekVUcVBSa0o4NkhBWWMvR1NadDV4YVEy") _, err = splctrl.ApplySecret(ctx, client, secret) if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } // Test existing secret @@ -593,12 +593,12 @@ func TestGetSmartstoreRemoteVolumeSecrets(t *testing.T) { // Just to simplify the test, assume that the keys are stored as part of the splunk-test-secret object, hence create that secret object secret, err := splutil.ApplyNamespaceScopedSecretObject(ctx, client, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } _, err = splctrl.ApplySecret(ctx, client, secret) if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } // Missing S3 access key should return error diff --git a/pkg/splunk/util/secrets_test.go b/pkg/splunk/util/secrets_test.go index 9a9f50913..0ace9ea2d 100644 --- a/pkg/splunk/util/secrets_test.go +++ b/pkg/splunk/util/secrets_test.go @@ -504,7 +504,7 @@ func TestGetNamespaceScopedSecret(t *testing.T) { // Create namespace scoped secret namespacescopedsecret, err := ApplyNamespaceScopedSecretObject(ctx, c, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } // Reconcile tester @@ -590,7 +590,7 @@ func TestGetExistingLatestVersionedSecret(t *testing.T) { // Get newer version newversion, err := (strconv.Atoi(splcommon.FirstVersion)) if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } newversion++ @@ -604,7 +604,7 @@ func TestGetExistingLatestVersionedSecret(t *testing.T) { } err = CreateResource(ctx, c, &secretv1) if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } // Create secret v2 @@ -617,7 +617,7 @@ func TestGetExistingLatestVersionedSecret(t *testing.T) { } err = CreateResource(ctx, c, &secretv2) if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } // List objects for mock client to pick up @@ -675,20 +675,20 @@ func TestGetLatestVersionedSecret(t *testing.T) { // Get newer version newversion, err := (strconv.Atoi(splcommon.FirstVersion)) if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } newversion++ // Create namespace scoped secret namespacescopedsecret, err := ApplyNamespaceScopedSecretObject(ctx, c, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } // Creates v1 v1Secret, err := GetLatestVersionedSecret(ctx, c, nil, "test", versionedSecretIdentifier) if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } if v1Secret.GetName() != "splunk-test-secret-v1" { @@ -711,7 +711,7 @@ func TestGetLatestVersionedSecret(t *testing.T) { // Retrieves v1 as there is no change in namespace scoped secret data v1SecretRetr, err := GetLatestVersionedSecret(ctx, c, nil, "test", versionedSecretIdentifier) if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } if v1SecretRetr.GetName() != "splunk-test-secret-v1" { @@ -726,13 +726,13 @@ func TestGetLatestVersionedSecret(t *testing.T) { namespacescopedsecret.Data["password"] = splcommon.GenerateSecret(splcommon.SecretBytes, 24) err = UpdateResource(context.TODO(), c, namespacescopedsecret) if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } // Creates v2, due to change in namespace scoped secret data v2Secret, err := GetLatestVersionedSecret(ctx, c, nil, "test", versionedSecretIdentifier) if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } if v2Secret.GetName() != "splunk-test-secret-v2" { @@ -766,12 +766,12 @@ func TestGetSplunkReadableNamespaceScopedSecretData(t *testing.T) { // Create a fully filled namespace scoped secrets object namespacescopedsecret, err := ApplyNamespaceScopedSecretObject(ctx, c, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } splunkReadableData, err := GetSplunkReadableNamespaceScopedSecretData(ctx, c, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } for _, tokenType := range splcommon.GetSplunkSecretTokenTypes() { @@ -826,13 +826,13 @@ func TestApplySplunkSecret(t *testing.T) { // Create a fully filled namespace scoped secrets object namespacescopedsecret, err := ApplyNamespaceScopedSecretObject(ctx, c, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } // Get namespaced scoped secret data in splunk readable format namespacescopedsecretData, err := GetSplunkReadableNamespaceScopedSecretData(ctx, c, "test") if err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } // Provide secret data @@ -1025,6 +1025,6 @@ func TestGetNamespaceScopedSecretByName(t *testing.T) { secret, err := GetSecretByName(ctx, c, cr.GetNamespace(), cr.GetName(), secretName) if secret == nil || err != nil { - t.Errorf(err.Error()) + t.Errorf("%s", err.Error()) } }