diff --git a/.github/workflows/build-apiserver.yaml b/.github/workflows/build-apiserver.yaml index dce37c4..d6328d6 100644 --- a/.github/workflows/build-apiserver.yaml +++ b/.github/workflows/build-apiserver.yaml @@ -46,6 +46,6 @@ jobs: with: bundle-name: ghcr.io/datum-cloud/search-kustomize bundle-path: config - image-overlays: config/base + image-overlays: config/overlays/core-control-plane image-name: ghcr.io/datum-cloud/search secrets: inherit diff --git a/Taskfile.yaml b/Taskfile.yaml index 1901a46..4fd02fe 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -122,37 +122,18 @@ tasks: # Generate RBAC rules for the controllers. - echo "Generating RBAC rules for the controllers..." - "\"{{.TOOL_DIR}}/controller-gen\" rbac:roleName=milo-controller-manager paths=\"./internal/controllers/...\" output:dir=\"./config/overlays/controller-manager/core-control-plane/rbac\"" + - "\"{{.TOOL_DIR}}/controller-gen\" rbac:roleName=milo-resource-indexer paths=\"./internal/indexer/...\" output:dir=\"./config/overlays/resource-indexer/core-control-plane/rbac\"" - task: generate:openapi silent: true generate:openapi: - desc: Generate OpenAPI definitions for search API types - deps: - - task: install-go-tool - vars: - NAME: openapi-gen - PACKAGE: k8s.io/code-generator/cmd/openapi-gen - VERSION: v0.23.0 + desc: Generate Kubernetes OpenAPI definitions cmds: - - echo "Generating OpenAPI definitions..." - | set -e - # Packages to generate OpenAPI for - PACKAGES=( - "pkg/apis/search/v1alpha1" - ) - - for REL_DIR in "${PACKAGES[@]}"; do - PKG="go.miloapis.net/search/$REL_DIR" - echo "Generating OpenAPI for $PKG..." - - "{{.TOOL_DIR}}/openapi-gen" \ - --input-dirs "$PKG,k8s.io/apimachinery/pkg/apis/meta/v1,k8s.io/apimachinery/pkg/runtime,k8s.io/apimachinery/pkg/version" \ - --output-package "$REL_DIR" \ - --output-base "." \ - --output-file-base "zz_generated.openapi" \ - --go-header-file "hack/boilerplate.go.txt" - done + echo "🔄 Generating Kubernetes OpenAPI definitions..." + ./hack/update-codegen.sh + echo "✅ OpenAPI generation complete" silent: true # Test tasks @@ -376,6 +357,7 @@ tasks: echo " Etcd: task test-infra:kubectl -- logs -l app.kubernetes.io/name=etcd -n etcd-system -f" echo " Search API Server: task test-infra:kubectl -- logs -l app.kubernetes.io/name=search-apiserver -n search-system -f" echo " Search Controller: task test-infra:kubectl -- logs -l app.kubernetes.io/name=search-controller-manager -n search-system -f" + echo " Search Indexer: task test-infra:kubectl -- logs -l app.kubernetes.io/name=resource-indexer -n search-system -f" dev:generate-webhook-certs: desc: Generate all certificates for webhook server @@ -409,7 +391,9 @@ tasks: --metrics-bind-address=:8085 \ --health-probe-bind-address=:8086 \ --leader-elect=false \ - --meilisearch-domain="http://127.0.0.1:7700" + --meilisearch-domain="http://127.0.0.1:7700" \ + --nats-url="nats://127.0.0.1:4222" + silent: true dev:run-indexer: @@ -422,21 +406,10 @@ tasks: # Trap to kill background processes (port-forward) on exit trap 'kill $(jobs -p)' EXIT - echo "🚀 Starting NATS port-forward in background..." - task dev:pf-nats > /dev/null 2>&1 & - - echo "🚀 Starting Meilisearch port-forward in background..." - task dev:pf-meilisearch > /dev/null 2>&1 & - - echo "Waiting for port-forward to be ready..." - sleep 5 - echo "🚀 Running indexer against local NATS..." MEILISEARCH_API_KEY=search-master-key \ go run ./cmd/search indexer \ --nats-url="nats://127.0.0.1:4222" \ - --nats-subject="audit.>" \ - --nats-queue-group="search-indexer" \ --nats-audit-consumer-name="search-indexer" \ --nats-reindex-consumer-name="search-reindexer" \ --nats-stream-name="AUDIT_EVENTS" \ @@ -472,6 +445,15 @@ tasks: - echo "Port forwarding Etcd to localhost:2379..." - task test-infra:kubectl -- port-forward -n etcd-system svc/etcd 2379:2379 + dev:pf-all: + desc: Port forward all dependencies for local development + cmds: + - | + task dev:pf-meilisearch & + task dev:pf-nats & + task dev:pf-etcd & + wait + dev:run-apiserver: desc: Run the API server locally (requires dev:pf-etcd running) cmds: @@ -495,6 +477,7 @@ tasks: - | # Use KUBECONFIG if set, otherwise fallback to default KCFG=${KUBECONFIG:-$HOME/.kube/config} + export MEILISEARCH_API_KEY=search-master-key go run ./cmd/search serve \ --etcd-servers http://127.0.0.1:2379 \ --secure-port 9443 \ @@ -504,7 +487,9 @@ tasks: --authorization-kubeconfig="$KCFG" \ --kubeconfig="$KCFG" \ --client-ca-file="{{.CERTS_DIR}}/kind-ca.crt" \ - --authorization-always-allow-paths=/healthz,/readyz,/livez,/openapi,/openapi/v2,/openapi/v3,/apis,/api + --authorization-always-allow-paths=/healthz,/readyz,/livez,/openapi,/openapi/v2,/openapi/v3,/apis,/api \ + --meilisearch-domain="http://127.0.0.1:7700" \ + dev:undeploy: desc: Undeploy Search server from test-infra cluster @@ -660,7 +645,6 @@ tasks: deps: - dev:build - dev:load - - dev:deploy cmds: - | set -e @@ -671,15 +655,19 @@ tasks: # Restart the deployment to pick up new image task test-infra:kubectl -- rollout restart deployment/search-apiserver -n search-system task test-infra:kubectl -- rollout restart deployment/search-controller-manager -n search-system + task test-infra:kubectl -- rollout restart deployment/resource-indexer -n search-system # Wait for rollout to complete echo "Waiting for rollout to complete..." + task test-infra:kubectl -- rollout status deployment/search-apiserver -n search-system --timeout=1000s task test-infra:kubectl -- rollout status deployment/search-controller-manager -n search-system --timeout=1000s + task test-infra:kubectl -- rollout status deployment/resource-indexer -n search-system --timeout=1000s echo "✅ Redeployment complete!" - echo "Check logs with: task test-infra:kubectl -- logs -n search-system -l app.kubernetes.io/name=search-controller-manager" + echo "Check pods with: task test-infra:kubectl -- get pods -n search-system" dev:nats-queue: desc: View the NATS queue for the indexer cmds: - - task test-infra:kubectl -- exec -n nats-system $(task test-infra:kubectl -- get pods -n nats-system -l app.kubernetes.io/component=nats-box -o jsonpath="{.items[0].metadata.name}") -- nats consumer info AUDIT_EVENTS search-indexer \ No newline at end of file + - task test-infra:kubectl -- exec -n nats-system $(task test-infra:kubectl -- get pods -n nats-system -l app.kubernetes.io/component=nats-box -o jsonpath="{.items[0].metadata.name}") -- nats consumer info AUDIT_EVENTS search-indexer + - task test-infra:kubectl -- exec -n nats-system $(task test-infra:kubectl -- get pods -n nats-system -l app.kubernetes.io/component=nats-box -o jsonpath="{.items[0].metadata.name}") -- nats consumer info REINDEX_EVENTS search-reindexer \ No newline at end of file diff --git a/cmd/search/indexer/command.go b/cmd/search/indexer/command.go index 43ee4ba..a0142a9 100644 --- a/cmd/search/indexer/command.go +++ b/cmd/search/indexer/command.go @@ -17,6 +17,7 @@ import ( "k8s.io/klog/v2" runtimecache "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client/config" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" ) // ResourceIndexerOptions holds the configuration for the resource indexer. @@ -25,6 +26,9 @@ type ResourceIndexerOptions struct { NatsURL string NatsAuditConsumerName string NatsStreamName string + NatsTLSCA string + NatsTLSCert string + NatsTLSKey string // NATS re-index consumer settings (separate REINDEX_EVENTS stream) NatsReindexStream string @@ -72,6 +76,9 @@ func (o *ResourceIndexerOptions) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&o.NatsReindexStream, "nats-reindex-stream", o.NatsReindexStream, "The JetStream stream name for re-index messages.") fs.StringVar(&o.NatsReindexConsumerName, "nats-reindex-consumer-name", o.NatsReindexConsumerName, "The name of the re-index JetStream consumer (must match the manifest).") + fs.StringVar(&o.NatsTLSCA, "nats-tls-ca", o.NatsTLSCA, "The path to the NATS TLS CA file.") + fs.StringVar(&o.NatsTLSCert, "nats-tls-cert", o.NatsTLSCert, "The path to the NATS TLS certificate file.") + fs.StringVar(&o.NatsTLSKey, "nats-tls-key", o.NatsTLSKey, "The path to the NATS TLS key file.") fs.StringVar(&o.MeilisearchDomain, "meilisearch-domain", o.MeilisearchDomain, "Domain of the Meilisearch instance.") fs.DurationVar(&o.MeilisearchTaskWaitTimeout, "meilisearch-task-wait-timeout", o.MeilisearchTaskWaitTimeout, "Timeout for waiting for Meilisearch tasks to complete.") @@ -159,6 +166,7 @@ func NewIndexerCommand() *cobra.Command { // Run starts the indexer consumer func Run(o *ResourceIndexerOptions, ctx context.Context) error { + ctrllog.SetLogger(klog.NewKlogr()) // Build a scheme and REST config for the controller-runtime cache. scheme := runtime.NewScheme() if err := searchv1alpha1.AddToScheme(scheme); err != nil { @@ -188,21 +196,42 @@ func Run(o *ResourceIndexerOptions, ctx context.Context) error { return fmt.Errorf("failed to create policy cache: %w", err) } - go func() { - if err := indexPolicyCache.Start(ctx); err != nil { - klog.Errorf("Index Policy cache stopped: %v", err) - } - }() + // Register handlers for both caches. They share the same underlying informer. + if err := indexPolicyCache.RegisterHandlers(ctx); err != nil { + return fmt.Errorf("failed to register index policy handlers: %w", err) + } + if err := reindexPolicyCache.RegisterHandlers(ctx); err != nil { + return fmt.Errorf("failed to register reindex policy handlers: %w", err) + } + // Start the shared cache and wait for it to be synced. go func() { - if err := reindexPolicyCache.Start(ctx); err != nil { - klog.Errorf("Reindex Policy cache stopped: %v", err) + klog.Info("Starting shared Kubernetes cache...") + if err := k8sCache.Start(ctx); err != nil { + klog.Fatalf("Kubernetes cache stopped with error: %v", err) } }() + klog.Info("Waiting for cache to sync...") + if !k8sCache.WaitForCacheSync(ctx) { + return fmt.Errorf("failed to sync Kubernetes cache") + } + klog.Info("Cache synced successfully") + // Connect to NATS klog.Infof("Connecting to NATS at %s...", o.NatsURL) - nc, err := nats.Connect(o.NatsURL) + + var natsOpts []nats.Option + if o.NatsTLSCert != "" && o.NatsTLSKey != "" { + if o.NatsTLSCA != "" { + klog.Infof("Using NATS TLS CA from %s", o.NatsTLSCA) + natsOpts = append(natsOpts, nats.RootCAs(o.NatsTLSCA)) + } + klog.Infof("Using NATS TLS cert from %s and key from %s", o.NatsTLSCert, o.NatsTLSKey) + natsOpts = append(natsOpts, nats.ClientCert(o.NatsTLSCert, o.NatsTLSKey)) + } + + nc, err := nats.Connect(o.NatsURL, natsOpts...) if err != nil { return fmt.Errorf("failed to connect to NATS: %w", err) } diff --git a/cmd/search/main.go b/cmd/search/main.go index d1272f6..0cfb8e2 100644 --- a/cmd/search/main.go +++ b/cmd/search/main.go @@ -4,13 +4,17 @@ import ( "context" "fmt" "os" - "strings" + "time" + "github.com/google/uuid" "github.com/spf13/cobra" "github.com/spf13/pflag" + searchapiserver "go.miloapis.net/search/internal/apiserver" "go.miloapis.net/search/internal/version" searchv1alpha1 "go.miloapis.net/search/pkg/apis/search/v1alpha1" + "go.miloapis.net/search/pkg/generated/openapi" + "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" apiopenapi "k8s.io/apiserver/pkg/endpoints/openapi" genericapiserver "k8s.io/apiserver/pkg/server" @@ -21,10 +25,15 @@ import ( "k8s.io/component-base/logs" logsapi "k8s.io/component-base/logs/api/v1" "k8s.io/klog/v2" - "k8s.io/kube-openapi/pkg/common" + openapiutil "k8s.io/kube-openapi/pkg/util" + "k8s.io/kube-openapi/pkg/validation/spec" + runtimecache "sigs.k8s.io/controller-runtime/pkg/cache" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" "go.miloapis.net/search/cmd/search/indexer" "go.miloapis.net/search/cmd/search/manager" + internalindexer "go.miloapis.net/search/internal/indexer" + "go.miloapis.net/search/pkg/meilisearch" // Register JSON logging format _ "k8s.io/component-base/logs/json/register" @@ -36,31 +45,6 @@ func init() { utilfeature.DefaultMutableFeatureGate.Set("RemoteRequestHeaderUID=true") } -func GetOpenAPIDefinitions(cb common.ReferenceCallback) map[string]common.OpenAPIDefinition { - defs := make(map[string]common.OpenAPIDefinition) - - merge := func(pkgDefs map[string]common.OpenAPIDefinition) { - for k, v := range pkgDefs { - // For k8s.io types, store both the original key and the transformed key - // because the namer behavior is inconsistent across different types - if strings.HasPrefix(k, "k8s.io/") { - // Store original key (with slashes) - defs[k] = v - // Also store transformed key (io.k8s with dots) - newK := "io.k8s." + k[7:] - newK = strings.ReplaceAll(newK, "/", ".") - defs[newK] = v - } else { - // For non-k8s.io types, keep as-is - defs[k] = v - } - } - } - - merge(searchv1alpha1.GetOpenAPIDefinitions(cb)) - return defs -} - func main() { cmd := NewSearchServerCommand() code := cli.Run(cmd) @@ -140,8 +124,14 @@ func NewVersionCommand() *cobra.Command { // SearchServerOptions contains configuration for the search server. type SearchServerOptions struct { - RecommendedOptions *options.RecommendedOptions - Logs *logsapi.LoggingConfiguration + RecommendedOptions *options.RecommendedOptions + Logs *logsapi.LoggingConfiguration + MeilisearchDomain string + MeilisearchHTTPTimeout time.Duration + MeilisearchTaskWaitTimeout time.Duration + MaxSearchLimit int + DefaultSearchLimit int + PagingTimeout time.Duration } // NewSearchServerOptions creates options with default values. @@ -151,7 +141,12 @@ func NewSearchServerOptions() *SearchServerOptions { "/registry/search.miloapis.com", searchapiserver.Codecs.LegacyCodec(searchapiserver.Scheme.PrioritizedVersionsAllGroups()...), ), - Logs: logsapi.NewLoggingConfiguration(), + Logs: logsapi.NewLoggingConfiguration(), + MeilisearchDomain: "http://meilisearch.meilisearch-system.svc.cluster.local:7700", + MeilisearchHTTPTimeout: 60 * time.Second, + MaxSearchLimit: 100, + DefaultSearchLimit: 10, + PagingTimeout: 24 * time.Hour, } return o @@ -159,6 +154,10 @@ func NewSearchServerOptions() *SearchServerOptions { func (o *SearchServerOptions) AddFlags(fs *pflag.FlagSet) { o.RecommendedOptions.AddFlags(fs) + fs.StringVar(&o.MeilisearchDomain, "meilisearch-domain", o.MeilisearchDomain, "Domain of the Meilisearch instance.") + fs.IntVar(&o.MaxSearchLimit, "max-search-limit", o.MaxSearchLimit, "The maximum number of results a SearchQuery can return in a single request.") + fs.IntVar(&o.DefaultSearchLimit, "default-search-limit", o.DefaultSearchLimit, "The default number of results a SearchQuery returns when no limit is specified.") + fs.DurationVar(&o.PagingTimeout, "paging-timeout", o.PagingTimeout, "The duration for which a paging (continue) token is valid.") } func (o *SearchServerOptions) Complete() error { @@ -181,25 +180,77 @@ func (o *SearchServerOptions) Config() (*searchapiserver.Config, error) { genericConfig := genericapiserver.NewRecommendedConfig(searchapiserver.Codecs) // Set effective version to match the Kubernetes version we're built against. - genericConfig.EffectiveVersion = basecompatibility.NewEffectiveVersionFromString("1.34", "", "") + genericConfig.EffectiveVersion = basecompatibility.NewEffectiveVersionFromString("1.35", "", "") namer := apiopenapi.NewDefinitionNamer(searchapiserver.Scheme) - genericConfig.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config(GetOpenAPIDefinitions, namer) + genericConfig.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config(openapi.GetOpenAPIDefinitions, namer) genericConfig.OpenAPIV3Config.Info.Title = "Search" genericConfig.OpenAPIV3Config.Info.Version = version.Version + genericConfig.OpenAPIV3Config.GetDefinitionName = func(name string) (string, spec.Extensions) { + friendlyName, extensions := namer.GetDefinitionName(name) + return openapiutil.ToRESTFriendlyName(friendlyName), extensions + } // Configure OpenAPI v2 - genericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(GetOpenAPIDefinitions, namer) + genericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(openapi.GetOpenAPIDefinitions, namer) genericConfig.OpenAPIConfig.Info.Title = "Search" genericConfig.OpenAPIConfig.Info.Version = version.Version + genericConfig.OpenAPIConfig.GetDefinitionName = func(name string) (string, spec.Extensions) { + friendlyName, extensions := namer.GetDefinitionName(name) + return openapiutil.ToRESTFriendlyName(friendlyName), extensions + } if err := o.RecommendedOptions.ApplyTo(genericConfig); err != nil { return nil, fmt.Errorf("failed to apply recommended options: %w", err) } + meiliClient, err := meilisearch.NewSDKClient(meilisearch.SDKConfig{ + Domain: o.MeilisearchDomain, + APIKey: os.Getenv("MEILISEARCH_API_KEY"), + WaitTimeout: o.MeilisearchTaskWaitTimeout, + HTTPTimeout: o.MeilisearchHTTPTimeout, + }) + if err != nil { + return nil, fmt.Errorf("failed to initialize meilisearch client: %w", err) + } + + // Create a dedicated scheme for the policy cache that only contains versioned types. + // This avoids ambiguity when the main scheme registers the same type for both + // v1alpha1 and __internal versions. + policyScheme := runtime.NewScheme() + if err := searchv1alpha1.AddToScheme(policyScheme); err != nil { + return nil, fmt.Errorf("failed to add v1alpha1 to policy scheme: %w", err) + } + + // Create a controller-runtime cache that uses a watch stream (informer) + // to keep ResourceIndexPolicies in-sync. + k8sCache, err := runtimecache.New(genericConfig.LoopbackClientConfig, runtimecache.Options{Scheme: policyScheme}) + if err != nil { + return nil, fmt.Errorf("failed to create controller-runtime cache: %w", err) + } + + // Create the policy cache (strict ready checking enabled) + indexPolicyCache, err := internalindexer.NewPolicyCache(k8sCache, true) + if err != nil { + return nil, fmt.Errorf("failed to create policy cache: %w", err) + } + + pagingSecret := []byte(os.Getenv("SEARCH_PAGING_SECRET")) + if len(pagingSecret) == 0 { + klog.Info("SEARCH_PAGING_SECRET not set, generating a random one") + pagingSecret = []byte(uuid.New().String()) + } + serverConfig := &searchapiserver.Config{ GenericConfig: genericConfig, - ExtraConfig: searchapiserver.ExtraConfig{}, + ExtraConfig: searchapiserver.ExtraConfig{ + MeiliClient: meiliClient, + PolicyCache: indexPolicyCache, + MaxSearchLimit: o.MaxSearchLimit, + DefaultSearchLimit: o.DefaultSearchLimit, + PagingSecret: pagingSecret, + PagingTimeout: o.PagingTimeout, + }, } return serverConfig, nil @@ -211,6 +262,8 @@ func Run(options *SearchServerOptions, ctx context.Context) error { return fmt.Errorf("failed to apply logging configuration: %w", err) } + ctrllog.SetLogger(klog.NewKlogr()) + config, err := options.Config() if err != nil { return err diff --git a/cmd/search/manager/command.go b/cmd/search/manager/command.go index e1a245a..47fe05c 100644 --- a/cmd/search/manager/command.go +++ b/cmd/search/manager/command.go @@ -43,6 +43,7 @@ func init() { type ControllerManagerOptions struct { MetricsAddr string EnableLeaderElection bool + LeaderElectionNamespace string ProbeAddr string SecureMetrics bool EnableHTTP2 bool @@ -55,6 +56,9 @@ type ControllerManagerOptions struct { // NATS settings for publishing per-resource re-index messages. NatsURL string NatsReindexSubject string + NatsTLSCA string + NatsTLSCert string + NatsTLSKey string } // NewControllerManagerOptions creates a new ControllerManagerOptions with default values @@ -63,6 +67,7 @@ func NewControllerManagerOptions() *ControllerManagerOptions { MetricsAddr: ":8080", ProbeAddr: ":8081", EnableLeaderElection: true, + LeaderElectionNamespace: "", SecureMetrics: false, EnableHTTP2: false, MaxCELDepth: 50, @@ -82,6 +87,8 @@ func (o *ControllerManagerOptions) AddFlags(fs *pflag.FlagSet) { fs.BoolVar(&o.EnableLeaderElection, "leader-elect", o.EnableLeaderElection, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") + fs.StringVar(&o.LeaderElectionNamespace, "leader-elect-resource-namespace", o.LeaderElectionNamespace, + "The namespace in which the leader election resource will be created.") fs.BoolVar(&o.SecureMetrics, "metrics-secure", o.SecureMetrics, "If set the metrics endpoint is served securely") fs.BoolVar(&o.EnableHTTP2, "enable-http2", o.EnableHTTP2, @@ -97,6 +104,9 @@ func (o *ControllerManagerOptions) AddFlags(fs *pflag.FlagSet) { // NATS fs.StringVar(&o.NatsURL, "nats-url", o.NatsURL, "The URL of the NATS server used to publish re-index messages.") fs.StringVar(&o.NatsReindexSubject, "nats-reindex-subject", o.NatsReindexSubject, "The NATS subject to publish per-resource re-index messages to.") + fs.StringVar(&o.NatsTLSCA, "nats-tls-ca", o.NatsTLSCA, "The path to the NATS TLS CA file.") + fs.StringVar(&o.NatsTLSCert, "nats-tls-cert", o.NatsTLSCert, "The path to the NATS TLS certificate file.") + fs.StringVar(&o.NatsTLSKey, "nats-tls-key", o.NatsTLSKey, "The path to the NATS TLS key file.") } // Validate validates the options @@ -167,11 +177,12 @@ func Run(o *ControllerManagerOptions, ctx context.Context) error { cfg := ctrl.GetConfigOrDie() mgr, err := ctrl.NewManager(cfg, ctrl.Options{ - Scheme: scheme, - Metrics: metricsserver.Options{BindAddress: o.MetricsAddr, SecureServing: o.SecureMetrics, TLSOpts: tlsOpts}, - HealthProbeBindAddress: o.ProbeAddr, - LeaderElection: o.EnableLeaderElection, - LeaderElectionID: "controller.search.miloapis.com", + Scheme: scheme, + Metrics: metricsserver.Options{BindAddress: o.MetricsAddr, SecureServing: o.SecureMetrics, TLSOpts: tlsOpts}, + HealthProbeBindAddress: o.ProbeAddr, + LeaderElection: o.EnableLeaderElection, + LeaderElectionID: "controller.search.miloapis.com", + LeaderElectionNamespace: o.LeaderElectionNamespace, }) if err != nil { setupLog.Error(err, "unable to start manager") @@ -205,7 +216,19 @@ func Run(o *ControllerManagerOptions, ctx context.Context) error { // Connect to NATS and set up the re-index stream + publisher. setupLog.Info("Connecting to NATS for re-index publishing", "url", o.NatsURL) - nc, err := nats.Connect(o.NatsURL) + + var natsOpts []nats.Option + if o.NatsTLSCert != "" && o.NatsTLSKey != "" { + if o.NatsTLSCA != "" { + setupLog.Info("Using NATS TLS CA", "ca", o.NatsTLSCA) + natsOpts = append(natsOpts, nats.RootCAs(o.NatsTLSCA)) + } + setupLog.Info("Using NATS TLS cert", "cert", o.NatsTLSCert) + setupLog.Info("Using NATS TLS key", "key", o.NatsTLSKey) + natsOpts = append(natsOpts, nats.ClientCert(o.NatsTLSCert, o.NatsTLSKey)) + } + + nc, err := nats.Connect(o.NatsURL, natsOpts...) if err != nil { setupLog.Error(err, "unable to connect to NATS") os.Exit(1) diff --git a/config/base/apiserver/deployment.yaml b/config/base/apiserver/deployment.yaml index a00ee93..b8ebe46 100644 --- a/config/base/apiserver/deployment.yaml +++ b/config/base/apiserver/deployment.yaml @@ -58,8 +58,9 @@ spec: - --requestheader-extra-headers-prefix=$(REQUESTHEADER_EXTRA_HEADERS_PREFIX) - --logging-format=$(LOGGING_FORMAT) - --tracing-config-file=$(TRACING_CONFIG_FILE) - - -v=$(LOG_LEVEL) + - --v=$(LOG_LEVEL) - --etcd-servers=$(ETCD_SERVERS) + - --meilisearch-domain=$(MEILISEARCH_DOMAIN) env: - name: ETCD_SERVERS value: "http://etcd.etcd-system.svc.cluster.local:2379" @@ -97,6 +98,11 @@ spec: value: "4" - name: TRACING_CONFIG_FILE value: "" + - name: MEILISEARCH_DOMAIN + value: "http://meilisearch.meilisearch-system.svc.cluster.local:7700" + envFrom: + - secretRef: + name: search-apiserver # TEMPLATE NOTE: Add your service-specific environment variables here # Example: database credentials, API keys, configuration values resources: diff --git a/config/base/controller-manager/deployment.yaml b/config/base/controller-manager/deployment.yaml index bbf9c06..95b5eee 100644 --- a/config/base/controller-manager/deployment.yaml +++ b/config/base/controller-manager/deployment.yaml @@ -43,6 +43,11 @@ spec: - --meilisearch-domain=$(MEILISEARCH_DOMAIN) - --meilisearch-task-wait-timeout=$(MEILISEARCH_TASK_WAIT_TIMEOUT) - --max-cel-depth=$(MAX_CEL_DEPTH) + - --nats-url=$(NATS_URL) + - --nats-tls-ca=$(NATS_TLS_CA) + - --nats-tls-cert=$(NATS_TLS_CERT) + - --nats-tls-key=$(NATS_TLS_KEY) + - --leader-elect-resource-namespace=$(LEADER_ELECT_RESOURCE_NAMESPACE) env: - name: POD_NAMESPACE valueFrom: @@ -62,6 +67,16 @@ spec: value: "30s" - name: MAX_CEL_DEPTH value: "10" + - name: NATS_URL + value: "nats://nats.nats-system.svc.cluster.local:4222" + - name: NATS_TLS_CA + value: "" + - name: NATS_TLS_CERT + value: "" + - name: NATS_TLS_KEY + value: "" + - name: LEADER_ELECT_RESOURCE_NAMESPACE + value: "" - name: MEILISEARCH_API_KEY valueFrom: secretKeyRef: diff --git a/config/base/resource-indexer/deployment.yaml b/config/base/resource-indexer/deployment.yaml index 19cba16..e7bef33 100644 --- a/config/base/resource-indexer/deployment.yaml +++ b/config/base/resource-indexer/deployment.yaml @@ -2,7 +2,6 @@ apiVersion: apps/v1 kind: Deployment metadata: name: resource-indexer - namespace: search-system labels: app.kubernetes.io/name: resource-indexer app.kubernetes.io/component: indexer @@ -23,9 +22,21 @@ spec: command: - /search - indexer + args: + - --nats-url=$(NATS_URL) + - --nats-tls-ca=$(NATS_TLS_CA) + - --nats-tls-cert=$(NATS_TLS_CERT) + - --nats-tls-key=$(NATS_TLS_KEY) + - --meilisearch-domain=$(MEILISEARCH_DOMAIN) env: - name: NATS_URL value: "nats://nats.nats-system.svc.cluster.local:4222" + - name: NATS_TLS_CA + value: "" + - name: NATS_TLS_CERT + value: "" + - name: NATS_TLS_KEY + value: "" - name: NATS_Subject value: "audit.>" - name: NATS_QUEUE_GROUP @@ -34,6 +45,8 @@ spec: value: "search-indexer" - name: NATS_STREAM_NAME value: "AUDIT_EVENTS" + - name: MEILISEARCH_DOMAIN + value: "http://meilisearch.meilisearch-system.svc.cluster.local:7700" envFrom: - secretRef: name: search-indexer diff --git a/config/dependencies/meilisearch/helmrelease.yaml b/config/dependencies/meilisearch/helmrelease.yaml index 2482c09..def63de 100644 --- a/config/dependencies/meilisearch/helmrelease.yaml +++ b/config/dependencies/meilisearch/helmrelease.yaml @@ -10,7 +10,7 @@ spec: chart: spec: chart: meilisearch - version: "0.24.0" + version: "0.26.0" sourceRef: kind: HelmRepository name: meilisearch diff --git a/config/milo/iam/resources/kustomization.yaml b/config/milo/iam/resources/kustomization.yaml index 1b069a7..2302dfc 100644 --- a/config/milo/iam/resources/kustomization.yaml +++ b/config/milo/iam/resources/kustomization.yaml @@ -3,3 +3,4 @@ kind: Component resources: - searchqueries.yaml + - resourceindexpolicies.yaml diff --git a/config/milo/iam/resources/resourceindexpolicies.yaml b/config/milo/iam/resources/resourceindexpolicies.yaml new file mode 100644 index 0000000..2bc9e51 --- /dev/null +++ b/config/milo/iam/resources/resourceindexpolicies.yaml @@ -0,0 +1,18 @@ +apiVersion: iam.miloapis.com/v1alpha1 +kind: ProtectedResource +metadata: + name: search.miloapis.com-resourceindexpolicies +spec: + serviceRef: + name: "search.miloapis.com" + kind: ResourceIndexPolicy + plural: resourceindexpolicies + singular: resourceindexpolicy + permissions: + - list + - get + - create + - update + - delete + - patch + - watch diff --git a/config/milo/iam/resources/searchqueries.yaml b/config/milo/iam/resources/searchqueries.yaml index 072e2d8..160422c 100644 --- a/config/milo/iam/resources/searchqueries.yaml +++ b/config/milo/iam/resources/searchqueries.yaml @@ -1,8 +1,3 @@ -# TEMPLATE NOTE: This is an example of integrating with Milo IAM -# Milo provides IAM capabilities for Kubernetes APIs -# See https://github.com/datum-cloud/milo for more information -# -# This ProtectedResource grants permissions for SearchQuery objects apiVersion: iam.miloapis.com/v1alpha1 kind: ProtectedResource metadata: @@ -12,17 +7,6 @@ spec: name: "search.miloapis.com" kind: SearchQuery plural: searchqueries - singular: exampleresource + singular: searchquery permissions: - create - - get - - list - - update - - delete - parentResources: - - apiGroup: resourcemanager.miloapis.com - kind: Organization - - apiGroup: resourcemanager.miloapis.com - kind: Project - - apiGroup: iam.miloapis.com - kind: User diff --git a/config/milo/iam/roles/admin.yaml b/config/milo/iam/roles/admin.yaml new file mode 100644 index 0000000..5736aca --- /dev/null +++ b/config/milo/iam/roles/admin.yaml @@ -0,0 +1,15 @@ +apiVersion: iam.miloapis.com/v1alpha1 +kind: Role +metadata: + name: search.miloapis.com-admin + namespace: milo-system + labels: + app.kubernetes.io/name: admin + app.kubernetes.io/part-of: search.miloapis.com +spec: + launchStage: Beta + inheritedRoles: + - name: search.miloapis.com-editor + namespace: milo-system + includedPermissions: + - search.miloapis.com/resourceindexpolicies.delete diff --git a/config/milo/iam/roles/editor.yaml b/config/milo/iam/roles/editor.yaml new file mode 100644 index 0000000..5c1f20c --- /dev/null +++ b/config/milo/iam/roles/editor.yaml @@ -0,0 +1,17 @@ +apiVersion: iam.miloapis.com/v1alpha1 +kind: Role +metadata: + name: search.miloapis.com-editor + namespace: milo-system + labels: + app.kubernetes.io/name: editor + app.kubernetes.io/part-of: search.miloapis.com +spec: + launchStage: Beta + inheritedRoles: + - name: search.miloapis.com-viewer + namespace: milo-system + includedPermissions: + - search.miloapis.com/resourceindexpolicies.create + - search.miloapis.com/resourceindexpolicies.update + - search.miloapis.com/resourceindexpolicies.patch diff --git a/config/milo/iam/roles/kustomization.yaml b/config/milo/iam/roles/kustomization.yaml index 4926b49..8428c3b 100644 --- a/config/milo/iam/roles/kustomization.yaml +++ b/config/milo/iam/roles/kustomization.yaml @@ -3,3 +3,6 @@ kind: Component resources: - searcher.yaml + - viewer.yaml + - editor.yaml + - admin.yaml diff --git a/config/milo/iam/roles/searcher.yaml b/config/milo/iam/roles/searcher.yaml index 9bd08ac..e6c20e0 100644 --- a/config/milo/iam/roles/searcher.yaml +++ b/config/milo/iam/roles/searcher.yaml @@ -3,7 +3,10 @@ kind: Role metadata: name: search.miloapis.com-searcher namespace: milo-system + labels: + app.kubernetes.io/name: searcher + app.kubernetes.io/part-of: search.miloapis.com spec: - launchStage: Alpha + launchStage: Beta includedPermissions: - search.miloapis.com/searchqueries.create diff --git a/config/milo/iam/roles/viewer.yaml b/config/milo/iam/roles/viewer.yaml new file mode 100644 index 0000000..37f9202 --- /dev/null +++ b/config/milo/iam/roles/viewer.yaml @@ -0,0 +1,17 @@ +apiVersion: iam.miloapis.com/v1alpha1 +kind: Role +metadata: + name: search.miloapis.com-viewer + namespace: milo-system + labels: + app.kubernetes.io/name: viewer + app.kubernetes.io/part-of: search.miloapis.com +spec: + launchStage: Beta + inheritedRoles: + - name: search.miloapis.com-searcher + namespace: milo-system + includedPermissions: + - search.miloapis.com/resourceindexpolicies.get + - search.miloapis.com/resourceindexpolicies.list + - search.miloapis.com/resourceindexpolicies.watch diff --git a/config/overlays/ci/kustomization.yaml b/config/overlays/ci/kustomization.yaml index 534b9c4..1fd8002 100644 --- a/config/overlays/ci/kustomization.yaml +++ b/config/overlays/ci/kustomization.yaml @@ -5,9 +5,10 @@ namespace: search-system resources: - ../../base/apiserver - - ../../base/resource-indexer # Controller manager with RBAC - ../controller-manager/core-control-plane + # Resource indexer with RBAC + - ../resource-indexer/core-control-plane # Include components for CI environment components: @@ -61,3 +62,6 @@ secretGenerator: - name: search-indexer literals: - MEILISEARCH_API_KEY=search-master-key +- name: search-apiserver + literals: + - MEILISEARCH_API_KEY=search-master-key diff --git a/config/overlays/controller-manager/core-control-plane/rbac/role.yaml b/config/overlays/controller-manager/core-control-plane/rbac/role.yaml index e591513..8b0e09a 100644 --- a/config/overlays/controller-manager/core-control-plane/rbac/role.yaml +++ b/config/overlays/controller-manager/core-control-plane/rbac/role.yaml @@ -4,6 +4,14 @@ kind: ClusterRole metadata: name: milo-controller-manager rules: +- apiGroups: + - '*' + resources: + - '*' + verbs: + - get + - list + - watch - apiGroups: - search.miloapis.com resources: diff --git a/config/overlays/core-control-plane/kustomization.yaml b/config/overlays/core-control-plane/kustomization.yaml new file mode 100644 index 0000000..cdb380d --- /dev/null +++ b/config/overlays/core-control-plane/kustomization.yaml @@ -0,0 +1,10 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ../../base/apiserver + - ../../base/resource-indexer + - ../controller-manager/core-control-plane + +images: + - name: ghcr.io/datum-cloud/search + newTag: latest diff --git a/config/overlays/dev/kustomization.yaml b/config/overlays/dev/kustomization.yaml index 44864ef..bb0690c 100644 --- a/config/overlays/dev/kustomization.yaml +++ b/config/overlays/dev/kustomization.yaml @@ -5,9 +5,10 @@ namespace: search-system resources: - ../../base/apiserver - - ../../base/resource-indexer # Controller manager with RBAC - ../controller-manager/core-control-plane + # Resource indexer with RBAC + - ../resource-indexer/core-control-plane # Include components for development environment components: @@ -51,4 +52,6 @@ secretGenerator: - name: search-indexer literals: - MEILISEARCH_API_KEY=search-master-key - +- name: search-apiserver + literals: + - MEILISEARCH_API_KEY=search-master-key diff --git a/config/overlays/resource-indexer/core-control-plane/kustomization.yaml b/config/overlays/resource-indexer/core-control-plane/kustomization.yaml new file mode 100644 index 0000000..310405f --- /dev/null +++ b/config/overlays/resource-indexer/core-control-plane/kustomization.yaml @@ -0,0 +1,14 @@ +# This overlay is used to install the resource indexer into the core control +# plane. +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + # Install the base resource indexer resources. + - ../../../base/resource-indexer + # Install the RBAC resources for the resource indexer that are specific to + # the core control plane. + - rbac + +patches: +- path: patches/deployment.yaml diff --git a/config/overlays/resource-indexer/core-control-plane/patches/deployment.yaml b/config/overlays/resource-indexer/core-control-plane/patches/deployment.yaml new file mode 100644 index 0000000..e53e127 --- /dev/null +++ b/config/overlays/resource-indexer/core-control-plane/patches/deployment.yaml @@ -0,0 +1,9 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: resource-indexer +spec: + template: + spec: + serviceAccountName: resource-indexer + automountServiceAccountToken: true diff --git a/config/overlays/resource-indexer/core-control-plane/rbac/kustomization.yaml b/config/overlays/resource-indexer/core-control-plane/rbac/kustomization.yaml new file mode 100644 index 0000000..8da1330 --- /dev/null +++ b/config/overlays/resource-indexer/core-control-plane/rbac/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- role.yaml +- rolebinding.yaml +- service-account.yaml diff --git a/config/overlays/resource-indexer/core-control-plane/rbac/role.yaml b/config/overlays/resource-indexer/core-control-plane/rbac/role.yaml new file mode 100644 index 0000000..53fbff1 --- /dev/null +++ b/config/overlays/resource-indexer/core-control-plane/rbac/role.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: milo-resource-indexer +rules: +- apiGroups: + - search.miloapis.com + resources: + - resourceindexpolicies + - resourceindexpolicies/status + verbs: + - get + - list + - watch diff --git a/config/overlays/resource-indexer/core-control-plane/rbac/rolebinding.yaml b/config/overlays/resource-indexer/core-control-plane/rbac/rolebinding.yaml new file mode 100644 index 0000000..0321428 --- /dev/null +++ b/config/overlays/resource-indexer/core-control-plane/rbac/rolebinding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: milo-resource-indexer +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: milo-resource-indexer +subjects: +- kind: ServiceAccount + name: resource-indexer + namespace: search-system diff --git a/config/overlays/resource-indexer/core-control-plane/rbac/service-account.yaml b/config/overlays/resource-indexer/core-control-plane/rbac/service-account.yaml new file mode 100644 index 0000000..a402824 --- /dev/null +++ b/config/overlays/resource-indexer/core-control-plane/rbac/service-account.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: resource-indexer + labels: + app.kubernetes.io/name: resource-indexer + app.kubernetes.io/part-of: search-control-plane diff --git a/config/samples/search_v1alpha1_searchquery.yaml b/config/samples/search_v1alpha1_searchquery.yaml new file mode 100644 index 0000000..653702f --- /dev/null +++ b/config/samples/search_v1alpha1_searchquery.yaml @@ -0,0 +1,12 @@ +apiVersion: search.miloapis.com/v1alpha1 +kind: SearchQuery +metadata: + name: configmap-search-sample +spec: + targetResources: + - group: "" + version: v1 + kind: ConfigMap + query: "" # Empty query returns everything, or put a specific string + limit: 20 + offset: 0 diff --git a/docs/diagrams/ResourceIndexerScaling.png b/docs/diagrams/ResourceIndexerScaling.png index 6f96d33..8e6355f 100644 Binary files a/docs/diagrams/ResourceIndexerScaling.png and b/docs/diagrams/ResourceIndexerScaling.png differ diff --git a/docs/diagrams/SearchServiceContainers.png b/docs/diagrams/SearchServiceContainers.png index c038821..f2900b7 100644 Binary files a/docs/diagrams/SearchServiceContainers.png and b/docs/diagrams/SearchServiceContainers.png differ diff --git a/docs/diagrams/SearchServiceContext.png b/docs/diagrams/SearchServiceContext.png index 9636bc4..0e6f48f 100644 Binary files a/docs/diagrams/SearchServiceContext.png and b/docs/diagrams/SearchServiceContext.png differ diff --git a/go.mod b/go.mod index 026420c..6f978ca 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.25.0 require ( github.com/go-logr/logr v1.4.3 + github.com/golang-jwt/jwt/v5 v5.3.1 github.com/google/cel-go v0.27.0 github.com/google/uuid v1.6.0 github.com/meilisearch/meilisearch-go v0.36.1 @@ -11,11 +12,11 @@ require ( github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 - k8s.io/api v0.35.1 - k8s.io/apimachinery v0.35.1 - k8s.io/apiserver v0.35.1 - k8s.io/client-go v0.35.1 - k8s.io/component-base v0.35.1 + k8s.io/api v0.35.2 + k8s.io/apimachinery v0.35.2 + k8s.io/apiserver v0.35.2 + k8s.io/client-go v0.35.2 + k8s.io/component-base v0.35.2 k8s.io/klog/v2 v2.130.1 k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4 sigs.k8s.io/controller-runtime v0.23.1 @@ -45,7 +46,6 @@ require ( github.com/go-openapi/swag v0.23.0 // indirect github.com/go-openapi/swag/jsonname v0.25.4 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.7.0 // indirect @@ -68,7 +68,6 @@ require ( github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/procfs v0.16.1 // indirect - github.com/stoewer/go-strcase v1.3.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/x448/float16 v0.8.4 // indirect go.etcd.io/etcd/api/v3 v3.6.5 // indirect @@ -109,9 +108,9 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.35.0 // indirect - k8s.io/code-generator v0.35.1 // indirect + k8s.io/code-generator v0.35.2 // indirect k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect - k8s.io/kms v0.35.1 // indirect + k8s.io/kms v0.35.2 // indirect k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect diff --git a/go.sum b/go.sum index ac59a2f..558a651 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= -cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= @@ -8,8 +6,6 @@ github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cq github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= -github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= -github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -65,16 +61,12 @@ github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 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.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= -github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= 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 v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI= -github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= @@ -118,8 +110,6 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/meilisearch/meilisearch-go v0.36.0 h1:N1etykTektXt5KPcSbhBO0d5Xx5NaKj4pJWEM7WA5dI= -github.com/meilisearch/meilisearch-go v0.36.0/go.mod h1:HBfHzKMxcSbTOvqdfuRA/yf6Vk9IivcwKocWRuW7W78= github.com/meilisearch/meilisearch-go v0.36.1 h1:mJTCJE5g7tRvaqKco6DfqOuJEjX+rRltDEnkEC02Y0M= github.com/meilisearch/meilisearch-go v0.36.1/go.mod h1:hWcR0MuWLSzHfbz9GGzIr3s9rnXLm1jqkmHkJPbUSvM= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -164,17 +154,10 @@ github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiT github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= -github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/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= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= @@ -238,8 +221,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA= golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -307,42 +288,28 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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= -k8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY= -k8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA= -k8s.io/api v0.35.1 h1:0PO/1FhlK/EQNVK5+txc4FuhQibV25VLSdLMmGpDE/Q= -k8s.io/api v0.35.1/go.mod h1:28uR9xlXWml9eT0uaGo6y71xK86JBELShLy4wR1XtxM= +k8s.io/api v0.35.2 h1:tW7mWc2RpxW7HS4CoRXhtYHSzme1PN1UjGHJ1bdrtdw= +k8s.io/api v0.35.2/go.mod h1:7AJfqGoAZcwSFhOjcGM7WV05QxMMgUaChNfLTXDRE60= k8s.io/apiextensions-apiserver v0.35.0 h1:3xHk2rTOdWXXJM+RDQZJvdx0yEOgC0FgQ1PlJatA5T4= k8s.io/apiextensions-apiserver v0.35.0/go.mod h1:E1Ahk9SADaLQ4qtzYFkwUqusXTcaV2uw3l14aqpL2LU= -k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8= -k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= -k8s.io/apimachinery v0.35.1 h1:yxO6gV555P1YV0SANtnTjXYfiivaTPvCTKX6w6qdDsU= -k8s.io/apimachinery v0.35.1/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= -k8s.io/apiserver v0.35.0 h1:CUGo5o+7hW9GcAEF3x3usT3fX4f9r8xmgQeCBDaOgX4= -k8s.io/apiserver v0.35.0/go.mod h1:QUy1U4+PrzbJaM3XGu2tQ7U9A4udRRo5cyxkFX0GEds= -k8s.io/apiserver v0.35.1 h1:potxdhhTL4i6AYAa2QCwtlhtB1eCdWQFvJV6fXgJzxs= -k8s.io/apiserver v0.35.1/go.mod h1:BiL6Dd3A2I/0lBnteXfWmCFobHM39vt5+hJQd7Lbpi4= -k8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE= -k8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o= -k8s.io/client-go v0.35.1 h1:+eSfZHwuo/I19PaSxqumjqZ9l5XiTEKbIaJ+j1wLcLM= -k8s.io/client-go v0.35.1/go.mod h1:1p1KxDt3a0ruRfc/pG4qT/3oHmUj1AhSHEcxNSGg+OA= -k8s.io/code-generator v0.35.0 h1:TvrtfKYZTm9oDF2z+veFKSCcgZE3Igv0svY+ehCmjHQ= -k8s.io/code-generator v0.35.0/go.mod h1:iS1gvVf3c/T71N5DOGYO+Gt3PdJ6B9LYSvIyQ4FHzgc= -k8s.io/code-generator v0.35.1/go.mod h1:F2Fhm7aA69tC/VkMXLDokdovltXEF026Tb9yfQXQWKg= -k8s.io/component-base v0.35.0 h1:+yBrOhzri2S1BVqyVSvcM3PtPyx5GUxCK2tinZz1G94= -k8s.io/component-base v0.35.0/go.mod h1:85SCX4UCa6SCFt6p3IKAPej7jSnF3L8EbfSyMZayJR0= -k8s.io/component-base v0.35.1 h1:XgvpRf4srp037QWfGBLFsYMUQJkE5yMa94UsJU7pmcE= -k8s.io/component-base v0.35.1/go.mod h1:HI/6jXlwkiOL5zL9bqA3en1Ygv60F03oEpnuU1G56Bs= +k8s.io/apimachinery v0.35.2 h1:NqsM/mmZA7sHW02JZ9RTtk3wInRgbVxL8MPfzSANAK8= +k8s.io/apimachinery v0.35.2/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= +k8s.io/apiserver v0.35.2 h1:rb52v0CZGEL0FkhjS+I6jHflAp7fZ4MIaKcEHX7wmDk= +k8s.io/apiserver v0.35.2/go.mod h1:CROJUAu0tfjZLyYgSeBsBan2T7LUJGh0ucWwTCSSk7g= +k8s.io/client-go v0.35.2 h1:YUfPefdGJA4aljDdayAXkc98DnPkIetMl4PrKX97W9o= +k8s.io/client-go v0.35.2/go.mod h1:4QqEwh4oQpeK8AaefZ0jwTFJw/9kIjdQi0jpKeYvz7g= +k8s.io/code-generator v0.35.2 h1:3874swbO2c26VWTf6lKD4NWGyHIfyBeTCk7caCG3TuU= +k8s.io/code-generator v0.35.2/go.mod h1:id4XLCm0yAQq5nlvyfAKibMOKnMjzlesAwGw6kM3Adc= +k8s.io/component-base v0.35.2 h1:btgR+qNrpWuRSuvWSnQYsZy88yf5gVwemvz0yw79pGc= +k8s.io/component-base v0.35.2/go.mod h1:B1iBJjooe6xIJYUucAxb26RwhAjzx0gHnqO9htWIX+0= k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b h1:gMplByicHV/TJBizHd9aVEsTYoJBnnUAT5MHlTkbjhQ= k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b/go.mod h1:CgujABENc3KuTrcsdpGmrrASjtQsWCT7R99mEV4U/fM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kms v0.35.0 h1:/x87FED2kDSo66csKtcYCEHsxF/DBlNl7LfJ1fVQs1o= -k8s.io/kms v0.35.0/go.mod h1:VT+4ekZAdrZDMgShK37vvlyHUVhwI9t/9tvh0AyCWmQ= -k8s.io/kms v0.35.1 h1:kjv2r9g1mY7uL+l1RhyAZvWVZIA/4qIfBHXyjFGLRhU= -k8s.io/kms v0.35.1/go.mod h1:VT+4ekZAdrZDMgShK37vvlyHUVhwI9t/9tvh0AyCWmQ= +k8s.io/kms v0.35.2 h1:XPlj7QmLBfzm8gGQnc3+Y95hZLiJs3DjA0IyFOV5Z7g= +k8s.io/kms v0.35.2/go.mod h1:VT+4ekZAdrZDMgShK37vvlyHUVhwI9t/9tvh0AyCWmQ= k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4 h1:HhDfevmPS+OalTjQRKbTHppRIz01AWi8s45TMXStgYY= k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh new file mode 100755 index 0000000..fcf5849 --- /dev/null +++ b/hack/update-codegen.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +set -o errexit +set -o nounset +set -o pipefail + +SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. +MODULE_NAME="go.miloapis.net/search" + +# Find code-generator +CODEGEN_PKG=$(go list -m -f '{{.Dir}}' k8s.io/code-generator 2>/dev/null) + +if [ -z "${CODEGEN_PKG}" ]; then + echo "ERROR: k8s.io/code-generator not found in go.mod" + echo "Run: go get k8s.io/code-generator" + exit 1 +fi + +echo "Using code-generator from: ${CODEGEN_PKG}" + +# Source the code generation helper +source "${CODEGEN_PKG}/kube_codegen.sh" + +# Generate deepcopy +echo "Generating deepcopy..." +kube::codegen::gen_helpers \ + --boilerplate "${SCRIPT_ROOT}/hack/boilerplate.go.txt" \ + "${SCRIPT_ROOT}/pkg/apis" + +# Generate OpenAPI definitions +echo "Generating OpenAPI definitions..." +go run k8s.io/kube-openapi/cmd/openapi-gen \ + --go-header-file "${SCRIPT_ROOT}/hack/boilerplate.go.txt" \ + --output-dir "${SCRIPT_ROOT}/pkg/generated/openapi" \ + --output-pkg "${MODULE_NAME}/pkg/generated/openapi" \ + --output-file zz_generated.openapi.go \ + --report-filename /dev/null \ + "${MODULE_NAME}/pkg/apis/search/v1alpha1" \ + "k8s.io/apimachinery/pkg/apis/meta/v1" \ + "k8s.io/apimachinery/pkg/api/resource" \ + "k8s.io/apimachinery/pkg/runtime" \ + "k8s.io/apimachinery/pkg/version" + +echo "" +echo "Code generation complete!" +echo "" +echo "Generated:" +echo " - Deepcopy functions: pkg/apis/search/v1alpha1/zz_generated.deepcopy.go" +echo " - OpenAPI: pkg/generated/openapi/" diff --git a/internal/apiserver/apiserver.go b/internal/apiserver/apiserver.go index 02e8346..3f24b9a 100644 --- a/internal/apiserver/apiserver.go +++ b/internal/apiserver/apiserver.go @@ -2,6 +2,8 @@ package apiserver import ( "context" + "fmt" + "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -11,11 +13,13 @@ import ( genericapiserver "k8s.io/apiserver/pkg/server" "k8s.io/klog/v2" + "go.miloapis.net/search/internal/indexer" _ "go.miloapis.net/search/internal/metrics" "go.miloapis.net/search/internal/registry/policy/resourceindexpolicy" + "go.miloapis.net/search/internal/registry/searchquery" "go.miloapis.net/search/pkg/apis/search/install" - searchinstall "go.miloapis.net/search/pkg/apis/search/install" searchv1alpha1 "go.miloapis.net/search/pkg/apis/search/v1alpha1" + "go.miloapis.net/search/pkg/meilisearch" ) var ( @@ -27,7 +31,6 @@ var ( func init() { install.Install(Scheme) - searchinstall.Install(Scheme) metav1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) @@ -36,6 +39,8 @@ func init() { Scheme.AddKnownTypes(schema.GroupVersion{Group: searchv1alpha1.GroupName, Version: runtime.APIVersionInternal}, &searchv1alpha1.ResourceIndexPolicy{}, &searchv1alpha1.ResourceIndexPolicyList{}, + &searchv1alpha1.SearchQuery{}, + &searchv1alpha1.SearchQueryList{}, ) // Register unversioned meta types required by the API machinery. @@ -51,7 +56,12 @@ func init() { // ExtraConfig extends the generic apiserver configuration with search-specific settings. type ExtraConfig struct { - // Add custom configuration here as needed + MeiliClient *meilisearch.SDKClient + PolicyCache *indexer.PolicyCache + MaxSearchLimit int + DefaultSearchLimit int + PagingSecret []byte + PagingTimeout time.Duration } // Config combines generic and search-specific configuration. @@ -110,12 +120,42 @@ func (c completedConfig) New() (*SearchServer, error) { searchV1alpha1Storage["resourceindexpolicies"] = policyStorage.Store searchV1alpha1Storage["resourceindexpolicies/status"] = policyStorage.Status + // Add searchquery resources + searchqueryStorage := searchquery.NewREST( + c.ExtraConfig.MeiliClient, + c.ExtraConfig.PolicyCache, + c.ExtraConfig.MaxSearchLimit, + c.ExtraConfig.DefaultSearchLimit, + c.ExtraConfig.PagingSecret, + c.ExtraConfig.PagingTimeout, + ) + searchV1alpha1Storage["searchqueries"] = searchqueryStorage + searchAPIGroupInfo.VersionedResourcesStorageMap["v1alpha1"] = searchV1alpha1Storage if err := s.GenericAPIServer.InstallAPIGroup(&searchAPIGroupInfo); err != nil { return nil, err } + // Add PostStartHook to start policy cache + s.GenericAPIServer.AddPostStartHookOrDie("search-policy-cache", func(ctx genericapiserver.PostStartHookContext) error { + if err := c.ExtraConfig.PolicyCache.RegisterHandlers(ctx.Context); err != nil { + return fmt.Errorf("failed to register policy cache handlers: %w", err) + } + + go func() { + klog.Info("Starting Search policy cache...") + if err := c.ExtraConfig.PolicyCache.Start(ctx.Context); err != nil { + klog.Errorf("Policy cache stopped with error: %v", err) + } + }() + + if !c.ExtraConfig.PolicyCache.WaitForCacheSync(ctx.Context) { + return fmt.Errorf("failed to wait for policy cache sync") + } + return nil + }) + klog.Info("Search server initialized successfully") return s, nil diff --git a/internal/controllers/policy/policy_controller.go b/internal/controllers/policy/policy_controller.go index c0d0441..043820e 100644 --- a/internal/controllers/policy/policy_controller.go +++ b/internal/controllers/policy/policy_controller.go @@ -85,6 +85,7 @@ const ( // +kubebuilder:rbac:groups=search.miloapis.com,resources=resourceindexpolicies,verbs=get;list;watch // +kubebuilder:rbac:groups=search.miloapis.com,resources=resourceindexpolicies/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=*,resources=*,verbs=get;list;watch // Reconcile matches the state of the cluster with the desired state of a ResourceIndexPolicy. func (r *ResourceIndexPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { diff --git a/internal/indexer/policy_cache.go b/internal/indexer/policy_cache.go index 7f694d7..c3654be 100644 --- a/internal/indexer/policy_cache.go +++ b/internal/indexer/policy_cache.go @@ -15,6 +15,9 @@ import ( runtimecache "sigs.k8s.io/controller-runtime/pkg/cache" ) +// +kubebuilder:rbac:groups=search.miloapis.com,resources=resourceindexpolicies,verbs=get;list;watch +// +kubebuilder:rbac:groups=search.miloapis.com,resources=resourceindexpolicies/status,verbs=get;list;watch + // PolicyCache maintains a thread-safe cache of compiled ResourceIndexPolicies. // It uses a controller-runtime informer to watch the API server for changes // via a watch stream, keeping policies in-sync without polling. @@ -50,9 +53,9 @@ func NewPolicyCache(c runtimecache.Cache, requireReadyCondition bool) (*PolicyCa }, nil } -// Start registers informer event handlers for ResourceIndexPolicy objects -func (c *PolicyCache) Start(ctx context.Context) error { - klog.Info("Starting policy cache informer") +// RegisterHandlers registers informer event handlers for ResourceIndexPolicy objects. +func (c *PolicyCache) RegisterHandlers(ctx context.Context) error { + klog.Info("Registering policy cache informer handlers") informer, err := c.cache.GetInformer(ctx, &v1alpha1.ResourceIndexPolicy{}) if err != nil { @@ -92,10 +95,6 @@ func (c *PolicyCache) Start(ctx context.Context) error { return fmt.Errorf("failed to add event handler to ResourceIndexPolicy informer: %w", err) } - // Start runs all informers and blocks until ctx is cancelled. - if err := c.cache.Start(ctx); err != nil { - return fmt.Errorf("policy cache informer stopped with error: %w", err) - } return nil } @@ -109,7 +108,7 @@ func (c *PolicyCache) upsertPolicy(p *v1alpha1.ResourceIndexPolicy) { // still being initialized (e.g. index creation or initial re-indexing). if c.requireReadyCondition { if !meta.IsStatusConditionTrue(p.Status.Conditions, "Ready") { - klog.Infof("Policy %s is not yet Ready; skipping cache", key) + klog.Infof("Policy %s is not yet Ready (Ready=True condition missing); skipping cache", key) c.deletePolicy(key) return } @@ -156,6 +155,16 @@ func (c *PolicyCache) deletePolicy(name string) { klog.Infof("Policy %s removed from cache", name) } +// Start starts the underlying cache. +func (c *PolicyCache) Start(ctx context.Context) error { + return c.cache.Start(ctx) +} + +// WaitForCacheSync waits for the underlying cache to sync. +func (c *PolicyCache) WaitForCacheSync(ctx context.Context) bool { + return c.cache.WaitForCacheSync(ctx) +} + // GetPolicies returns a snapshot of all cached policies. func (c *PolicyCache) GetPolicies() []*policyevaluation.CachedPolicy { c.mu.RLock() diff --git a/internal/indexer/reindex_consumer.go b/internal/indexer/reindex_consumer.go index 3fc7047..3d6ac09 100644 --- a/internal/indexer/reindex_consumer.go +++ b/internal/indexer/reindex_consumer.go @@ -64,6 +64,7 @@ func (r *ReindexConsumer) Start(ctx context.Context) error { for _, cp := range policies { // Skip if index name is not set yet if cp.Policy.Status.IndexName == "" { + klog.V(2).Infof("ReindexConsumer: policy %s has no IndexName in status, skipping", cp.Policy.Name) continue } @@ -86,6 +87,8 @@ func (r *ReindexConsumer) Start(ctx context.Context) error { r.batcher.QueueUpsert(cp.Policy.Status.IndexName, doc, &msg) queued = true } else { + klog.V(4).Infof("ReindexConsumer: policy %s did not match resource %s/%s (id=%s), ensuring deletion", cp.Policy.Name, obj.GetNamespace(), obj.GetName(), event.ID) + // If it doesn't match this policy, we should ensure it's removed from the index // in case it was previously indexed there. r.batcher.QueueDelete(cp.Policy.Status.IndexName, resourceUID, &msg) diff --git a/internal/policy/evaluation/evaluator.go b/internal/policy/evaluation/evaluator.go index dfb7608..9ff49b2 100644 --- a/internal/policy/evaluation/evaluator.go +++ b/internal/policy/evaluation/evaluator.go @@ -16,6 +16,12 @@ type EvalResult struct { Matched bool // Fields contains the extracted field values from the resource, keyed by path. Fields map[string]any + // Group is the API group of the matching policy. + Group string + // Version is the API version of the matching policy. + Version string + // Kind is the kind of the matching policy. + Kind string } // PolicyEvaluator evaluates whether a Kubernetes resource matches a policy @@ -33,7 +39,12 @@ type CachedPolicy struct { // Evaluate checks if the resource matches the policy's target GVK, conditions, // and extracts the configured fields from matching resources. func (cp *CachedPolicy) Evaluate(u *unstructured.Unstructured) (*EvalResult, error) { - result := &EvalResult{Fields: map[string]any{}} + result := &EvalResult{ + Fields: map[string]any{}, + Group: cp.Policy.Spec.TargetResource.Group, + Version: cp.Policy.Spec.TargetResource.Version, + Kind: cp.Policy.Spec.TargetResource.Kind, + } // 1. Check GVK match gvk := u.GroupVersionKind() @@ -155,6 +166,15 @@ func (r *EvalResult) Transform() map[string]any { leaf := segments[len(segments)-1] current[leaf] = value } + + // Add policy GVK metadata to the document + if r.Group != "" { + doc["apiVersion"] = r.Group + "/" + r.Version + } else { + doc["apiVersion"] = r.Version + } + doc["kind"] = r.Kind + return doc } diff --git a/internal/policy/evaluation/transform_test.go b/internal/policy/evaluation/transform_test.go index ce8c452..682bfd1 100644 --- a/internal/policy/evaluation/transform_test.go +++ b/internal/policy/evaluation/transform_test.go @@ -11,17 +11,33 @@ func TestEvalResult_Transform(t *testing.T) { tests := []struct { name string fields map[string]any + group string + version string + kind string expected map[string]any }{ { - name: "empty fields", - fields: map[string]any{}, - expected: map[string]any{}, + name: "empty fields", + fields: map[string]any{}, + group: "search.miloapis.com", + version: "v1alpha1", + kind: "ResourceIndexPolicy", + expected: map[string]any{ + "apiVersion": "search.miloapis.com/v1alpha1", + "kind": "ResourceIndexPolicy", + }, }, { - name: "single field", - fields: map[string]any{".metadata.name": "test-cm"}, - expected: map[string]any{"metadata": map[string]any{"name": "test-cm"}}, + name: "single field", + fields: map[string]any{".metadata.name": "test-cm"}, + group: "", + version: "v1", + kind: "ConfigMap", + expected: map[string]any{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]any{"name": "test-cm"}, + }, }, { name: "multiple fields", @@ -30,8 +46,13 @@ func TestEvalResult_Transform(t *testing.T) { ".spec.replicas": int64(3), ".spec.selector.matchLabels.app": "foo", }, + group: "apps", + version: "v1", + kind: "Deployment", expected: map[string]any{ - "metadata": map[string]any{"name": "test-cm"}, + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]any{"name": "test-cm"}, "spec": map[string]any{ "replicas": int64(3), "selector": map[string]any{ @@ -41,10 +62,15 @@ func TestEvalResult_Transform(t *testing.T) { }, }, { - name: "nested brackets", - fields: map[string]any{".data['config.yaml']": "content"}, + name: "nested brackets", + fields: map[string]any{".data['config.yaml']": "content"}, + group: "", + version: "v1", + kind: "ConfigMap", expected: map[string]any{ - "data": map[string]any{"config.yaml": "content"}, + "apiVersion": "v1", + "kind": "ConfigMap", + "data": map[string]any{"config.yaml": "content"}, }, }, { @@ -53,7 +79,12 @@ func TestEvalResult_Transform(t *testing.T) { ".metadata.name": "test", ".metadata.namespace": "default", }, + group: "", + version: "v1", + kind: "Pod", expected: map[string]any{ + "apiVersion": "v1", + "kind": "Pod", "metadata": map[string]any{ "name": "test", "namespace": "default", @@ -66,7 +97,12 @@ func TestEvalResult_Transform(t *testing.T) { ".spec.selector['app']": "backend", ".spec.selector.component": "core", }, + group: "apps", + version: "v1", + kind: "Deployment", expected: map[string]any{ + "apiVersion": "apps/v1", + "kind": "Deployment", "spec": map[string]any{ "selector": map[string]any{ "app": "backend", @@ -85,7 +121,12 @@ func TestEvalResult_Transform(t *testing.T) { ".spec.ports[1].targetPort": int64(8443), ".spec.ports[1].name": "https", }, + group: "apps", + version: "v1", + kind: "StatefulSet", expected: map[string]any{ + "apiVersion": "apps/v1", + "kind": "StatefulSet", "spec": map[string]any{ "ports": map[string]any{ "0": map[string]any{ @@ -110,7 +151,12 @@ func TestEvalResult_Transform(t *testing.T) { ".status.containerStatuses[0].name": "main", ".status.hostIP": "10.0.0.1", }, + group: "", + version: "v1", + kind: "Pod", expected: map[string]any{ + "apiVersion": "v1", + "kind": "Pod", "status": map[string]any{ "conditions": map[string]any{ "0": map[string]any{ @@ -133,7 +179,12 @@ func TestEvalResult_Transform(t *testing.T) { ".a.b.c.d.e": "deep", ".a.b.c.f": "shallow", }, + group: "example.com", + version: "v1", + kind: "CustomResource", expected: map[string]any{ + "apiVersion": "example.com/v1", + "kind": "CustomResource", "a": map[string]any{ "b": map[string]any{ "c": map[string]any{ @@ -153,7 +204,12 @@ func TestEvalResult_Transform(t *testing.T) { ".metadata.labels['app.kubernetes.io/name']": "myapp", ".data['config.json']": "{}", }, + group: "", + version: "v1", + kind: "ConfigMap", expected: map[string]any{ + "apiVersion": "v1", + "kind": "ConfigMap", "metadata": map[string]any{ "annotations": map[string]any{ "example.com/managed-by": "controller", @@ -173,9 +229,12 @@ func TestEvalResult_Transform(t *testing.T) { "kind": "Service", "apiVersion": "v1", }, + group: "", + version: "v2", // Policy version should overwrite the one from fields + kind: "ServiceOverride", expected: map[string]any{ - "kind": "Service", - "apiVersion": "v1", + "kind": "ServiceOverride", + "apiVersion": "v2", }, }, { @@ -184,7 +243,12 @@ func TestEvalResult_Transform(t *testing.T) { ".spec['selector']['app']": "foo", ".data['key']['subkey']": "bar", }, + group: "", + version: "v1", + kind: "ConfigMap", expected: map[string]any{ + "apiVersion": "v1", + "kind": "ConfigMap", "spec": map[string]any{ "selector": map[string]any{ "app": "foo", @@ -203,6 +267,9 @@ func TestEvalResult_Transform(t *testing.T) { ".a": "scalar-value", // This sets "a" = "scalar-value" ".a.b": "nested-value", // This requires "a" to be a map. Logic should overwrite "a" with map {"b": "nested-value"} }, + group: "x", + version: "v1", + kind: "Y", expected: nil, // skip }, } @@ -212,7 +279,13 @@ func TestEvalResult_Transform(t *testing.T) { continue } t.Run(tt.name, func(t *testing.T) { - r := &EvalResult{Matched: true, Fields: tt.fields} + r := &EvalResult{ + Matched: true, + Fields: tt.fields, + Group: tt.group, + Version: tt.version, + Kind: tt.kind, + } doc := r.Transform() // Use assert.Equal which compares map structure values deeply diff --git a/internal/registry/searchquery/rest.go b/internal/registry/searchquery/rest.go new file mode 100644 index 0000000..4e4091f --- /dev/null +++ b/internal/registry/searchquery/rest.go @@ -0,0 +1,271 @@ +package searchquery + +import ( + "context" + "encoding/json" + "fmt" + "math" + "reflect" + "time" + + "github.com/golang-jwt/jwt/v5" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/rest" + "k8s.io/klog/v2" + + "go.miloapis.net/search/internal/indexer" + searchv1alpha1 "go.miloapis.net/search/pkg/apis/search/v1alpha1" + "go.miloapis.net/search/pkg/meilisearch" +) + +// REST implements a RESTStorage for SearchQuery API. +type REST struct { + meiliClient *meilisearch.SDKClient + policyCache *indexer.PolicyCache + maxSearchLimit int + defaultSearchLimit int + secretKey []byte + pagingTimeout time.Duration +} + +// Ensure REST implements required interfaces +var _ rest.Storage = &REST{} +var _ rest.Creater = &REST{} +var _ rest.Scoper = &REST{} +var _ rest.SingularNameProvider = &REST{} + +// NewREST returns a RESTStorage object that will work against SearchQuery. +func NewREST(meiliClient *meilisearch.SDKClient, policyCache *indexer.PolicyCache, maxSearchLimit int, defaultSearchLimit int, pagingSecret []byte, pagingTimeout time.Duration) *REST { + return &REST{ + meiliClient: meiliClient, + policyCache: policyCache, + maxSearchLimit: maxSearchLimit, + defaultSearchLimit: defaultSearchLimit, + secretKey: pagingSecret, + pagingTimeout: pagingTimeout, + } +} + +// New returns an empty object that can be used with Create. +func (r *REST) New() runtime.Object { + return &searchv1alpha1.SearchQuery{} +} + +// Destroy cleans up its resources on shutdown. +func (r *REST) Destroy() {} + +// NamespaceScoped returns true if the storage is namespaced +func (r *REST) NamespaceScoped() bool { + return false +} + +// GetSingularName implements the rest.SingularNameProvider interface +func (r *REST) GetSingularName() string { + return "searchquery" +} + +// Create creates a new version of a resource. +func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { + query, ok := obj.(*searchv1alpha1.SearchQuery) + if !ok { + return nil, fmt.Errorf("not a SearchQuery: %#v", obj) + } + + if createValidation != nil { + if err := createValidation(ctx, obj); err != nil { + return nil, err + } + } + + limit, offset, err := r.validateAndGetPagination(query) + if err != nil { + return nil, err + } + + indexUIDs, err := r.resolveIndexUIDs(query) + if err != nil { + return nil, err + } + + if len(indexUIDs) == 0 { + // No ready policies or matching indices exist yet + created := query.DeepCopy() + created.Status = searchv1alpha1.SearchQueryStatus{ + Results: []searchv1alpha1.SearchResult{}, + } + return created, nil + } + + // Perform the multi search + resp, err := r.meiliClient.MultiSearch(indexUIDs, query.Spec.Query, limit, offset) + if err != nil { + return nil, fmt.Errorf("failed to search: %w", err) + } + + // Process search results + var results []searchv1alpha1.SearchResult + + // Handle search results + for _, hit := range resp.Hits { + if res, err := formatSearchResult(hit); err == nil { + results = append(results, res) + } + } + + // Populate response status + created := query.DeepCopy() + created.Status = searchv1alpha1.SearchQueryStatus{ + Results: results, + Continue: r.calculateNextContinueToken(offset, limit, len(resp.Hits), query), + } + + return created, nil +} + +type PagingClaims struct { + Offset int64 `json:"offset"` + Limit int32 `json:"limit"` + Query string `json:"query"` + TargetResources []searchv1alpha1.TargetResource `json:"targetResources"` + jwt.RegisteredClaims +} + +// validateAndGetPagination validates the limit and continue token from the query and returns +// their effective values. +func (r *REST) validateAndGetPagination(query *searchv1alpha1.SearchQuery) (int64, int64, error) { + limit := int64(r.defaultSearchLimit) + var offset int64 = 0 + + // If continue token is provided, it dictates the query state + if query.Spec.Continue != "" { + claims := &PagingClaims{} + token, err := jwt.ParseWithClaims(query.Spec.Continue, claims, func(token *jwt.Token) (interface{}, error) { + return r.secretKey, nil + }) + + if err != nil || !token.Valid { + return 0, 0, apierrors.NewBadRequest("invalid continue token") + } + + // Verify that the query has not changed + if query.Spec.Query != claims.Query { + return 0, 0, apierrors.NewBadRequest("query string cannot be changed when using a continue token") + } + if int32(limit) != claims.Limit && query.Spec.Limit != 0 { + // If limit was specified and is different from the token, error. + // (Note: limit in query.Spec might be 0 if not provided, in which case we use the defaulted limit from the token) + if query.Spec.Limit != claims.Limit { + return 0, 0, apierrors.NewBadRequest("limit cannot be changed when using a continue token") + } + } + if !reflect.DeepEqual(query.Spec.TargetResources, claims.TargetResources) { + return 0, 0, apierrors.NewBadRequest("targetResources cannot be changed when using a continue token") + } + + // Ensure we use the correct limit/offset from the token + limit = int64(claims.Limit) + offset = claims.Offset + return limit, offset, nil + } + + // For new queries, validate limit + if query.Spec.Limit < 0 { + return 0, 0, apierrors.NewBadRequest("limit cannot be negative") + } + if query.Spec.Limit > 0 { + if int(query.Spec.Limit) > r.maxSearchLimit { + return 0, 0, apierrors.NewBadRequest(fmt.Sprintf("limit %d exceeds the maximum search limit of %d", query.Spec.Limit, r.maxSearchLimit)) + } + limit = int64(query.Spec.Limit) + } + + return limit, offset, nil +} + +// resolveIndexUIDs retrieves the Meilisearch index UIDs for the targeted resources +// in the query. If no target resources are specified, it returns all indices from +// all ready policies. +func (r *REST) resolveIndexUIDs(query *searchv1alpha1.SearchQuery) ([]string, error) { + var indexUIDs []string + policies := r.policyCache.GetPolicies() + + if len(query.Spec.TargetResources) > 0 { + for _, tr := range query.Spec.TargetResources { + found := false + for _, cp := range policies { + p := cp.Policy + if p.Spec.TargetResource.Group == tr.Group && + p.Spec.TargetResource.Version == tr.Version && + p.Spec.TargetResource.Kind == tr.Kind { + if p.Status.IndexName != "" { + indexUIDs = append(indexUIDs, p.Status.IndexName) + found = true + } + } + } + if !found { + return nil, apierrors.NewBadRequest(fmt.Sprintf("target resource %s/%s %s is not currently indexed or policy is not ready", tr.Group, tr.Version, tr.Kind)) + } + } + } else { + for _, cp := range policies { + if cp.Policy.Status.IndexName != "" { + indexUIDs = append(indexUIDs, cp.Policy.Status.IndexName) + } + } + } + return indexUIDs, nil +} + +// calculateNextContinueToken determines the next continue token for pagination. +// If the number of hits on the current page is equal to or greater than the limit, +// it assumes there are more results and returns a signed JWT containing the next offset +// and the original query parameters to ensure consistency. +func (r *REST) calculateNextContinueToken(currentOffset, limit int64, totalHitsOnPage int, query *searchv1alpha1.SearchQuery) string { + if totalHitsOnPage > 0 && int64(totalHitsOnPage) >= limit { + claims := PagingClaims{ + Offset: currentOffset + limit, + Limit: int32(limit), + Query: query.Spec.Query, + TargetResources: query.Spec.TargetResources, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(r.pagingTimeout)), + IssuedAt: jwt.NewNumericDate(time.Now()), + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + ss, err := token.SignedString(r.secretKey) + if err != nil { + klog.Errorf("Failed to sign paging token: %v", err) + return "" + } + return ss + } + return "" +} + +// formatSearchResult converts a Meilisearch hit into a SearchResult API object. +func formatSearchResult(hit map[string]json.RawMessage) (searchv1alpha1.SearchResult, error) { + var score float64 + if s, found := hit["_rankingScore"]; found { + _ = json.Unmarshal(s, &score) + score = math.Round(score*10000) / 10000 + } + + // Remove meilisearch internal fields + delete(hit, "_rankingScore") + delete(hit, "_federation") + + b, err := json.Marshal(hit) + if err != nil { + return searchv1alpha1.SearchResult{}, err + } + + return searchv1alpha1.SearchResult{ + Resource: runtime.RawExtension{Raw: b}, + RelevanceScore: score, + }, nil +} diff --git a/pkg/apis/search/v1alpha1/policy_types.go b/pkg/apis/search/v1alpha1/policy_types.go index b479632..dae272c 100644 --- a/pkg/apis/search/v1alpha1/policy_types.go +++ b/pkg/apis/search/v1alpha1/policy_types.go @@ -5,6 +5,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// +k8s:openapi-gen=true // +kubebuilder:object:root=true // +kubebuilder:subresource:status // +kubebuilder:resource:scope=Cluster @@ -49,11 +50,15 @@ type ResourceIndexPolicySpec struct { // - Ternary: condition ? trueValue : falseValue // +kubebuilder:validation:MinItems=1 // +kubebuilder:validation:MaxItems=10 + // +listType=map + // +listMapKey=name Conditions []PolicyCondition `json:"conditions"` // Fields defines which fields from the resource are indexed. // +kubebuilder:validation:MinItems=1 // +kubebuilder:validation:MaxItems=10 + // +listType=map + // +listMapKey=path Fields []FieldPolicy `json:"fields"` } @@ -110,6 +115,8 @@ type ResourceIndexPolicyStatus struct { // Conditions represents the latest available observations of the policy's state. // +kubebuilder:default={{type: "Ready", status: "Unknown", reason: "Unknown", message: "Waiting for control plane to reconcile", lastTransitionTime: "1970-01-01T00:00:00Z"}} // +optional + // +listType=map + // +listMapKey=type Conditions []metav1.Condition `json:"conditions,omitempty"` // IndexName is the name of the search index created for this policy. diff --git a/pkg/apis/search/v1alpha1/register.go b/pkg/apis/search/v1alpha1/register.go index 4f0d4ae..5a5155e 100644 --- a/pkg/apis/search/v1alpha1/register.go +++ b/pkg/apis/search/v1alpha1/register.go @@ -27,8 +27,8 @@ func Resource(resource string) schema.GroupResource { // addKnownTypes adds the set of types defined in this package to the supplied scheme func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, - // &SearchQuery{}, - // &SearchQueryList{}, + &SearchQuery{}, + &SearchQueryList{}, &ResourceIndexPolicy{}, &ResourceIndexPolicyList{}, ) diff --git a/pkg/apis/search/v1alpha1/types.go b/pkg/apis/search/v1alpha1/types.go index fc658da..9028883 100644 --- a/pkg/apis/search/v1alpha1/types.go +++ b/pkg/apis/search/v1alpha1/types.go @@ -6,6 +6,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +// +k8s:openapi-gen=true // +genclient // +genclient:nonNamespaced // +genclient:onlyVerbs=create @@ -36,6 +37,11 @@ type SearchQuery struct { // // The actual fields will depend on the specific search implementation. type SearchQuerySpec struct { + // TargetResources limits the search to specific resource types. + // +optional + // +listType=atomic + TargetResources []TargetResource `json:"targetResources,omitempty"` + // Query is the search query string. // // +required @@ -58,19 +64,29 @@ type SearchQuerySpec struct { // SearchQueryStatus contains the query results and pagination state. type SearchQueryStatus struct { - // Results contains the search results as an array of Kubernetes resources. + // Results contains the search results. // // +optional - Results []runtime.RawExtension `json:"results,omitempty"` + // +listType=atomic + Results []SearchResult `json:"results,omitempty"` // Continue is the pagination cursor. // Non-empty means more results are available - copy this to spec.continue for the next page. // Empty means you have all results. - // // +optional Continue string `json:"continue,omitempty"` } +// SearchResult represents a single search result with its relevance score. +type SearchResult struct { + // Resource contains the actual Kubernetes resource. + Resource runtime.RawExtension `json:"resource"` + + // RelevanceScore is the relevance score from Meilisearch. + // +optional + RelevanceScore float64 `json:"relevanceScore,omitempty"` +} + // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // SearchQueryList is a list of SearchQuery objects diff --git a/pkg/apis/search/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/search/v1alpha1/zz_generated.deepcopy.go index 971235c..76ad395 100644 --- a/pkg/apis/search/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/search/v1alpha1/zz_generated.deepcopy.go @@ -1,17 +1,19 @@ //go:build !ignore_autogenerated +// +build !ignore_autogenerated -// Code generated by controller-gen. DO NOT EDIT. +// Code generated by deepcopy-gen. DO NOT EDIT. package v1alpha1 import ( - "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" + v1 "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 *FieldPolicy) DeepCopyInto(out *FieldPolicy) { *out = *in + return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FieldPolicy. @@ -27,6 +29,7 @@ func (in *FieldPolicy) DeepCopy() *FieldPolicy { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PolicyCondition) DeepCopyInto(out *PolicyCondition) { *out = *in + return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyCondition. @@ -46,6 +49,7 @@ func (in *ResourceIndexPolicy) DeepCopyInto(out *ResourceIndexPolicy) { in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) + return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceIndexPolicy. @@ -78,6 +82,7 @@ func (in *ResourceIndexPolicyList) DeepCopyInto(out *ResourceIndexPolicyList) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceIndexPolicyList. @@ -112,6 +117,7 @@ func (in *ResourceIndexPolicySpec) DeepCopyInto(out *ResourceIndexPolicySpec) { *out = make([]FieldPolicy, len(*in)) copy(*out, *in) } + return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceIndexPolicySpec. @@ -134,6 +140,7 @@ func (in *ResourceIndexPolicyStatus) DeepCopyInto(out *ResourceIndexPolicyStatus (*in)[i].DeepCopyInto(&(*out)[i]) } } + return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceIndexPolicyStatus. @@ -151,8 +158,9 @@ func (in *SearchQuery) DeepCopyInto(out *SearchQuery) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) + return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SearchQuery. @@ -185,6 +193,7 @@ func (in *SearchQueryList) DeepCopyInto(out *SearchQueryList) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SearchQueryList. @@ -208,6 +217,12 @@ func (in *SearchQueryList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SearchQuerySpec) DeepCopyInto(out *SearchQuerySpec) { *out = *in + if in.TargetResources != nil { + in, out := &in.TargetResources, &out.TargetResources + *out = make([]TargetResource, len(*in)) + copy(*out, *in) + } + return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SearchQuerySpec. @@ -225,11 +240,12 @@ func (in *SearchQueryStatus) DeepCopyInto(out *SearchQueryStatus) { *out = *in if in.Results != nil { in, out := &in.Results, &out.Results - *out = make([]runtime.RawExtension, len(*in)) + *out = make([]SearchResult, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } + return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SearchQueryStatus. @@ -242,9 +258,27 @@ func (in *SearchQueryStatus) DeepCopy() *SearchQueryStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SearchResult) DeepCopyInto(out *SearchResult) { + *out = *in + in.Resource.DeepCopyInto(&out.Resource) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SearchResult. +func (in *SearchResult) DeepCopy() *SearchResult { + if in == nil { + return nil + } + out := new(SearchResult) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TargetResource) DeepCopyInto(out *TargetResource) { *out = *in + return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetResource. diff --git a/pkg/apis/search/v1alpha1/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go similarity index 89% rename from pkg/apis/search/v1alpha1/zz_generated.openapi.go rename to pkg/generated/openapi/zz_generated.openapi.go index 350f3e5..7691282 100644 --- a/pkg/apis/search/v1alpha1/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -3,12 +3,13 @@ // Code generated by openapi-gen. DO NOT EDIT. -// This file was autogenerated by openapi-gen. Do not edit it manually! - -package v1alpha1 +package openapi import ( + resource "k8s.io/apimachinery/pkg/api/resource" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + version "k8s.io/apimachinery/pkg/version" common "k8s.io/kube-openapi/pkg/common" spec "k8s.io/kube-openapi/pkg/validation/spec" ) @@ -25,60 +26,62 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "go.miloapis.net/search/pkg/apis/search/v1alpha1.SearchQueryList": schema_pkg_apis_search_v1alpha1_SearchQueryList(ref), "go.miloapis.net/search/pkg/apis/search/v1alpha1.SearchQuerySpec": schema_pkg_apis_search_v1alpha1_SearchQuerySpec(ref), "go.miloapis.net/search/pkg/apis/search/v1alpha1.SearchQueryStatus": schema_pkg_apis_search_v1alpha1_SearchQueryStatus(ref), + "go.miloapis.net/search/pkg/apis/search/v1alpha1.SearchResult": schema_pkg_apis_search_v1alpha1_SearchResult(ref), "go.miloapis.net/search/pkg/apis/search/v1alpha1.TargetResource": schema_pkg_apis_search_v1alpha1_TargetResource(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.APIGroup": schema_pkg_apis_meta_v1_APIGroup(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.APIGroupList": schema_pkg_apis_meta_v1_APIGroupList(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.APIResource": schema_pkg_apis_meta_v1_APIResource(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.APIResourceList": schema_pkg_apis_meta_v1_APIResourceList(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.APIVersions": schema_pkg_apis_meta_v1_APIVersions(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.ApplyOptions": schema_pkg_apis_meta_v1_ApplyOptions(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.Condition": schema_pkg_apis_meta_v1_Condition(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.CreateOptions": schema_pkg_apis_meta_v1_CreateOptions(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.DeleteOptions": schema_pkg_apis_meta_v1_DeleteOptions(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.Duration": schema_pkg_apis_meta_v1_Duration(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.FieldSelectorRequirement": schema_pkg_apis_meta_v1_FieldSelectorRequirement(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.FieldsV1": schema_pkg_apis_meta_v1_FieldsV1(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.GetOptions": schema_pkg_apis_meta_v1_GetOptions(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.GroupKind": schema_pkg_apis_meta_v1_GroupKind(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.GroupResource": schema_pkg_apis_meta_v1_GroupResource(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.GroupVersion": schema_pkg_apis_meta_v1_GroupVersion(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.GroupVersionForDiscovery": schema_pkg_apis_meta_v1_GroupVersionForDiscovery(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.GroupVersionKind": schema_pkg_apis_meta_v1_GroupVersionKind(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.GroupVersionResource": schema_pkg_apis_meta_v1_GroupVersionResource(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.InternalEvent": schema_pkg_apis_meta_v1_InternalEvent(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector": schema_pkg_apis_meta_v1_LabelSelector(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelectorRequirement": schema_pkg_apis_meta_v1_LabelSelectorRequirement(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.List": schema_pkg_apis_meta_v1_List(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta": schema_pkg_apis_meta_v1_ListMeta(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.ListOptions": schema_pkg_apis_meta_v1_ListOptions(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsEntry": schema_pkg_apis_meta_v1_ManagedFieldsEntry(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.MicroTime": schema_pkg_apis_meta_v1_MicroTime(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta": schema_pkg_apis_meta_v1_ObjectMeta(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.OwnerReference": schema_pkg_apis_meta_v1_OwnerReference(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.PartialObjectMetadata": schema_pkg_apis_meta_v1_PartialObjectMetadata(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.PartialObjectMetadataList": schema_pkg_apis_meta_v1_PartialObjectMetadataList(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.Patch": schema_pkg_apis_meta_v1_Patch(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.PatchOptions": schema_pkg_apis_meta_v1_PatchOptions(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.Preconditions": schema_pkg_apis_meta_v1_Preconditions(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.RootPaths": schema_pkg_apis_meta_v1_RootPaths(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.ServerAddressByClientCIDR": schema_pkg_apis_meta_v1_ServerAddressByClientCIDR(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.Status": schema_pkg_apis_meta_v1_Status(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.StatusCause": schema_pkg_apis_meta_v1_StatusCause(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.StatusDetails": schema_pkg_apis_meta_v1_StatusDetails(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.Table": schema_pkg_apis_meta_v1_Table(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.TableColumnDefinition": schema_pkg_apis_meta_v1_TableColumnDefinition(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.TableOptions": schema_pkg_apis_meta_v1_TableOptions(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.TableRow": schema_pkg_apis_meta_v1_TableRow(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.TableRowCondition": schema_pkg_apis_meta_v1_TableRowCondition(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.Time": schema_pkg_apis_meta_v1_Time(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.Timestamp": schema_pkg_apis_meta_v1_Timestamp(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.TypeMeta": schema_pkg_apis_meta_v1_TypeMeta(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.UpdateOptions": schema_pkg_apis_meta_v1_UpdateOptions(ref), - "k8s.io/apimachinery/pkg/apis/meta/v1.WatchEvent": schema_pkg_apis_meta_v1_WatchEvent(ref), - "k8s.io/apimachinery/pkg/runtime.RawExtension": schema_k8sio_apimachinery_pkg_runtime_RawExtension(ref), - "k8s.io/apimachinery/pkg/runtime.TypeMeta": schema_k8sio_apimachinery_pkg_runtime_TypeMeta(ref), - "k8s.io/apimachinery/pkg/runtime.Unknown": schema_k8sio_apimachinery_pkg_runtime_Unknown(ref), - "k8s.io/apimachinery/pkg/version.Info": schema_k8sio_apimachinery_pkg_version_Info(ref), + resource.Quantity{}.OpenAPIModelName(): schema_apimachinery_pkg_api_resource_Quantity(ref), + v1.APIGroup{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_APIGroup(ref), + v1.APIGroupList{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_APIGroupList(ref), + v1.APIResource{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_APIResource(ref), + v1.APIResourceList{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_APIResourceList(ref), + v1.APIVersions{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_APIVersions(ref), + v1.ApplyOptions{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_ApplyOptions(ref), + v1.Condition{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_Condition(ref), + v1.CreateOptions{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_CreateOptions(ref), + v1.DeleteOptions{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_DeleteOptions(ref), + v1.Duration{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_Duration(ref), + v1.FieldSelectorRequirement{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_FieldSelectorRequirement(ref), + v1.FieldsV1{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_FieldsV1(ref), + v1.GetOptions{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_GetOptions(ref), + v1.GroupKind{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_GroupKind(ref), + v1.GroupResource{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_GroupResource(ref), + v1.GroupVersion{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_GroupVersion(ref), + v1.GroupVersionForDiscovery{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_GroupVersionForDiscovery(ref), + v1.GroupVersionKind{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_GroupVersionKind(ref), + v1.GroupVersionResource{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_GroupVersionResource(ref), + v1.InternalEvent{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_InternalEvent(ref), + v1.LabelSelector{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_LabelSelector(ref), + v1.LabelSelectorRequirement{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_LabelSelectorRequirement(ref), + v1.List{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_List(ref), + v1.ListMeta{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_ListMeta(ref), + v1.ListOptions{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_ListOptions(ref), + v1.ManagedFieldsEntry{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_ManagedFieldsEntry(ref), + v1.MicroTime{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_MicroTime(ref), + v1.ObjectMeta{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_ObjectMeta(ref), + v1.OwnerReference{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_OwnerReference(ref), + v1.PartialObjectMetadata{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_PartialObjectMetadata(ref), + v1.PartialObjectMetadataList{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_PartialObjectMetadataList(ref), + v1.Patch{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_Patch(ref), + v1.PatchOptions{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_PatchOptions(ref), + v1.Preconditions{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_Preconditions(ref), + v1.RootPaths{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_RootPaths(ref), + v1.ServerAddressByClientCIDR{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_ServerAddressByClientCIDR(ref), + v1.Status{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_Status(ref), + v1.StatusCause{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_StatusCause(ref), + v1.StatusDetails{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_StatusDetails(ref), + v1.Table{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_Table(ref), + v1.TableColumnDefinition{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_TableColumnDefinition(ref), + v1.TableOptions{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_TableOptions(ref), + v1.TableRow{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_TableRow(ref), + v1.TableRowCondition{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_TableRowCondition(ref), + v1.Time{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_Time(ref), + v1.Timestamp{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_Timestamp(ref), + v1.TypeMeta{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_TypeMeta(ref), + v1.UpdateOptions{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_UpdateOptions(ref), + v1.WatchEvent{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_WatchEvent(ref), + runtime.RawExtension{}.OpenAPIModelName(): schema_k8sio_apimachinery_pkg_runtime_RawExtension(ref), + runtime.TypeMeta{}.OpenAPIModelName(): schema_k8sio_apimachinery_pkg_runtime_TypeMeta(ref), + runtime.Unknown{}.OpenAPIModelName(): schema_k8sio_apimachinery_pkg_runtime_Unknown(ref), + version.Info{}.OpenAPIModelName(): schema_k8sio_apimachinery_pkg_version_Info(ref), } } @@ -165,7 +168,7 @@ func schema_pkg_apis_search_v1alpha1_ResourceIndexPolicy(ref common.ReferenceCal "metadata": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + Ref: ref(v1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { @@ -185,7 +188,7 @@ func schema_pkg_apis_search_v1alpha1_ResourceIndexPolicy(ref common.ReferenceCal }, }, Dependencies: []string{ - "go.miloapis.net/search/pkg/apis/search/v1alpha1.ResourceIndexPolicySpec", "go.miloapis.net/search/pkg/apis/search/v1alpha1.ResourceIndexPolicyStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "go.miloapis.net/search/pkg/apis/search/v1alpha1.ResourceIndexPolicySpec", "go.miloapis.net/search/pkg/apis/search/v1alpha1.ResourceIndexPolicyStatus", v1.ObjectMeta{}.OpenAPIModelName()}, } } @@ -213,7 +216,7 @@ func schema_pkg_apis_search_v1alpha1_ResourceIndexPolicyList(ref common.Referenc "metadata": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + Ref: ref(v1.ListMeta{}.OpenAPIModelName()), }, }, "items": { @@ -234,7 +237,7 @@ func schema_pkg_apis_search_v1alpha1_ResourceIndexPolicyList(ref common.Referenc }, }, Dependencies: []string{ - "go.miloapis.net/search/pkg/apis/search/v1alpha1.ResourceIndexPolicy", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + "go.miloapis.net/search/pkg/apis/search/v1alpha1.ResourceIndexPolicy", v1.ListMeta{}.OpenAPIModelName()}, } } @@ -253,6 +256,14 @@ func schema_pkg_apis_search_v1alpha1_ResourceIndexPolicySpec(ref common.Referenc }, }, "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + }, + }, SchemaProps: spec.SchemaProps{ Description: "Conditions filter which resources are indexed using CEL expressions. Multiple conditions can be specified and are evaluated with OR semantics - a resource is indexed if it satisfies ANY condition. Use && within a single expression to require multiple criteria together.\n\nEach condition has: - name: A unique identifier for the condition, used in status reporting\n and debugging to identify which condition(s) matched a resource.\n- expression: A CEL expression that must evaluate to a boolean. The\n resource is available as the root object in the expression context.\n\nAvailable CEL operations: - Field access: spec.replicas, metadata.name, status.phase - Map access: metadata.labels[\"app\"], metadata.annotations[\"key\"] - Comparisons: ==, !=, <, <=, >, >= - Logical operators: &&, ||, ! - String functions: contains(), startsWith(), endsWith(), matches() - List functions: exists(), all(), size(), map(), filter() - Membership: \"value\" in list, \"key\" in map - Ternary: condition ? trueValue : falseValue", Type: []string{"array"}, @@ -267,6 +278,14 @@ func schema_pkg_apis_search_v1alpha1_ResourceIndexPolicySpec(ref common.Referenc }, }, "fields": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "path", + }, + "x-kubernetes-list-type": "map", + }, + }, SchemaProps: spec.SchemaProps{ Description: "Fields defines which fields from the resource are indexed.", Type: []string{"array"}, @@ -297,6 +316,14 @@ func schema_pkg_apis_search_v1alpha1_ResourceIndexPolicyStatus(ref common.Refere Type: []string{"object"}, Properties: map[string]spec.Schema{ "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + }, + }, SchemaProps: spec.SchemaProps{ Description: "Conditions represents the latest available observations of the policy's state.", Type: []string{"array"}, @@ -304,7 +331,7 @@ func schema_pkg_apis_search_v1alpha1_ResourceIndexPolicyStatus(ref common.Refere Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Condition"), + Ref: ref(v1.Condition{}.OpenAPIModelName()), }, }, }, @@ -328,7 +355,7 @@ func schema_pkg_apis_search_v1alpha1_ResourceIndexPolicyStatus(ref common.Refere }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + v1.Condition{}.OpenAPIModelName()}, } } @@ -356,7 +383,7 @@ func schema_pkg_apis_search_v1alpha1_SearchQuery(ref common.ReferenceCallback) c "metadata": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + Ref: ref(v1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { @@ -376,7 +403,7 @@ func schema_pkg_apis_search_v1alpha1_SearchQuery(ref common.ReferenceCallback) c }, }, Dependencies: []string{ - "go.miloapis.net/search/pkg/apis/search/v1alpha1.SearchQuerySpec", "go.miloapis.net/search/pkg/apis/search/v1alpha1.SearchQueryStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "go.miloapis.net/search/pkg/apis/search/v1alpha1.SearchQuerySpec", "go.miloapis.net/search/pkg/apis/search/v1alpha1.SearchQueryStatus", v1.ObjectMeta{}.OpenAPIModelName()}, } } @@ -404,7 +431,7 @@ func schema_pkg_apis_search_v1alpha1_SearchQueryList(ref common.ReferenceCallbac "metadata": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + Ref: ref(v1.ListMeta{}.OpenAPIModelName()), }, }, "items": { @@ -425,7 +452,7 @@ func schema_pkg_apis_search_v1alpha1_SearchQueryList(ref common.ReferenceCallbac }, }, Dependencies: []string{ - "go.miloapis.net/search/pkg/apis/search/v1alpha1.SearchQuery", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + "go.miloapis.net/search/pkg/apis/search/v1alpha1.SearchQuery", v1.ListMeta{}.OpenAPIModelName()}, } } @@ -436,6 +463,25 @@ func schema_pkg_apis_search_v1alpha1_SearchQuerySpec(ref common.ReferenceCallbac Description: "SearchQuerySpec defines the search parameters.\n\nThe actual fields will depend on the specific search implementation.", Type: []string{"object"}, Properties: map[string]spec.Schema{ + "targetResources": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "TargetResources limits the search to specific resource types.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("go.miloapis.net/search/pkg/apis/search/v1alpha1.TargetResource"), + }, + }, + }, + }, + }, "query": { SchemaProps: spec.SchemaProps{ Description: "Query is the search query string.", @@ -462,6 +508,8 @@ func schema_pkg_apis_search_v1alpha1_SearchQuerySpec(ref common.ReferenceCallbac Required: []string{"query"}, }, }, + Dependencies: []string{ + "go.miloapis.net/search/pkg/apis/search/v1alpha1.TargetResource"}, } } @@ -473,14 +521,19 @@ func schema_pkg_apis_search_v1alpha1_SearchQueryStatus(ref common.ReferenceCallb Type: []string{"object"}, Properties: map[string]spec.Schema{ "results": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, SchemaProps: spec.SchemaProps{ - Description: "Results contains the search results as an array of Kubernetes resources.", + Description: "Results contains the search results.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/runtime.RawExtension"), + Ref: ref("go.miloapis.net/search/pkg/apis/search/v1alpha1.SearchResult"), }, }, }, @@ -497,7 +550,36 @@ func schema_pkg_apis_search_v1alpha1_SearchQueryStatus(ref common.ReferenceCallb }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/runtime.RawExtension"}, + "go.miloapis.net/search/pkg/apis/search/v1alpha1.SearchResult"}, + } +} + +func schema_pkg_apis_search_v1alpha1_SearchResult(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SearchResult represents a single search result with its relevance score.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "resource": { + SchemaProps: spec.SchemaProps{ + Description: "Resource contains the actual Kubernetes resource.", + Ref: ref(runtime.RawExtension{}.OpenAPIModelName()), + }, + }, + "relevanceScore": { + SchemaProps: spec.SchemaProps{ + Description: "RelevanceScore is the relevance score from Meilisearch.", + Type: []string{"number"}, + Format: "double", + }, + }, + }, + Required: []string{"resource"}, + }, + }, + Dependencies: []string{ + runtime.RawExtension{}.OpenAPIModelName()}, } } @@ -539,6 +621,54 @@ func schema_pkg_apis_search_v1alpha1_TargetResource(ref common.ReferenceCallback } } +func schema_apimachinery_pkg_api_resource_Quantity(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.EmbedOpenAPIDefinitionIntoV2Extension(common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Quantity is a fixed-point representation of a number. It provides convenient marshaling/unmarshaling in JSON and YAML, in addition to String() and AsInt64() accessors.\n\nThe serialization format is:\n\n``` ::= \n\n\t(Note that may be empty, from the \"\" case in .)\n\n ::= 0 | 1 | ... | 9 ::= | ::= | . | . | . ::= \"+\" | \"-\" ::= | ::= | | ::= Ki | Mi | Gi | Ti | Pi | Ei\n\n\t(International System of units; See: http://physics.nist.gov/cuu/Units/binary.html)\n\n ::= m | \"\" | k | M | G | T | P | E\n\n\t(Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.)\n\n ::= \"e\" | \"E\" ```\n\nNo matter which of the three exponent forms is used, no quantity may represent a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal places. Numbers larger or more precise will be capped or rounded up. (E.g.: 0.1m will rounded up to 1m.) This may be extended in the future if we require larger or smaller quantities.\n\nWhen a Quantity is parsed from a string, it will remember the type of suffix it had, and will use the same type again when it is serialized.\n\nBefore serializing, Quantity will be put in \"canonical form\". This means that Exponent/suffix will be adjusted up or down (with a corresponding increase or decrease in Mantissa) such that:\n\n- No precision is lost - No fractional digits will be emitted - The exponent (or suffix) is as large as possible.\n\nThe sign will be omitted unless the number is negative.\n\nExamples:\n\n- 1.5 will be serialized as \"1500m\" - 1.5Gi will be serialized as \"1536Mi\"\n\nNote that the quantity will NEVER be internally represented by a floating point number. That is the whole point of this exercise.\n\nNon-canonical values will still parse as long as they are well formed, but will be re-emitted in their canonical form. (So always use canonical form, or don't diff.)\n\nThis format is intended to make it difficult to use these numbers without writing some sort of special handling code in the hopes that that will cause implementors to also use a fixed point implementation.", + OneOf: common.GenerateOpenAPIV3OneOfSchema(resource.Quantity{}.OpenAPIV3OneOfTypes()), + Format: resource.Quantity{}.OpenAPISchemaFormat(), + }, + }, + }, common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Quantity is a fixed-point representation of a number. It provides convenient marshaling/unmarshaling in JSON and YAML, in addition to String() and AsInt64() accessors.\n\nThe serialization format is:\n\n``` ::= \n\n\t(Note that may be empty, from the \"\" case in .)\n\n ::= 0 | 1 | ... | 9 ::= | ::= | . | . | . ::= \"+\" | \"-\" ::= | ::= | | ::= Ki | Mi | Gi | Ti | Pi | Ei\n\n\t(International System of units; See: http://physics.nist.gov/cuu/Units/binary.html)\n\n ::= m | \"\" | k | M | G | T | P | E\n\n\t(Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.)\n\n ::= \"e\" | \"E\" ```\n\nNo matter which of the three exponent forms is used, no quantity may represent a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal places. Numbers larger or more precise will be capped or rounded up. (E.g.: 0.1m will rounded up to 1m.) This may be extended in the future if we require larger or smaller quantities.\n\nWhen a Quantity is parsed from a string, it will remember the type of suffix it had, and will use the same type again when it is serialized.\n\nBefore serializing, Quantity will be put in \"canonical form\". This means that Exponent/suffix will be adjusted up or down (with a corresponding increase or decrease in Mantissa) such that:\n\n- No precision is lost - No fractional digits will be emitted - The exponent (or suffix) is as large as possible.\n\nThe sign will be omitted unless the number is negative.\n\nExamples:\n\n- 1.5 will be serialized as \"1500m\" - 1.5Gi will be serialized as \"1536Mi\"\n\nNote that the quantity will NEVER be internally represented by a floating point number. That is the whole point of this exercise.\n\nNon-canonical values will still parse as long as they are well formed, but will be re-emitted in their canonical form. (So always use canonical form, or don't diff.)\n\nThis format is intended to make it difficult to use these numbers without writing some sort of special handling code in the hopes that that will cause implementors to also use a fixed point implementation.", + Type: resource.Quantity{}.OpenAPISchemaType(), + Format: resource.Quantity{}.OpenAPISchemaFormat(), + }, + }, + }) +} + +func schema_apimachinery_pkg_api_resource_int64Amount(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "int64Amount represents a fixed precision numerator and arbitrary scale exponent. It is faster than operations on inf.Dec for values that can be represented as int64.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "value": { + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + "scale": { + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"value", "scale"}, + }, + }, + } +} + func schema_pkg_apis_meta_v1_APIGroup(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -581,7 +711,7 @@ func schema_pkg_apis_meta_v1_APIGroup(ref common.ReferenceCallback) common.OpenA Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.GroupVersionForDiscovery"), + Ref: ref(v1.GroupVersionForDiscovery{}.OpenAPIModelName()), }, }, }, @@ -591,7 +721,7 @@ func schema_pkg_apis_meta_v1_APIGroup(ref common.ReferenceCallback) common.OpenA SchemaProps: spec.SchemaProps{ Description: "preferredVersion is the version preferred by the API server, which probably is the storage version.", Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.GroupVersionForDiscovery"), + Ref: ref(v1.GroupVersionForDiscovery{}.OpenAPIModelName()), }, }, "serverAddressByClientCIDRs": { @@ -607,7 +737,7 @@ func schema_pkg_apis_meta_v1_APIGroup(ref common.ReferenceCallback) common.OpenA Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ServerAddressByClientCIDR"), + Ref: ref(v1.ServerAddressByClientCIDR{}.OpenAPIModelName()), }, }, }, @@ -618,7 +748,7 @@ func schema_pkg_apis_meta_v1_APIGroup(ref common.ReferenceCallback) common.OpenA }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/apis/meta/v1.GroupVersionForDiscovery", "k8s.io/apimachinery/pkg/apis/meta/v1.ServerAddressByClientCIDR"}, + v1.GroupVersionForDiscovery{}.OpenAPIModelName(), v1.ServerAddressByClientCIDR{}.OpenAPIModelName()}, } } @@ -656,7 +786,7 @@ func schema_pkg_apis_meta_v1_APIGroupList(ref common.ReferenceCallback) common.O Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.APIGroup"), + Ref: ref(v1.APIGroup{}.OpenAPIModelName()), }, }, }, @@ -667,7 +797,7 @@ func schema_pkg_apis_meta_v1_APIGroupList(ref common.ReferenceCallback) common.O }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/apis/meta/v1.APIGroup"}, + v1.APIGroup{}.OpenAPIModelName()}, } } @@ -835,7 +965,7 @@ func schema_pkg_apis_meta_v1_APIResourceList(ref common.ReferenceCallback) commo Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.APIResource"), + Ref: ref(v1.APIResource{}.OpenAPIModelName()), }, }, }, @@ -846,7 +976,7 @@ func schema_pkg_apis_meta_v1_APIResourceList(ref common.ReferenceCallback) commo }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/apis/meta/v1.APIResource"}, + v1.APIResource{}.OpenAPIModelName()}, } } @@ -904,7 +1034,7 @@ func schema_pkg_apis_meta_v1_APIVersions(ref common.ReferenceCallback) common.Op Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ServerAddressByClientCIDR"), + Ref: ref(v1.ServerAddressByClientCIDR{}.OpenAPIModelName()), }, }, }, @@ -915,7 +1045,7 @@ func schema_pkg_apis_meta_v1_APIVersions(ref common.ReferenceCallback) common.Op }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/apis/meta/v1.ServerAddressByClientCIDR"}, + v1.ServerAddressByClientCIDR{}.OpenAPIModelName()}, } } @@ -1016,8 +1146,7 @@ func schema_pkg_apis_meta_v1_Condition(ref common.ReferenceCallback) common.Open "lastTransitionTime": { SchemaProps: spec.SchemaProps{ 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.", - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + Ref: ref(v1.Time{}.OpenAPIModelName()), }, }, "reason": { @@ -1041,7 +1170,7 @@ func schema_pkg_apis_meta_v1_Condition(ref common.ReferenceCallback) common.Open }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + v1.Time{}.OpenAPIModelName()}, } } @@ -1137,7 +1266,7 @@ func schema_pkg_apis_meta_v1_DeleteOptions(ref common.ReferenceCallback) common. "preconditions": { SchemaProps: spec.SchemaProps{ Description: "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.", - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Preconditions"), + Ref: ref(v1.Preconditions{}.OpenAPIModelName()), }, }, "orphanDependents": { @@ -1185,7 +1314,7 @@ func schema_pkg_apis_meta_v1_DeleteOptions(ref common.ReferenceCallback) common. }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/apis/meta/v1.Preconditions"}, + v1.Preconditions{}.OpenAPIModelName()}, } } @@ -1497,15 +1626,12 @@ func schema_pkg_apis_meta_v1_InternalEvent(ref common.ReferenceCallback) common. "Object": { SchemaProps: spec.SchemaProps{ Description: "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Bookmark: the object (instance of a type being watched) where\n only ResourceVersion field is set. On successful restart of watch from a\n bookmark resourceVersion, client is guaranteed to not get repeat event\n nor miss any events.\n * If Type is Error: *api.Status is recommended; other types may make sense\n depending on context.", - Ref: ref("k8s.io/apimachinery/pkg/runtime.Object"), }, }, }, Required: []string{"Type", "Object"}, }, }, - Dependencies: []string{ - "k8s.io/apimachinery/pkg/runtime.Object"}, } } @@ -1545,7 +1671,7 @@ func schema_pkg_apis_meta_v1_LabelSelector(ref common.ReferenceCallback) common. Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelectorRequirement"), + Ref: ref(v1.LabelSelectorRequirement{}.OpenAPIModelName()), }, }, }, @@ -1560,7 +1686,7 @@ func schema_pkg_apis_meta_v1_LabelSelector(ref common.ReferenceCallback) common. }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelectorRequirement"}, + v1.LabelSelectorRequirement{}.OpenAPIModelName()}, } } @@ -1639,7 +1765,7 @@ func schema_pkg_apis_meta_v1_List(ref common.ReferenceCallback) common.OpenAPIDe SchemaProps: spec.SchemaProps{ Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + Ref: ref(v1.ListMeta{}.OpenAPIModelName()), }, }, "items": { @@ -1649,8 +1775,7 @@ func schema_pkg_apis_meta_v1_List(ref common.ReferenceCallback) common.OpenAPIDe Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/runtime.RawExtension"), + Ref: ref(runtime.RawExtension{}.OpenAPIModelName()), }, }, }, @@ -1661,7 +1786,7 @@ func schema_pkg_apis_meta_v1_List(ref common.ReferenceCallback) common.OpenAPIDe }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "k8s.io/apimachinery/pkg/runtime.RawExtension"}, + v1.ListMeta{}.OpenAPIModelName(), runtime.RawExtension{}.OpenAPIModelName()}, } } @@ -1834,7 +1959,7 @@ func schema_pkg_apis_meta_v1_ManagedFieldsEntry(ref common.ReferenceCallback) co "time": { SchemaProps: spec.SchemaProps{ Description: "Time is the timestamp of when the ManagedFields entry was added. The timestamp will also be updated if a field is added, the manager changes any of the owned fields value or removes a field. The timestamp does not update when a field is removed from the entry because another manager took it over.", - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + Ref: ref(v1.Time{}.OpenAPIModelName()), }, }, "fieldsType": { @@ -1847,7 +1972,7 @@ func schema_pkg_apis_meta_v1_ManagedFieldsEntry(ref common.ReferenceCallback) co "fieldsV1": { SchemaProps: spec.SchemaProps{ Description: "FieldsV1 holds the first JSON version format as described in the \"FieldsV1\" type.", - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.FieldsV1"), + Ref: ref(v1.FieldsV1{}.OpenAPIModelName()), }, }, "subresource": { @@ -1861,7 +1986,7 @@ func schema_pkg_apis_meta_v1_ManagedFieldsEntry(ref common.ReferenceCallback) co }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/apis/meta/v1.FieldsV1", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + v1.FieldsV1{}.OpenAPIModelName(), v1.Time{}.OpenAPIModelName()}, } } @@ -1936,14 +2061,13 @@ func schema_pkg_apis_meta_v1_ObjectMeta(ref common.ReferenceCallback) common.Ope "creationTimestamp": { SchemaProps: spec.SchemaProps{ Description: "CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\n\nPopulated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + Ref: ref(v1.Time{}.OpenAPIModelName()), }, }, "deletionTimestamp": { SchemaProps: spec.SchemaProps{ Description: "DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This field is set by the server when a graceful deletion is requested by the user, and is not directly settable by a client. The resource is expected to be deleted (no longer visible from resource lists, and not reachable by name) after the time in this field, once the finalizers list is empty. As long as the finalizers list contains items, deletion is blocked. Once the deletionTimestamp is set, this value may not be unset or be set further into the future, although it may be shortened or the resource may be deleted prior to this time. For example, a user may request that a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination signal to the containers in the pod. After that 30 seconds, the Kubelet will send a hard termination signal (SIGKILL) to the container and after cleanup, remove the pod from the API. In the presence of network partitions, this object may still exist after this timestamp, until an administrator or automated process can determine the resource is fully terminated. If not set, graceful deletion of the object has not been requested.\n\nPopulated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + Ref: ref(v1.Time{}.OpenAPIModelName()), }, }, "deletionGracePeriodSeconds": { @@ -2003,7 +2127,7 @@ func schema_pkg_apis_meta_v1_ObjectMeta(ref common.ReferenceCallback) common.Ope Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.OwnerReference"), + Ref: ref(v1.OwnerReference{}.OpenAPIModelName()), }, }, }, @@ -2043,7 +2167,7 @@ func schema_pkg_apis_meta_v1_ObjectMeta(ref common.ReferenceCallback) common.Ope Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsEntry"), + Ref: ref(v1.ManagedFieldsEntry{}.OpenAPIModelName()), }, }, }, @@ -2053,7 +2177,7 @@ func schema_pkg_apis_meta_v1_ObjectMeta(ref common.ReferenceCallback) common.Ope }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsEntry", "k8s.io/apimachinery/pkg/apis/meta/v1.OwnerReference", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + v1.ManagedFieldsEntry{}.OpenAPIModelName(), v1.OwnerReference{}.OpenAPIModelName(), v1.Time{}.OpenAPIModelName()}, } } @@ -2147,14 +2271,14 @@ func schema_pkg_apis_meta_v1_PartialObjectMetadata(ref common.ReferenceCallback) SchemaProps: spec.SchemaProps{ Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + Ref: ref(v1.ObjectMeta{}.OpenAPIModelName()), }, }, }, }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + v1.ObjectMeta{}.OpenAPIModelName()}, } } @@ -2183,7 +2307,7 @@ func schema_pkg_apis_meta_v1_PartialObjectMetadataList(ref common.ReferenceCallb SchemaProps: spec.SchemaProps{ Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + Ref: ref(v1.ListMeta{}.OpenAPIModelName()), }, }, "items": { @@ -2194,7 +2318,7 @@ func schema_pkg_apis_meta_v1_PartialObjectMetadataList(ref common.ReferenceCallb Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.PartialObjectMetadata"), + Ref: ref(v1.PartialObjectMetadata{}.OpenAPIModelName()), }, }, }, @@ -2205,7 +2329,7 @@ func schema_pkg_apis_meta_v1_PartialObjectMetadataList(ref common.ReferenceCallb }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "k8s.io/apimachinery/pkg/apis/meta/v1.PartialObjectMetadata"}, + v1.ListMeta{}.OpenAPIModelName(), v1.PartialObjectMetadata{}.OpenAPIModelName()}, } } @@ -2404,7 +2528,7 @@ func schema_pkg_apis_meta_v1_Status(ref common.ReferenceCallback) common.OpenAPI SchemaProps: spec.SchemaProps{ Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + Ref: ref(v1.ListMeta{}.OpenAPIModelName()), }, }, "status": { @@ -2431,7 +2555,7 @@ func schema_pkg_apis_meta_v1_Status(ref common.ReferenceCallback) common.OpenAPI "details": { SchemaProps: spec.SchemaProps{ Description: "Extended data associated with the reason. Each reason may define its own extended details. This field is optional and the data returned is not guaranteed to conform to any schema except that defined by the reason type.", - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.StatusDetails"), + Ref: ref(v1.StatusDetails{}.OpenAPIModelName()), }, }, "code": { @@ -2445,7 +2569,7 @@ func schema_pkg_apis_meta_v1_Status(ref common.ReferenceCallback) common.OpenAPI }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "k8s.io/apimachinery/pkg/apis/meta/v1.StatusDetails"}, + v1.ListMeta{}.OpenAPIModelName(), v1.StatusDetails{}.OpenAPIModelName()}, } } @@ -2531,7 +2655,7 @@ func schema_pkg_apis_meta_v1_StatusDetails(ref common.ReferenceCallback) common. Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.StatusCause"), + Ref: ref(v1.StatusCause{}.OpenAPIModelName()), }, }, }, @@ -2548,7 +2672,7 @@ func schema_pkg_apis_meta_v1_StatusDetails(ref common.ReferenceCallback) common. }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/apis/meta/v1.StatusCause"}, + v1.StatusCause{}.OpenAPIModelName()}, } } @@ -2577,7 +2701,7 @@ func schema_pkg_apis_meta_v1_Table(ref common.ReferenceCallback) common.OpenAPID SchemaProps: spec.SchemaProps{ Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + Ref: ref(v1.ListMeta{}.OpenAPIModelName()), }, }, "columnDefinitions": { @@ -2593,7 +2717,7 @@ func schema_pkg_apis_meta_v1_Table(ref common.ReferenceCallback) common.OpenAPID Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.TableColumnDefinition"), + Ref: ref(v1.TableColumnDefinition{}.OpenAPIModelName()), }, }, }, @@ -2612,7 +2736,7 @@ func schema_pkg_apis_meta_v1_Table(ref common.ReferenceCallback) common.OpenAPID Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.TableRow"), + Ref: ref(v1.TableRow{}.OpenAPIModelName()), }, }, }, @@ -2623,7 +2747,7 @@ func schema_pkg_apis_meta_v1_Table(ref common.ReferenceCallback) common.OpenAPID }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "k8s.io/apimachinery/pkg/apis/meta/v1.TableColumnDefinition", "k8s.io/apimachinery/pkg/apis/meta/v1.TableRow"}, + v1.ListMeta{}.OpenAPIModelName(), v1.TableColumnDefinition{}.OpenAPIModelName(), v1.TableRow{}.OpenAPIModelName()}, } } @@ -2754,7 +2878,7 @@ func schema_pkg_apis_meta_v1_TableRow(ref common.ReferenceCallback) common.OpenA Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.TableRowCondition"), + Ref: ref(v1.TableRowCondition{}.OpenAPIModelName()), }, }, }, @@ -2763,8 +2887,7 @@ func schema_pkg_apis_meta_v1_TableRow(ref common.ReferenceCallback) common.OpenA "object": { SchemaProps: spec.SchemaProps{ Description: "This field contains the requested additional information about each object based on the includeObject policy when requesting the Table. If \"None\", this field is empty, if \"Object\" this will be the default serialization of the object for the current API version, and if \"Metadata\" (the default) will contain the object metadata. Check the returned kind and apiVersion of the object before parsing. The media type of the object will always match the enclosing list - if this as a JSON table, these will be JSON encoded objects.", - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/runtime.RawExtension"), + Ref: ref(runtime.RawExtension{}.OpenAPIModelName()), }, }, }, @@ -2772,7 +2895,7 @@ func schema_pkg_apis_meta_v1_TableRow(ref common.ReferenceCallback) common.OpenA }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/apis/meta/v1.TableRowCondition", "k8s.io/apimachinery/pkg/runtime.RawExtension"}, + v1.TableRowCondition{}.OpenAPIModelName(), runtime.RawExtension{}.OpenAPIModelName()}, } } @@ -2967,8 +3090,7 @@ func schema_pkg_apis_meta_v1_WatchEvent(ref common.ReferenceCallback) common.Ope "object": { SchemaProps: spec.SchemaProps{ Description: "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context.", - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/runtime.RawExtension"), + Ref: ref(runtime.RawExtension{}.OpenAPIModelName()), }, }, }, @@ -2976,7 +3098,7 @@ func schema_pkg_apis_meta_v1_WatchEvent(ref common.ReferenceCallback) common.Ope }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/runtime.RawExtension"}, + runtime.RawExtension{}.OpenAPIModelName()}, } } diff --git a/pkg/meilisearch/sdk_client.go b/pkg/meilisearch/sdk_client.go index c8758df..6c84e62 100644 --- a/pkg/meilisearch/sdk_client.go +++ b/pkg/meilisearch/sdk_client.go @@ -353,3 +353,36 @@ func (s *SDKClient) GetSettingsUpdateTask(indexUID string) (*meilisearch.Task, e // Return the most recent task return &resp.Results[0], nil } + +// MultiSearch performs a search query across multiple indices. +func (s *SDKClient) MultiSearch(indexUIDs []string, query string, limit int64, offset int64) (*meilisearch.MultiSearchResponse, error) { + if len(indexUIDs) == 0 { + return nil, fmt.Errorf("no index UIDs provided") + } + + var queries []*meilisearch.SearchRequest + for _, uid := range indexUIDs { + queries = append(queries, &meilisearch.SearchRequest{ + IndexUID: uid, + Query: query, + ShowRankingScore: true, + }) + } + + req := &meilisearch.MultiSearchRequest{ + Queries: queries, + Federation: &meilisearch.MultiSearchFederation{ + Limit: limit, + Offset: offset, + }, + } + + klog.V(4).Infof("MultiSearch across indices %v with query %q, limit %d, offset %d", indexUIDs, query, limit, offset) + resp, err := s.client.MultiSearch(req) + if err != nil { + klog.Errorf("MultiSearch failed across indices %v: %v", indexUIDs, err) + return nil, fmt.Errorf("multi-search failed: %w", err) + } + + return resp, nil +} diff --git a/zz_generated.openapi.go b/zz_generated.openapi.go new file mode 100644 index 0000000..253a22d --- /dev/null +++ b/zz_generated.openapi.go @@ -0,0 +1,300 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// Code generated by openapi-gen. DO NOT EDIT. + +// This file was autogenerated by openapi-gen. Do not edit it manually! + +package v1alpha1 + +import ( + common "k8s.io/kube-openapi/pkg/common" + spec "k8s.io/kube-openapi/pkg/validation/spec" +) + +func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { + return map[string]common.OpenAPIDefinition{ + "go.miloapis.com/milo/pkg/apis/identity/v1alpha1.Session": schema_pkg_apis_identity_v1alpha1_Session(ref), + "go.miloapis.com/milo/pkg/apis/identity/v1alpha1.SessionList": schema_pkg_apis_identity_v1alpha1_SessionList(ref), + "go.miloapis.com/milo/pkg/apis/identity/v1alpha1.SessionStatus": schema_pkg_apis_identity_v1alpha1_SessionStatus(ref), + "go.miloapis.com/milo/pkg/apis/identity/v1alpha1.UserIdentity": schema_pkg_apis_identity_v1alpha1_UserIdentity(ref), + "go.miloapis.com/milo/pkg/apis/identity/v1alpha1.UserIdentityList": schema_pkg_apis_identity_v1alpha1_UserIdentityList(ref), + "go.miloapis.com/milo/pkg/apis/identity/v1alpha1.UserIdentityStatus": schema_pkg_apis_identity_v1alpha1_UserIdentityStatus(ref), + } +} + +func schema_pkg_apis_identity_v1alpha1_Session(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + 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{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + 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{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("go.miloapis.com/milo/pkg/apis/identity/v1alpha1.SessionStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "go.miloapis.com/milo/pkg/apis/identity/v1alpha1.SessionStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_identity_v1alpha1_SessionList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + 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{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + 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{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("go.miloapis.com/milo/pkg/apis/identity/v1alpha1.Session"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "go.miloapis.com/milo/pkg/apis/identity/v1alpha1.Session", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_identity_v1alpha1_SessionStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "userUID": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "provider": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "ip": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "fingerprintID": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "createdAt": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "expiresAt": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + }, + Required: []string{"userUID", "provider", "createdAt"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_pkg_apis_identity_v1alpha1_UserIdentity(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "UserIdentity represents a user's linked identity within an external identity provider.\n\nThis resource describes the connection between a Milo user and their account in an external authentication provider (e.g., GitHub, Google, Microsoft). It is NOT the identity provider itself, but rather the user's specific identity within that provider.\n\nUse cases:\n - Display all authentication methods linked to a user account in the UI\n - Show which external accounts a user has connected\n - Provide visibility into federated identity mappings\n\nImportant notes:\n - This is a read-only resource for display purposes only\n - Identity management (linking/unlinking providers) is handled by the external\n authentication provider (e.g., Zitadel), not through this API\n - No sensitive credentials or tokens are exposed through this resource", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + 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{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + 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{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("go.miloapis.com/milo/pkg/apis/identity/v1alpha1.UserIdentityStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "go.miloapis.com/milo/pkg/apis/identity/v1alpha1.UserIdentityStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_identity_v1alpha1_UserIdentityList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "UserIdentityList is a list of UserIdentity resources.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + 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{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + 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{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("go.miloapis.com/milo/pkg/apis/identity/v1alpha1.UserIdentity"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "go.miloapis.com/milo/pkg/apis/identity/v1alpha1.UserIdentity", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_identity_v1alpha1_UserIdentityStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "UserIdentityStatus contains the details of a user's identity within an external provider. All fields are read-only and populated by the authentication provider.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "userUID": { + SchemaProps: spec.SchemaProps{ + Description: "UserUID is the unique identifier of the Milo user who owns this identity.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "providerID": { + SchemaProps: spec.SchemaProps{ + Description: "ProviderID is the unique identifier of the external identity provider instance. This is typically an internal ID from the authentication system.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "providerName": { + SchemaProps: spec.SchemaProps{ + Description: "ProviderName is the human-readable name of the identity provider. Examples: \"GitHub\", \"Google\", \"Microsoft\", \"GitLab\"", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "username": { + SchemaProps: spec.SchemaProps{ + Description: "Username is the user's username or identifier within the external identity provider. This is the name the user is known by in the external system (e.g., GitHub username).", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"userUID", "providerID", "providerName", "username"}, + }, + }, + } +}