diff --git a/README.md b/README.md
index 34788c12..37e18472 100644
--- a/README.md
+++ b/README.md
@@ -73,6 +73,47 @@ you will be able to see the Results objects of the analysis after some minutes (
"details": "The error message means that the service in Kubernetes doesn't have any associated endpoints, which should have been labeled with \"control-plane=controller-manager\". \n\nTo solve this issue, you need to add the \"control-plane=controller-manager\" label to the endpoint that matches the service. Once the endpoint is labeled correctly, Kubernetes can associate it with the service, and the error should be resolved.",
```
+## Remote Cache
+
+
+
+S3
+
+1. Install the operator from the [Installation](#installation) section.
+
+2. Create secret:
+```sh
+kubectl create secret generic k8sgpt-sample-cache-secret --from-literal=aws_access_key_id= --from-literal=aws_secret_access_key= -n k8sgpt-
+operator-system
+```
+
+3. Apply the K8sGPT configuration object:
+```
+kubectl apply -f - << EOF
+apiVersion: core.k8sgpt.ai/v1alpha1
+kind: K8sGPT
+metadata:
+ name: k8sgpt-sample
+ namespace: k8sgpt-operator-system
+spec:
+ model: gpt-3.5-turbo
+ backend: openai
+ noCache: false
+ version: v0.3.0
+ enableAI: true
+ secret:
+ name: k8sgpt-sample-secret
+ key: openai-api-key
+ remoteCache:
+ credentials:
+ name: k8sgpt-sample-cache-secret
+ bucketName: foo
+ region: us-west-1
+EOF
+```
+
+
+
## Other AI Backend Examples
diff --git a/api/v1alpha1/k8sgpt_types.go b/api/v1alpha1/k8sgpt_types.go
index 57150b34..b720c357 100644
--- a/api/v1alpha1/k8sgpt_types.go
+++ b/api/v1alpha1/k8sgpt_types.go
@@ -36,6 +36,16 @@ type ExtraOptionsRef struct {
Backstage *Backstage `json:"backstage,omitempty"`
}
+type CredentialsRef struct {
+ Name string `json:"name,omitempty"`
+}
+
+type RemoteCacheRef struct {
+ Credentials *CredentialsRef `json:"credentials,omitempty"`
+ BucketName string `json:"bucketName,omitempty"`
+ Region string `json:"region,omitempty"`
+}
+
type WebhookRef struct {
// +kubebuilder:validation:Enum=slack
Type string `json:"type,omitempty"`
@@ -66,6 +76,7 @@ type K8sGPTSpec struct {
ExtraOptions *ExtraOptionsRef `json:"extraOptions,omitempty"`
Sink *WebhookRef `json:"sink,omitempty"`
AI *AISpec `json:"ai,omitempty"`
+ RemoteCache *RemoteCacheRef `json:"remoteCache,omitempty"`
}
const (
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index 7b507896..c131490b 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -60,6 +60,21 @@ func (in *Backstage) DeepCopy() *Backstage {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CredentialsRef) DeepCopyInto(out *CredentialsRef) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialsRef.
+func (in *CredentialsRef) DeepCopy() *CredentialsRef {
+ if in == nil {
+ return nil
+ }
+ out := new(CredentialsRef)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ExtraOptionsRef) DeepCopyInto(out *ExtraOptionsRef) {
*out = *in
@@ -182,6 +197,11 @@ func (in *K8sGPTSpec) DeepCopyInto(out *K8sGPTSpec) {
*out = new(AISpec)
(*in).DeepCopyInto(*out)
}
+ if in.RemoteCache != nil {
+ in, out := &in.RemoteCache, &out.RemoteCache
+ *out = new(RemoteCacheRef)
+ (*in).DeepCopyInto(*out)
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new K8sGPTSpec.
@@ -209,6 +229,26 @@ func (in *K8sGPTStatus) DeepCopy() *K8sGPTStatus {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *RemoteCacheRef) DeepCopyInto(out *RemoteCacheRef) {
+ *out = *in
+ if in.Credentials != nil {
+ in, out := &in.Credentials, &out.Credentials
+ *out = new(CredentialsRef)
+ **out = **in
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RemoteCacheRef.
+func (in *RemoteCacheRef) DeepCopy() *RemoteCacheRef {
+ if in == nil {
+ return nil
+ }
+ out := new(RemoteCacheRef)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Result) DeepCopyInto(out *Result) {
*out = *in
diff --git a/config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml b/config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml
index d8c67226..d071c423 100644
--- a/config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml
+++ b/config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml
@@ -83,6 +83,18 @@ spec:
type: array
noCache:
type: boolean
+ remoteCache:
+ properties:
+ bucketName:
+ type: string
+ credentials:
+ properties:
+ name:
+ type: string
+ type: object
+ region:
+ type: string
+ type: object
sink:
properties:
type:
diff --git a/config/samples/core_v1alpha1_k8sgpt.yaml b/config/samples/core_v1alpha1_k8sgpt.yaml
index 6d214b28..56687150 100644
--- a/config/samples/core_v1alpha1_k8sgpt.yaml
+++ b/config/samples/core_v1alpha1_k8sgpt.yaml
@@ -12,4 +12,9 @@ spec:
name: k8sgpt-sample-secret
key: openai-api-key
noCache: false
- version: v0.3.0
+ version: v0.3.5
+ remoteCache:
+ credentials:
+ name: k8sgpt-sample-cache-secret
+ bucketName: k8sgpt-debug-test
+ region: eu-west-2
\ No newline at end of file
diff --git a/controllers/k8sgpt_controller.go b/controllers/k8sgpt_controller.go
index 96e562f4..a939a428 100644
--- a/controllers/k8sgpt_controller.go
+++ b/controllers/k8sgpt_controller.go
@@ -170,6 +170,15 @@ func (r *K8sGPTReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
defer k8sgptClient.Close()
+ // Configure the k8sgpt deployment if required
+ if k8sgptConfig.Spec.RemoteCache != nil {
+ err = k8sgptClient.AddConfig(k8sgptConfig)
+ if err != nil {
+ k8sgptReconcileErrorCount.Inc()
+ return r.finishReconcile(err, false)
+ }
+ }
+
response, err := k8sgptClient.ProcessAnalysis(deployment, k8sgptConfig)
if err != nil {
k8sgptReconcileErrorCount.Inc()
diff --git a/go.mod b/go.mod
index 0bbb812a..97411f6c 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.19
require (
buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20230620082254-6f80f9533908.1
- buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.30.0-20230620082254-6f80f9533908.1
+ buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.31.0-20230620082254-6f80f9533908.1
github.com/onsi/ginkgo/v2 v2.12.0
github.com/onsi/gomega v1.27.10
github.com/prometheus/client_golang v1.16.0
@@ -91,7 +91,7 @@ require (
gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect
- google.golang.org/protobuf v1.30.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
diff --git a/go.sum b/go.sum
index 32a08651..348a523f 100644
--- a/go.sum
+++ b/go.sum
@@ -1,8 +1,8 @@
buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20230620082254-6f80f9533908.1 h1:Z0zeGzAumjLyAb/24aiBNyAheT+SDBhlxPfcUy12nPI=
buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20230620082254-6f80f9533908.1/go.mod h1:ydXSuYyk0CN76EA+cjFemhpz87XtuU310GdmkmXUUY8=
buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.28.1-20230620082254-6f80f9533908.4/go.mod h1:i/s4ALHwKvjA1oGNKpoHg0FpEOTbufoOm/NdTE6YQAE=
-buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.30.0-20230620082254-6f80f9533908.1 h1:FNJYUdFjROTTKhIQ+VtJCzuWywQU430leJfnkStRRic=
-buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.30.0-20230620082254-6f80f9533908.1/go.mod h1:karV92RruD5davytOQq20lDyAqBnai8ajNolo98nu94=
+buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.31.0-20230620082254-6f80f9533908.1 h1:FCNAoc1SnZQ0nUgWgV75izPiLGl3Q1X6pxWjL/wB6cI=
+buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.31.0-20230620082254-6f80f9533908.1/go.mod h1:gtnk2yAUexdY5nTuUg0SH5WCCGvpKzr7pd3Xbi7MWjE=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@@ -1259,8 +1259,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
-google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
diff --git a/main.go b/main.go
index fdae8141..fd839fe7 100644
--- a/main.go
+++ b/main.go
@@ -16,6 +16,8 @@ package main
import (
"context"
"flag"
+ "fmt"
+ "math/rand"
"os"
"time"
@@ -64,7 +66,15 @@ func main() {
flag.Parse()
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
-
+ if os.Getenv("LOCAL_MODE") != "" {
+ setupLog.Info("Running in local mode")
+ min := 7000
+ max := 8000
+ metricsAddr = fmt.Sprintf(":%d", rand.Intn(max-min+1)+min)
+ probeAddr = fmt.Sprintf(":%d", rand.Intn(max-min+1)+min)
+ setupLog.Info(fmt.Sprintf("Metrics address: %s", metricsAddr))
+ setupLog.Info(fmt.Sprintf("Probe address: %s", probeAddr))
+ }
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
MetricsBindAddress: metricsAddr,
diff --git a/pkg/client/analysis.go b/pkg/client/analysis.go
new file mode 100644
index 00000000..1fbafae6
--- /dev/null
+++ b/pkg/client/analysis.go
@@ -0,0 +1,50 @@
+package client
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+
+ rpc "buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go/schema/v1/schemav1grpc"
+ schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1"
+ "github.com/k8sgpt-ai/k8sgpt-operator/api/v1alpha1"
+ "github.com/k8sgpt-ai/k8sgpt-operator/pkg/common"
+ v1 "k8s.io/api/apps/v1"
+)
+
+func (c *Client) ProcessAnalysis(deployment v1.Deployment, config *v1alpha1.K8sGPT) (*common.K8sGPTReponse, error) {
+
+ client := rpc.NewServerServiceClient(c.conn)
+ req := &schemav1.AnalyzeRequest{
+ Explain: config.Spec.AI.Enabled,
+ Nocache: config.Spec.NoCache,
+ Backend: config.Spec.AI.Backend,
+ Filters: config.Spec.Filters,
+ Anonymize: config.Spec.AI.Anonymize,
+ Language: config.Spec.AI.Language,
+ }
+
+ res, err := client.Analyze(context.Background(), req)
+ if err != nil {
+ return nil, fmt.Errorf("failed to call Analyze RPC: %v", err)
+ }
+
+ var target []v1alpha1.ResultSpec
+
+ jsonBytes, err := json.Marshal(res.Results)
+ if err != nil {
+ return nil, err
+ }
+
+ err = json.Unmarshal(jsonBytes, &target)
+ if err != nil {
+ return nil, err
+ }
+
+ response := &common.K8sGPTReponse{
+ Status: res.Status,
+ Results: target,
+ Problems: int(res.Problems),
+ }
+ return response, nil
+}
diff --git a/pkg/client/client.go b/pkg/client/client.go
index 6d3cb327..e4ff946e 100644
--- a/pkg/client/client.go
+++ b/pkg/client/client.go
@@ -15,18 +15,13 @@ package client
import (
"context"
- "encoding/json"
"fmt"
"net"
"os"
"time"
- rpc "buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go/schema/v1/schemav1grpc"
- schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1"
"github.com/k8sgpt-ai/k8sgpt-operator/api/v1alpha1"
- "github.com/k8sgpt-ai/k8sgpt-operator/pkg/common"
"google.golang.org/grpc"
- v1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
@@ -79,40 +74,3 @@ func GenerateAddress(ctx context.Context, cli client.Client, k8sgptConfig *v1alp
return address, nil
}
-
-func (c *Client) ProcessAnalysis(deployment v1.Deployment, config *v1alpha1.K8sGPT) (*common.K8sGPTReponse, error) {
-
- client := rpc.NewServerServiceClient(c.conn)
- req := &schemav1.AnalyzeRequest{
- Explain: config.Spec.AI.Enabled,
- Nocache: config.Spec.NoCache,
- Backend: config.Spec.AI.Backend,
- Filters: config.Spec.Filters,
- Anonymize: config.Spec.AI.Anonymize,
- Language: config.Spec.AI.Language,
- }
-
- res, err := client.Analyze(context.Background(), req)
- if err != nil {
- return nil, fmt.Errorf("failed to call Analyze RPC: %v", err)
- }
-
- var target []v1alpha1.ResultSpec
-
- jsonBytes, err := json.Marshal(res.Results)
- if err != nil {
- return nil, err
- }
-
- err = json.Unmarshal(jsonBytes, &target)
- if err != nil {
- return nil, err
- }
-
- response := &common.K8sGPTReponse{
- Status: res.Status,
- Results: target,
- Problems: int(res.Problems),
- }
- return response, nil
-}
diff --git a/pkg/client/config.go b/pkg/client/config.go
new file mode 100644
index 00000000..7d49d006
--- /dev/null
+++ b/pkg/client/config.go
@@ -0,0 +1,46 @@
+package client
+
+import (
+ "context"
+ "fmt"
+
+ rpc "buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go/schema/v1/schemav1grpc"
+ schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1"
+ "github.com/k8sgpt-ai/k8sgpt-operator/api/v1alpha1"
+)
+
+func (c *Client) AddConfig(config *v1alpha1.K8sGPT) error {
+ client := rpc.NewServerServiceClient(c.conn)
+
+ req := &schemav1.AddConfigRequest{
+ Cache: &schemav1.Cache{
+ BucketName: config.Spec.RemoteCache.BucketName,
+ Region: config.Spec.RemoteCache.Region,
+ },
+ }
+
+ _, err := client.AddConfig(context.Background(), req)
+ if err != nil {
+ return fmt.Errorf("failed to call AddConfig RPC: %v", err)
+ }
+
+ return nil
+}
+
+func (c *Client) RemoveConfig(config *v1alpha1.K8sGPT) error {
+ client := rpc.NewServerServiceClient(c.conn)
+
+ req := &schemav1.RemoveConfigRequest{
+ Cache: &schemav1.Cache{
+ BucketName: config.Spec.RemoteCache.BucketName,
+ Region: config.Spec.RemoteCache.Region,
+ },
+ }
+
+ _, err := client.RemoveConfig(context.Background(), req)
+ if err != nil {
+ return fmt.Errorf("failed to call RemoveConfig RPC: %v", err)
+ }
+
+ return nil
+}
diff --git a/pkg/resources/k8sgpt.go b/pkg/resources/k8sgpt.go
index 7425bbe5..99c76157 100644
--- a/pkg/resources/k8sgpt.go
+++ b/pkg/resources/k8sgpt.go
@@ -22,6 +22,7 @@ import (
"github.com/k8sgpt-ai/k8sgpt-operator/pkg/utils"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
+ v1 "k8s.io/api/core/v1"
r1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
@@ -272,6 +273,29 @@ func GetDeployment(config v1alpha1.K8sGPT) (*appsv1.Deployment, error) {
deployment.Spec.Template.Spec.Containers[0].Env, password,
)
}
+ if config.Spec.RemoteCache != nil {
+
+ // check to see if key/value exists
+ addRemoteCacheEnvVar := func(name, key string) {
+ envVar := v1.EnvVar{
+ Name: name,
+ ValueFrom: &v1.EnvVarSource{
+ SecretKeyRef: &v1.SecretKeySelector{
+ LocalObjectReference: v1.LocalObjectReference{
+ Name: config.Spec.RemoteCache.Credentials.Name,
+ },
+ Key: key,
+ },
+ },
+ }
+ deployment.Spec.Template.Spec.Containers[0].Env = append(
+ deployment.Spec.Template.Spec.Containers[0].Env, envVar,
+ )
+ }
+ addRemoteCacheEnvVar("AWS_ACCESS_KEY_ID", "aws_access_key_id")
+ addRemoteCacheEnvVar("AWS_SECRET_ACCESS_KEY", "aws_secret_access_key")
+
+ }
if config.Spec.AI.BaseUrl != "" {
baseUrl := corev1.EnvVar{
Name: "K8SGPT_BASEURL",