diff --git a/charts/controller/crds/deployments.plural.sh_deploymentsettings.yaml b/charts/controller/crds/deployments.plural.sh_deploymentsettings.yaml
index 0b3d550f6e..9890917574 100644
--- a/charts/controller/crds/deployments.plural.sh_deploymentsettings.yaml
+++ b/charts/controller/crds/deployments.plural.sh_deploymentsettings.yaml
@@ -353,6 +353,48 @@ spec:
x-kubernetes-map-type: atomic
type: object
type: object
+ vectorStore:
+ properties:
+ elastic:
+ properties:
+ host:
+ type: string
+ index:
+ type: string
+ passwordSecretRef:
+ description: SecretKeySelector selects a key of a Secret.
+ properties:
+ key:
+ description: The key of the secret to select from. Must
+ be a valid secret key.
+ type: string
+ name:
+ description: |-
+ Name of the referent.
+ More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+ type: string
+ optional:
+ description: Specify whether the Secret or its key
+ must be defined
+ type: boolean
+ required:
+ - key
+ type: object
+ x-kubernetes-map-type: atomic
+ user:
+ type: string
+ required:
+ - host
+ - index
+ type: object
+ enabled:
+ default: false
+ type: boolean
+ vectorStore:
+ enum:
+ - ELASTIC
+ type: string
+ type: object
vertex:
description: Vertex holds configuration for using GCP VertexAI
to generate LLM insights
diff --git a/go/controller/api/v1alpha1/deploymentsettings_types.go b/go/controller/api/v1alpha1/deploymentsettings_types.go
index adeb418f28..e0628a6234 100644
--- a/go/controller/api/v1alpha1/deploymentsettings_types.go
+++ b/go/controller/api/v1alpha1/deploymentsettings_types.go
@@ -332,6 +332,9 @@ type AISettings struct {
//
// +kubebuilder:validation:Optional
Vertex *VertexSettings `json:"vertex,omitempty"`
+
+ // +kubebuilder:validation:Optional
+ VectorStore *VectorStore `json:"vectorStore,omitempty"`
}
type Tools struct {
@@ -369,11 +372,17 @@ func (in *LoggingSettings) Attributes(ctx context.Context, c client.Client, name
}
func (in *AISettings) Attributes(ctx context.Context, c client.Client, namespace string) (*console.AiSettingsAttributes, error) {
+ vectorStoreAttributes, err := in.VectorStore.Attributes(ctx, c, namespace)
+ if err != nil {
+ return nil, err
+ }
+
attr := &console.AiSettingsAttributes{
Enabled: in.Enabled,
Provider: in.Provider,
ToolProvider: in.ToolProvider,
EmbeddingProvider: in.EmbeddingProvider,
+ VectorStore: vectorStoreAttributes,
}
if in.Tools != nil && in.Tools.CreatePr != nil {
@@ -691,3 +700,79 @@ func (in *VertexSettings) ServiceAccountJSON(ctx context.Context, c client.Clien
res, err := utils.GetSecretKey(ctx, c, in.ServiceAccountJsonSecretRef, namespace)
return lo.ToPtr(res), err
}
+
+type VectorStore struct {
+ // +kubebuilder:default=false
+ // +kubebuilder:validation:Optional
+ Enabled *bool `json:"enabled,omitempty"`
+
+ // +kubebuilder:validation:Enum=ELASTIC
+ // +kubebuilder:validation:Optional
+ VectorStore *console.VectorStore `json:"vectorStore,omitempty"`
+
+ // +kubebuilder:validation:Optional
+ Elastic *ElasticsearchConnectionSettings `json:"elastic,omitempty"`
+}
+
+func (in *VectorStore) Attributes(ctx context.Context, c client.Client, namespace string) (*console.VectorStoreAttributes, error) {
+ if in == nil {
+ return nil, nil
+ }
+
+ if lo.FromPtr(in.Enabled) && in.VectorStore == nil {
+ return nil, fmt.Errorf("vector store type has to be set if it is enabled")
+ }
+
+ attr := &console.VectorStoreAttributes{
+ Enabled: in.Enabled,
+ Store: in.VectorStore,
+ }
+
+ switch *in.VectorStore {
+ case console.VectorStoreElastic:
+ if in.Elastic == nil {
+ return nil, fmt.Errorf("must provide elastic configuration to set the provider to ELASTIC")
+ }
+
+ password, err := in.Elastic.Password(ctx, c, namespace)
+ if err != nil {
+ return nil, err
+ }
+
+ attr.Elastic = &console.ElasticsearchConnectionAttributes{
+ Host: in.Elastic.Host,
+ Index: in.Elastic.Index,
+ User: in.Elastic.User,
+ Password: password,
+ }
+ }
+
+ return attr, nil
+}
+
+type ElasticsearchConnectionSettings struct {
+ // +kubebuilder:validation:Required
+ Host string `json:"host"`
+
+ // +kubebuilder:validation:Required
+ Index string `json:"index"`
+
+ // +kubebuilder:validation:Optional
+ User *string `json:"user,omitempty"`
+
+ // +kubebuilder:validation:Optional
+ PasswordSecretRef *corev1.SecretKeySelector `json:"passwordSecretRef,omitempty"`
+}
+
+func (in *ElasticsearchConnectionSettings) Password(ctx context.Context, c client.Client, namespace string) (*string, error) {
+ if in == nil {
+ return nil, fmt.Errorf("configured elastic settings cannot be nil")
+ }
+
+ if in.PasswordSecretRef == nil {
+ return nil, nil
+ }
+
+ res, err := utils.GetSecretKey(ctx, c, in.PasswordSecretRef, namespace)
+ return lo.ToPtr(res), err
+}
diff --git a/go/controller/api/v1alpha1/zz_generated.deepcopy.go b/go/controller/api/v1alpha1/zz_generated.deepcopy.go
index 8efaafbef0..59097718ec 100644
--- a/go/controller/api/v1alpha1/zz_generated.deepcopy.go
+++ b/go/controller/api/v1alpha1/zz_generated.deepcopy.go
@@ -122,6 +122,11 @@ func (in *AISettings) DeepCopyInto(out *AISettings) {
*out = new(VertexSettings)
(*in).DeepCopyInto(*out)
}
+ if in.VectorStore != nil {
+ in, out := &in.VectorStore, &out.VectorStore
+ *out = new(VectorStore)
+ (*in).DeepCopyInto(*out)
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AISettings.
@@ -1608,6 +1613,31 @@ func (in *ElasticsearchConnection) DeepCopy() *ElasticsearchConnection {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ElasticsearchConnectionSettings) DeepCopyInto(out *ElasticsearchConnectionSettings) {
+ *out = *in
+ if in.User != nil {
+ in, out := &in.User, &out.User
+ *out = new(string)
+ **out = **in
+ }
+ if in.PasswordSecretRef != nil {
+ in, out := &in.PasswordSecretRef, &out.PasswordSecretRef
+ *out = new(v1.SecretKeySelector)
+ (*in).DeepCopyInto(*out)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ElasticsearchConnectionSettings.
+func (in *ElasticsearchConnectionSettings) DeepCopy() *ElasticsearchConnectionSettings {
+ if in == nil {
+ return nil
+ }
+ out := new(ElasticsearchConnectionSettings)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Env) DeepCopyInto(out *Env) {
*out = *in
@@ -5717,6 +5747,36 @@ func (in *Tools) DeepCopy() *Tools {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VectorStore) DeepCopyInto(out *VectorStore) {
+ *out = *in
+ if in.Enabled != nil {
+ in, out := &in.Enabled, &out.Enabled
+ *out = new(bool)
+ **out = **in
+ }
+ if in.VectorStore != nil {
+ in, out := &in.VectorStore, &out.VectorStore
+ *out = new(client.VectorStore)
+ **out = **in
+ }
+ if in.Elastic != nil {
+ in, out := &in.Elastic, &out.Elastic
+ *out = new(ElasticsearchConnectionSettings)
+ (*in).DeepCopyInto(*out)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VectorStore.
+func (in *VectorStore) DeepCopy() *VectorStore {
+ if in == nil {
+ return nil
+ }
+ out := new(VectorStore)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VertexSettings) DeepCopyInto(out *VertexSettings) {
*out = *in
diff --git a/go/controller/config/crd/bases/deployments.plural.sh_deploymentsettings.yaml b/go/controller/config/crd/bases/deployments.plural.sh_deploymentsettings.yaml
index 0b3d550f6e..9890917574 100644
--- a/go/controller/config/crd/bases/deployments.plural.sh_deploymentsettings.yaml
+++ b/go/controller/config/crd/bases/deployments.plural.sh_deploymentsettings.yaml
@@ -353,6 +353,48 @@ spec:
x-kubernetes-map-type: atomic
type: object
type: object
+ vectorStore:
+ properties:
+ elastic:
+ properties:
+ host:
+ type: string
+ index:
+ type: string
+ passwordSecretRef:
+ description: SecretKeySelector selects a key of a Secret.
+ properties:
+ key:
+ description: The key of the secret to select from. Must
+ be a valid secret key.
+ type: string
+ name:
+ description: |-
+ Name of the referent.
+ More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+ type: string
+ optional:
+ description: Specify whether the Secret or its key
+ must be defined
+ type: boolean
+ required:
+ - key
+ type: object
+ x-kubernetes-map-type: atomic
+ user:
+ type: string
+ required:
+ - host
+ - index
+ type: object
+ enabled:
+ default: false
+ type: boolean
+ vectorStore:
+ enum:
+ - ELASTIC
+ type: string
+ type: object
vertex:
description: Vertex holds configuration for using GCP VertexAI
to generate LLM insights
diff --git a/go/controller/docs/api.md b/go/controller/docs/api.md
index 67c8b19184..51e34c7bda 100644
--- a/go/controller/docs/api.md
+++ b/go/controller/docs/api.md
@@ -56,6 +56,7 @@ _Appears in:_
| --- | --- | --- | --- |
| `model` _string_ | Model is the LLM model name to use. | | Optional: {}
|
| `toolModel` _string_ | Model to use for tool calling, which is less frequent and often requires more advanced reasoning | | Optional: {}
|
+| `embeddingModel` _string_ | Model to use for generating embeddings | | Optional: {}
|
| `baseUrl` _string_ | A custom base url to use, for reimplementations of the same API scheme (for instance Together.ai uses the OpenAI API spec) | | Optional: {}
|
| `tokenSecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretkeyselector-v1-core)_ | TokenSecretRef is a reference to the local secret holding the token to access
the configured AI provider. | | Required: {}
|
@@ -77,12 +78,14 @@ _Appears in:_
| `tools` _[Tools](#tools)_ | | | Optional: {}
|
| `provider` _[AiProvider](#aiprovider)_ | Provider defines which of the supported LLM providers should be used. | OPENAI | Enum: [OPENAI ANTHROPIC OLLAMA AZURE BEDROCK VERTEX]
Optional: {}
|
| `toolProvider` _[AiProvider](#aiprovider)_ | Provider to use for tool calling, in case you want to use a different LLM more optimized to those tasks | | Enum: [OPENAI ANTHROPIC OLLAMA AZURE BEDROCK VERTEX]
Optional: {}
|
+| `embeddingProvider` _[AiProvider](#aiprovider)_ | Provider to use for generating embeddings. Oftentimes foundational model providers do not have embeddings models, and it's better to simply use OpenAI. | | Enum: [OPENAI ANTHROPIC OLLAMA AZURE BEDROCK VERTEX]
Optional: {}
|
| `openAI` _[AIProviderSettings](#aiprovidersettings)_ | OpenAI holds the OpenAI provider configuration. | | Optional: {}
|
| `anthropic` _[AIProviderSettings](#aiprovidersettings)_ | Anthropic holds the Anthropic provider configuration. | | Optional: {}
|
| `ollama` _[OllamaSettings](#ollamasettings)_ | Ollama holds configuration for a self-hosted Ollama deployment, more details available at https://github.com/ollama/ollama | | Optional: {}
|
| `azure` _[AzureOpenAISettings](#azureopenaisettings)_ | Azure holds configuration for using AzureOpenAI to generate LLM insights | | Optional: {}
|
| `bedrock` _[BedrockSettings](#bedrocksettings)_ | Bedrock holds configuration for using AWS Bedrock to generate LLM insights | | Optional: {}
|
| `vertex` _[VertexSettings](#vertexsettings)_ | Vertex holds configuration for using GCP VertexAI to generate LLM insights | | Optional: {}
|
+| `vectorStore` _[VectorStore](#vectorstore)_ | | | Optional: {}
|
@@ -104,6 +107,7 @@ _Appears in:_
| `apiVersion` _string_ | The azure openai Data plane - inference api version to use, defaults to 2024-10-01-preview or the latest available | | Optional: {}
|
| `model` _string_ | The OpenAi Model you wish to use. If not specified, Plural will provide a default | | Optional: {}
|
| `toolModel` _string_ | Model to use for tool calling, which is less frequent and often requires more advanced reasoning | | Optional: {}
|
+| `embeddingModel` _string_ | Model to use for generating embeddings | | Optional: {}
|
| `tokenSecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretkeyselector-v1-core)_ | TokenSecretRef is a reference to the local secret holding the token to access
the configured AI provider. | | Required: {}
|
@@ -821,6 +825,8 @@ _Appears in:_
| `ai` _[AISettings](#aisettings)_ | AI settings specifies a configuration for LLM provider clients | | Optional: {}
|
| `logging` _[LoggingSettings](#loggingsettings)_ | Settings for connections to log aggregation datastores | | Optional: {}
|
| `cost` _[CostSettings](#costsettings)_ | Settings for managing Plural's cost management features | | Optional: {}
|
+| `deploymentRepositoryRef` _[NamespacedName](#namespacedname)_ | pointer to the deployment GIT repository to use | | Optional: {}
|
+| `scaffoldsRepositoryRef` _[NamespacedName](#namespacedname)_ | pointer to the Scaffolds GIT repository to use | | Optional: {}
|
#### ElasticsearchConnection
@@ -842,6 +848,25 @@ _Appears in:_
| `passwordSecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretkeyselector-v1-core)_ | PasswordSecretRef selects a key of a password Secret | | Optional: {}
|
+#### ElasticsearchConnectionSettings
+
+
+
+
+
+
+
+_Appears in:_
+- [VectorStore](#vectorstore)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `host` _string_ | | | Required: {}
|
+| `index` _string_ | | | Required: {}
|
+| `user` _string_ | | | Optional: {}
|
+| `passwordSecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretkeyselector-v1-core)_ | | | Optional: {}
|
+
+
@@ -1390,6 +1415,7 @@ with the addition of kubebuilder/json annotations for better schema support.
_Appears in:_
+- [DeploymentSettingsSpec](#deploymentsettingsspec)
- [ServiceHelm](#servicehelm)
| Field | Description | Default | Validation |
@@ -2918,6 +2944,24 @@ _Appears in:_
| `createPr` _[CreatePr](#createpr)_ | | | |
+#### VectorStore
+
+
+
+
+
+
+
+_Appears in:_
+- [AISettings](#aisettings)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `enabled` _boolean_ | | false | Optional: {}
|
+| `vectorStore` _[VectorStore](#vectorstore)_ | | | Enum: [ELASTIC]
Optional: {}
|
+| `elastic` _[ElasticsearchConnectionSettings](#elasticsearchconnectionsettings)_ | | | Optional: {}
|
+
+
#### VertexSettings
diff --git a/go/controller/internal/controller/common.go b/go/controller/internal/controller/common.go
index 28b0e38e1f..eab0ee8cd6 100644
--- a/go/controller/internal/controller/common.go
+++ b/go/controller/internal/controller/common.go
@@ -9,6 +9,7 @@ import (
"github.com/pluralsh/polly/algorithms"
"github.com/samber/lo"
corev1 "k8s.io/api/core/v1"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
@@ -40,8 +41,15 @@ var (
// If the result is set, then any potential error will be saved in a condition
// and ignored in the return to avoid rate limiting.
//
+// If not found error is detected, then the result is automatically changed to
+// wait for resources.
+//
// It is important that at least one from a result or an error have to be non-nil.
func handleRequeue(result *ctrl.Result, err error, setCondition func(condition metav1.Condition)) (ctrl.Result, error) {
+ if err != nil && apierrors.IsNotFound(err) {
+ result = &waitForResources
+ }
+
utils.MarkCondition(setCondition, v1alpha1.SynchronizedConditionType, metav1.ConditionFalse,
v1alpha1.SynchronizedConditionReasonError, defaultErrMessage(err, ""))
return lo.FromPtr(result), lo.Ternary(result != nil, nil, err)
diff --git a/go/controller/internal/controller/deploymentsettings_controller.go b/go/controller/internal/controller/deploymentsettings_controller.go
index adc2ecb61c..28641c7b98 100644
--- a/go/controller/internal/controller/deploymentsettings_controller.go
+++ b/go/controller/internal/controller/deploymentsettings_controller.go
@@ -18,13 +18,13 @@ package controller
import (
"context"
- goerrors "errors"
"fmt"
"github.com/samber/lo"
"k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -37,7 +37,6 @@ import (
"github.com/pluralsh/console/go/controller/internal/cache"
consoleclient "github.com/pluralsh/console/go/controller/internal/client"
"github.com/pluralsh/console/go/controller/internal/credentials"
- operrors "github.com/pluralsh/console/go/controller/internal/errors"
"github.com/pluralsh/console/go/controller/internal/utils"
)
@@ -116,12 +115,9 @@ func (r *DeploymentSettingsReconciler) Reconcile(ctx context.Context, req ctrl.R
logger.Info("upsert deployment settings", "name", settings.Name)
attr, err := r.genDeploymentSettingsAttr(ctx, settings)
if err != nil {
- utils.MarkCondition(settings.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, err.Error())
- if goerrors.Is(err, operrors.ErrRetriable) || goerrors.Is(err, operrors.ErrReferenceNotFound) {
- return requeue, nil
- }
- return ctrl.Result{}, err
+ return handleRequeue(nil, err, settings.SetCondition)
}
+
_, err = r.ConsoleClient.UpdateDeploymentSettings(ctx, *attr)
if err != nil {
utils.MarkCondition(settings.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, err.Error())
@@ -177,10 +173,6 @@ func (r *DeploymentSettingsReconciler) genDeploymentSettingsAttr(ctx context.Con
}
if settings.Spec.AI != nil {
ai, err := settings.Spec.AI.Attributes(ctx, r.Client, settings.Namespace)
- if errors.IsNotFound(err) {
- return nil, operrors.ErrReferenceNotFound
- }
-
if err != nil {
return nil, err
}
@@ -188,10 +180,6 @@ func (r *DeploymentSettingsReconciler) genDeploymentSettingsAttr(ctx context.Con
}
if settings.Spec.Logging != nil {
logging, err := settings.Spec.Logging.Attributes(ctx, r.Client, settings.Namespace)
- if errors.IsNotFound(err) {
- return nil, operrors.ErrReferenceNotFound
- }
-
if err != nil {
return nil, err
}
@@ -210,9 +198,6 @@ func (r *DeploymentSettingsReconciler) genDeploymentSettingsAttr(ctx context.Con
if settings.Spec.Stacks.ConnectionRef != nil {
connection := &v1alpha1.ScmConnection{}
if err := r.Get(ctx, types.NamespacedName{Name: settings.Spec.Stacks.ConnectionRef.Name, Namespace: settings.Spec.Stacks.ConnectionRef.Namespace}, connection); err != nil {
- if errors.IsNotFound(err) {
- return nil, operrors.ErrReferenceNotFound
- }
return nil, err
}
connectionID = connection.Status.ID
@@ -235,9 +220,6 @@ func (r *DeploymentSettingsReconciler) genDeploymentSettingsAttr(ctx context.Con
if settings.Spec.DeploymentRepositoryRef != nil {
id, err := getGitRepoID(ctx, r.Client, *settings.Spec.DeploymentRepositoryRef)
if err != nil {
- if errors.IsNotFound(err) {
- return nil, operrors.ErrReferenceNotFound
- }
return nil, err
}
attr.DeployerRepositoryID = id
@@ -246,9 +228,6 @@ func (r *DeploymentSettingsReconciler) genDeploymentSettingsAttr(ctx context.Con
if settings.Spec.ScaffoldsRepositoryRef != nil {
id, err := getGitRepoID(ctx, r.Client, *settings.Spec.ScaffoldsRepositoryRef)
if err != nil {
- if errors.IsNotFound(err) {
- return nil, operrors.ErrReferenceNotFound
- }
return nil, err
}
attr.ArtifactRepositoryID = id
@@ -289,7 +268,7 @@ func (r *DeploymentSettingsReconciler) ensure(settings *v1alpha1.DeploymentSetti
settings.Spec.Bindings.Git = bindings
if req || req2 || req3 || req4 {
- return operrors.ErrRetriable
+ return errors.NewNotFound(schema.GroupResource{}, "bindings")
}
return nil
diff --git a/plural/helm/console/crds/deployments.plural.sh_deploymentsettings.yaml b/plural/helm/console/crds/deployments.plural.sh_deploymentsettings.yaml
index 0b3d550f6e..9890917574 100644
--- a/plural/helm/console/crds/deployments.plural.sh_deploymentsettings.yaml
+++ b/plural/helm/console/crds/deployments.plural.sh_deploymentsettings.yaml
@@ -353,6 +353,48 @@ spec:
x-kubernetes-map-type: atomic
type: object
type: object
+ vectorStore:
+ properties:
+ elastic:
+ properties:
+ host:
+ type: string
+ index:
+ type: string
+ passwordSecretRef:
+ description: SecretKeySelector selects a key of a Secret.
+ properties:
+ key:
+ description: The key of the secret to select from. Must
+ be a valid secret key.
+ type: string
+ name:
+ description: |-
+ Name of the referent.
+ More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+ type: string
+ optional:
+ description: Specify whether the Secret or its key
+ must be defined
+ type: boolean
+ required:
+ - key
+ type: object
+ x-kubernetes-map-type: atomic
+ user:
+ type: string
+ required:
+ - host
+ - index
+ type: object
+ enabled:
+ default: false
+ type: boolean
+ vectorStore:
+ enum:
+ - ELASTIC
+ type: string
+ type: object
vertex:
description: Vertex holds configuration for using GCP VertexAI
to generate LLM insights