From 585628cb1198832b09889842bcf701bdad512609 Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Tue, 10 Feb 2026 13:55:28 +0100 Subject: [PATCH 01/17] Create valkey resources using pgrator resources Some features are therefore removed or altered, but most tests are fairly unchanged. Requires an update to liberator that's not merged yet: https://github.com/nais/liberator/pull/306 --- integration_tests/valkey_crud.lua | 255 ++------------- internal/cmd/api/http.go | 2 +- internal/kubernetes/fake/fake.go | 2 + internal/kubernetes/scheme.go | 4 + internal/kubernetes/utils.go | 24 ++ internal/kubernetes/watchers/watchers.go | 3 + internal/persistence/valkey/client.go | 5 +- internal/persistence/valkey/dataloader.go | 50 ++- internal/persistence/valkey/models.go | 63 +++- internal/persistence/valkey/queries.go | 380 +++++++++++----------- 10 files changed, 357 insertions(+), 431 deletions(-) create mode 100644 internal/kubernetes/utils.go diff --git a/integration_tests/valkey_crud.lua b/integration_tests/valkey_crud.lua index 4f9aabc80..97d1fc5c5 100644 --- a/integration_tests/valkey_crud.lua +++ b/integration_tests/valkey_crud.lua @@ -133,7 +133,7 @@ Test.gql("Create valkey as team member with existing name", function(t) errors = { { locations = NotNull(), - message = "Resource already exists.", + message = "Valkey with the name \"not-managed\" already exists, but are not yet managed through Console.", path = { "createValkey", }, @@ -218,72 +218,25 @@ Test.gql("Create valkey with databases above maximum", function(t) end) Test.k8s("Validate Valkey resource", function(t) - local resourceName = string.format("valkey-%s-foobar", mainTeam:slug()) - - t.check("aiven.io/v1alpha1", "valkeys", "dev", mainTeam:slug(), resourceName, { - apiVersion = "aiven.io/v1alpha1", + t.check("nais.io/v1", "valkeys", "dev", mainTeam:slug(), "foobar", { + apiVersion = "nais.io/v1", kind = "Valkey", metadata = { - name = resourceName, - namespace = mainTeam:slug(), annotations = { ["console.nais.io/last-modified-at"] = NotNull(), - ["console.nais.io/last-modified-by"] = user:email(), + ["console.nais.io/last-modified-by"] = "user@usersen.com", }, labels = { ["app.kubernetes.io/managed-by"] = "console", ["nais.io/managed-by"] = "console", }, + name = "foobar", + namespace = "someteamname", }, - spec = { - project = "aiven-dev", - projectVpcId = "aiven-vpc", - plan = "startup-14", - cloudName = "google-europe-north1", - terminationProtection = true, - userConfig = { - valkey_number_of_databases = 32, - }, - tags = { - environment = "dev", - team = mainTeam:slug(), - tenant = "some-tenant", - }, - }, - }) -end) - -Test.k8s("Validate serviceintegration", function(t) - local resourceName = string.format("valkey-%s-foobar", mainTeam:slug()) - - t.check("aiven.io/v1alpha1", "serviceintegrations", "dev", mainTeam:slug(), resourceName, { - apiVersion = "aiven.io/v1alpha1", - kind = "ServiceIntegration", - metadata = { - name = resourceName, - namespace = mainTeam:slug(), - annotations = { - ["console.nais.io/last-modified-at"] = NotNull(), - ["console.nais.io/last-modified-by"] = user:email(), - }, - labels = { - ["app.kubernetes.io/managed-by"] = "console", - ["nais.io/managed-by"] = "console", - }, - ownerReferences = { - { - apiVersion = "aiven.io/v1alpha1", - kind = "Valkey", - name = resourceName, - uid = NotNull(), - }, - }, - }, - spec = { - project = "aiven-dev", - destinationEndpointId = "endpoint-id", - integrationType = "prometheus", - sourceServiceName = resourceName, + spec = { + databases = 32, + memory = "14GB", + tier = "SingleNode", }, }) end) @@ -393,39 +346,26 @@ Test.gql("Update Valkey as team-member", function(t) end) Test.k8s("Validate Valkey resource after update", function(t) - local resourceName = string.format("valkey-%s-foobar", mainTeam:slug()) - - t.check("aiven.io/v1alpha1", "valkeys", "dev", mainTeam:slug(), resourceName, { - apiVersion = "aiven.io/v1alpha1", + t.check("nais.io/v1", "valkeys", "dev", mainTeam:slug(), "foobar", { + apiVersion = "nais.io/v1", kind = "Valkey", metadata = { - name = resourceName, - namespace = mainTeam:slug(), annotations = { ["console.nais.io/last-modified-at"] = NotNull(), - ["console.nais.io/last-modified-by"] = user:email(), + ["console.nais.io/last-modified-by"] = "user@usersen.com", }, labels = { ["app.kubernetes.io/managed-by"] = "console", ["nais.io/managed-by"] = "console", }, + name = "foobar", + namespace = "someteamname", }, spec = { - project = "aiven-dev", - projectVpcId = "aiven-vpc", - plan = "business-4", - cloudName = "google-europe-north1", - terminationProtection = true, - userConfig = { - valkey_maxmemory_policy = "allkeys-random", - valkey_notify_keyspace_events = "Exd", - valkey_number_of_databases = 64, - }, - tags = { - environment = "dev", - team = mainTeam:slug(), - tenant = "some-tenant", - }, + maxMemoryPolicy = "allkeys-random", + memory = "4GB", + notifyKeyspaceEvents = "Exd", + tier = "HighAvailability", }, }) end) @@ -461,70 +401,26 @@ Test.gql("Create valkey with tier and memory equivalent to hobbyist plan", funct } end) +-- TODO: Do we need this? Test.k8s("Validate hobbyist Valkey resource", function(t) - local resourceName = string.format("valkey-%s-foobar-hobbyist", mainTeam:slug()) - - t.check("aiven.io/v1alpha1", "valkeys", "dev", mainTeam:slug(), resourceName, { - apiVersion = "aiven.io/v1alpha1", + t.check("nais.io/v1", "valkeys", "dev", mainTeam:slug(), "foobar-hobbyist", { + apiVersion = "nais.io/v1", kind = "Valkey", metadata = { - name = resourceName, - namespace = mainTeam:slug(), - annotations = { - ["console.nais.io/last-modified-at"] = NotNull(), - ["console.nais.io/last-modified-by"] = user:email(), - }, - labels = { - ["app.kubernetes.io/managed-by"] = "console", - ["nais.io/managed-by"] = "console", - }, - }, - spec = { - project = "aiven-dev", - projectVpcId = "aiven-vpc", - plan = "hobbyist", - cloudName = "google-europe-north1", - terminationProtection = true, - tags = { - environment = "dev", - team = mainTeam:slug(), - tenant = "some-tenant", - }, - }, - }) -end) - -Test.k8s("Validate hobbyist serviceintegration", function(t) - local resourceName = string.format("valkey-%s-foobar-hobbyist", mainTeam:slug()) - - t.check("aiven.io/v1alpha1", "serviceintegrations", "dev", mainTeam:slug(), resourceName, { - apiVersion = "aiven.io/v1alpha1", - kind = "ServiceIntegration", - metadata = { - name = resourceName, - namespace = mainTeam:slug(), annotations = { ["console.nais.io/last-modified-at"] = NotNull(), - ["console.nais.io/last-modified-by"] = user:email(), + ["console.nais.io/last-modified-by"] = "user@usersen.com", }, labels = { ["app.kubernetes.io/managed-by"] = "console", ["nais.io/managed-by"] = "console", }, - ownerReferences = { - { - apiVersion = "aiven.io/v1alpha1", - kind = "Valkey", - name = resourceName, - uid = NotNull(), - }, - }, + name = "foobar-hobbyist", + namespace = "someteamname", }, spec = { - project = "aiven-dev", - destinationEndpointId = "endpoint-id", - integrationType = "prometheus", - sourceServiceName = resourceName, + memory = "1GB", + tier = "SingleNode", }, }) end) @@ -593,42 +489,6 @@ Test.gql("List valkeys for team", function(t) } end) -Test.gql("Update non-console managed Valkey as team-member", function(t) - t.addHeader("x-user-email", user:email()) - t.query [[ - mutation UpdateValkey { - updateValkey( - input: { - name: "not-managed" - environmentName: "dev" - teamSlug: "someteamname" - tier: HIGH_AVAILABILITY - memory: GB_4 - maxMemoryPolicy: ALLKEYS_RANDOM - notifyKeyspaceEvents: "Exd" - } - ) { - valkey { - name - } - } - } - ]] - - t.check { - errors = { - { - locations = NotNull(), - message = "Valkey someteamname/not-managed is not managed by Console", - path = { - "updateValkey", - }, - }, - }, - data = Null, - } -end) - Test.gql("Update Valkey with tier and memory equivalent to hobbyist plan", function(t) t.addHeader("x-user-email", user:email()) t.query [[ @@ -663,39 +523,26 @@ Test.gql("Update Valkey with tier and memory equivalent to hobbyist plan", funct end) Test.k8s("Validate hobbyist Valkey resource after update", function(t) - local resourceName = string.format("valkey-%s-foobar", mainTeam:slug()) - - t.check("aiven.io/v1alpha1", "valkeys", "dev", mainTeam:slug(), resourceName, { - apiVersion = "aiven.io/v1alpha1", + t.check("nais.io/v1", "valkeys", "dev", mainTeam:slug(), "foobar", { + apiVersion = "nais.io/v1", kind = "Valkey", metadata = { - name = resourceName, - namespace = mainTeam:slug(), annotations = { ["console.nais.io/last-modified-at"] = NotNull(), - ["console.nais.io/last-modified-by"] = user:email(), + ["console.nais.io/last-modified-by"] = "user@usersen.com", }, labels = { ["app.kubernetes.io/managed-by"] = "console", ["nais.io/managed-by"] = "console", }, + name = "foobar", + namespace = "someteamname", }, spec = { - project = "aiven-dev", - projectVpcId = "aiven-vpc", - plan = "hobbyist", - cloudName = "google-europe-north1", - terminationProtection = true, - userConfig = { - valkey_maxmemory_policy = "allkeys-random", - valkey_notify_keyspace_events = "Exd", - valkey_number_of_databases = 64, - }, - tags = { - environment = "dev", - team = mainTeam:slug(), - tenant = "some-tenant", - }, + maxMemoryPolicy = "allkeys-random", + memory = "1GB", + notifyKeyspaceEvents = "Exd", + tier = "SingleNode", }, }) end) @@ -830,36 +677,6 @@ Test.gql("Verify activity log after deleting valkey", function(t) } end) -Test.gql("Delete non-managed valkey as team-member", function(t) - t.addHeader("x-user-email", user:email()) - t.query [[ - mutation DeleteValkey { - deleteValkey( - input: { - name: "not-managed" - environmentName: "dev" - teamSlug: "someteamname" - } - ) { - valkeyDeleted - } - } - ]] - - t.check { - errors = { - { - locations = NotNull(), - message = "Valkey someteamname/not-managed is not managed by Console", - path = { - "deleteValkey", - }, - }, - }, - data = Null, - } -end) - Test.gql("Verify activity log for valkey operations", function(t) t.addHeader("x-user-email", user:email()) t.query(string.format([[ diff --git a/internal/cmd/api/http.go b/internal/cmd/api/http.go index c0d939824..d6d9307d6 100644 --- a/internal/cmd/api/http.go +++ b/internal/cmd/api/http.go @@ -349,7 +349,7 @@ func ConfigureGraph( ctx = instancegroup.NewLoaderContext(ctx, watchers.ReplicaSetWatcher, watchers.PodWatcher, watchers.AppWatcher, dynamicClients, log) ctx = aiven.NewLoaderContext(ctx, aivenProjects) ctx = opensearch.NewLoaderContext(ctx, tenantName, watchers.OpenSearchWatcher, aivenClient, log) - ctx = valkey.NewLoaderContext(ctx, tenantName, watchers.ValkeyWatcher, aivenClient) + ctx = valkey.NewLoaderContext(ctx, tenantName, watchers.ValkeyWatcher, watchers.NaisValkeyWatcher, aivenClient) ctx = price.NewLoaderContext(ctx, priceRetriever, log) ctx = utilization.NewLoaderContext(ctx, prometheusClient, log) ctx = alerts.NewLoaderContext(ctx, prometheusClient, log) diff --git a/internal/kubernetes/fake/fake.go b/internal/kubernetes/fake/fake.go index 234fdfc1f..626c0c0e0 100644 --- a/internal/kubernetes/fake/fake.go +++ b/internal/kubernetes/fake/fake.go @@ -16,6 +16,7 @@ import ( liberator_aiven_io_v1alpha1 "github.com/nais/liberator/pkg/apis/aiven.io/v1alpha1" nais_io_v1alpha1 "github.com/nais/liberator/pkg/apis/nais.io/v1alpha1" data_nais_io_v1 "github.com/nais/pgrator/pkg/api/datav1" + mapperatorv1 "github.com/nais/pgrator/pkg/api/v1" unleash_nais_io_v1 "github.com/nais/unleasherator/api/v1" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -216,6 +217,7 @@ func NewDynamicClient(scheme *runtime.Scheme) *dynfake.FakeDynamicClient { unleash_nais_io_v1.GroupVersion.WithResource("unleashes"): "UnleashList", unleash_nais_io_v1.GroupVersion.WithResource("remoteunleashes"): "RemoteUnleashList", data_nais_io_v1.GroupVersion.WithResource("postgres"): "PostgresList", + mapperatorv1.GroupVersion.WithResource("valkeys"): "ValkeyList", nais_io_v1alpha1.GroupVersion.WithResource("tunnels"): "TunnelList", }) diff --git a/internal/kubernetes/scheme.go b/internal/kubernetes/scheme.go index dbbf03e08..515b73e25 100644 --- a/internal/kubernetes/scheme.go +++ b/internal/kubernetes/scheme.go @@ -10,6 +10,8 @@ import ( nais_io_v1 "github.com/nais/liberator/pkg/apis/nais.io/v1" nais_io_v1alpha1 "github.com/nais/liberator/pkg/apis/nais.io/v1alpha1" data_nais_io_v1 "github.com/nais/pgrator/pkg/api/datav1" + mapperatordatav1 "github.com/nais/pgrator/pkg/api/v1" + mapperatorv1 "github.com/nais/pgrator/pkg/api/v1" unleash_nais_io_v1 "github.com/nais/unleasherator/api/v1" appsv1 "k8s.io/api/apps/v1" authorizationv1 "k8s.io/api/authorization/v1" @@ -40,6 +42,8 @@ func NewScheme() (*runtime.Scheme, error) { aiven_nais_io_v1.AddToScheme, data_nais_io_v1.AddToScheme, authorizationv1.AddToScheme, + mapperatorv1.AddToScheme, + mapperatordatav1.AddToScheme, } for _, f := range funcs { diff --git a/internal/kubernetes/utils.go b/internal/kubernetes/utils.go new file mode 100644 index 000000000..4c49491ca --- /dev/null +++ b/internal/kubernetes/utils.go @@ -0,0 +1,24 @@ +package kubernetes + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" +) + +func ToUnstructured(obj any) (*unstructured.Unstructured, error) { + mp, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + if err != nil { + return nil, err + } + + return &unstructured.Unstructured{Object: mp}, nil +} + +func ToConcrete[T any](u *unstructured.Unstructured) (*T, error) { + var obj T + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil { + return nil, err + } + + return &obj, nil +} diff --git a/internal/kubernetes/watchers/watchers.go b/internal/kubernetes/watchers/watchers.go index 3b5b2f133..9f882e35b 100644 --- a/internal/kubernetes/watchers/watchers.go +++ b/internal/kubernetes/watchers/watchers.go @@ -48,6 +48,7 @@ type ( ConfigWatcher = watcher.Watcher[*config.Config] ReplicaSetWatcher = watcher.Watcher[*appsv1.ReplicaSet] TunnelWatcher = watcher.Watcher[*tunnel.Tunnel] + NaisValkeyWatcher = watcher.Watcher[*valkey.Valkey] ) type Watchers struct { @@ -70,6 +71,7 @@ type Watchers struct { ConfigWatcher *ConfigWatcher ReplicaSetWatcher *ReplicaSetWatcher TunnelWatcher *TunnelWatcher + NaisValkeyWatcher *NaisValkeyWatcher } func SetupWatchers( @@ -97,5 +99,6 @@ func SetupWatchers( ConfigWatcher: config.NewWatcher(ctx, watcherMgr), ReplicaSetWatcher: instancegroup.NewWatcher(ctx, watcherMgr), TunnelWatcher: tunnel.NewWatcher(ctx, watcherMgr), + NaisValkeyWatcher: valkey.NewNaisValkeyWatcher(ctx, watcherMgr), } } diff --git a/internal/persistence/valkey/client.go b/internal/persistence/valkey/client.go index a565a151b..571cccdb8 100644 --- a/internal/persistence/valkey/client.go +++ b/internal/persistence/valkey/client.go @@ -3,16 +3,13 @@ package valkey import ( "context" - "github.com/nais/api/internal/kubernetes/watcher" "github.com/nais/api/internal/slug" "github.com/nais/api/internal/workload" "github.com/nais/api/internal/workload/application" "github.com/nais/api/internal/workload/job" ) -type client struct { - watcher *watcher.Watcher[*Valkey] -} +type client struct{} // NamePrefix returns the Kubernetes resource name prefix for Valkey instances // belonging to the given team (e.g. "valkey-myteam-"). diff --git a/internal/persistence/valkey/dataloader.go b/internal/persistence/valkey/dataloader.go index 81cc60a53..4f7adfffc 100644 --- a/internal/persistence/valkey/dataloader.go +++ b/internal/persistence/valkey/dataloader.go @@ -3,18 +3,28 @@ package valkey import ( "context" + "github.com/nais/api/internal/kubernetes" "github.com/nais/api/internal/kubernetes/watcher" + "github.com/nais/api/internal/slug" "github.com/nais/api/internal/thirdparty/aiven" + naiscrd "github.com/nais/pgrator/pkg/api/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" ) type ctxKey int const loadersKey ctxKey = iota -func NewLoaderContext(ctx context.Context, tenantName string, valkeyWatcher *watcher.Watcher[*Valkey], aivenClient aiven.AivenClient) context.Context { - return context.WithValue(ctx, loadersKey, newLoaders(tenantName, valkeyWatcher, aivenClient)) +var naisGVR = schema.GroupVersionResource{ + Group: "nais.io", + Version: "v1", + Resource: "valkeys", +} + +func NewLoaderContext(ctx context.Context, tenantName string, valkeyWatcher, naisValkeyWatcher *watcher.Watcher[*Valkey], aivenClient aiven.AivenClient) context.Context { + return context.WithValue(ctx, loadersKey, newLoaders(tenantName, valkeyWatcher, naisValkeyWatcher, aivenClient)) } func NewWatcher(ctx context.Context, mgr *watcher.Manager) *watcher.Watcher[*Valkey] { @@ -33,6 +43,22 @@ func NewWatcher(ctx context.Context, mgr *watcher.Manager) *watcher.Watcher[*Val return w } +func NewNaisValkeyWatcher(ctx context.Context, mgr *watcher.Manager) *watcher.Watcher[*Valkey] { + w := watcher.Watch(mgr, &Valkey{}, watcher.WithConverter(func(o *unstructured.Unstructured, environmentName string) (obj any, ok bool) { + v, err := kubernetes.ToConcrete[naiscrd.Valkey](o) + if err != nil { + return nil, false + } + ret, err := toValkeyFromNais(v, environmentName) + if err != nil { + return nil, false + } + return ret, true + }), watcher.WithGVR(naisGVR)) + w.Start(ctx) + return w +} + func fromContext(ctx context.Context) *loaders { return ctx.Value(loadersKey).(*loaders) } @@ -41,18 +67,30 @@ type loaders struct { client *client tenantName string watcher *watcher.Watcher[*Valkey] + naisWatcher *watcher.Watcher[*Valkey] aivenClient aiven.AivenClient } -func newLoaders(tenantName string, watcher *watcher.Watcher[*Valkey], aivenClient aiven.AivenClient) *loaders { - client := &client{ - watcher: watcher, - } +func newLoaders(tenantName string, watcher, naisValkeyWatcher *watcher.Watcher[*Valkey], aivenClient aiven.AivenClient) *loaders { + client := &client{} return &loaders{ client: client, tenantName: tenantName, watcher: watcher, + naisWatcher: naisValkeyWatcher, aivenClient: aivenClient, } } + +func newK8sClient(ctx context.Context, environmentName string, teamSlug slug.Slug) (dynamic.ResourceInterface, error) { + sysClient, err := fromContext(ctx).watcher.ImpersonatedClient( + ctx, + environmentName, + watcher.WithImpersonatedClientGVR(naisGVR), + ) + if err != nil { + return nil, err + } + return sysClient.Namespace(teamSlug.String()), nil +} diff --git a/internal/persistence/valkey/models.go b/internal/persistence/valkey/models.go index 52d56f7aa..33c4c7dea 100644 --- a/internal/persistence/valkey/models.go +++ b/internal/persistence/valkey/models.go @@ -16,6 +16,8 @@ import ( "github.com/nais/api/internal/validate" "github.com/nais/api/internal/workload" aiven_io_v1alpha1 "github.com/nais/liberator/pkg/apis/aiven.io/v1alpha1" + "github.com/nais/pgrator/pkg/api" + naiscrd "github.com/nais/pgrator/pkg/api/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -32,7 +34,7 @@ type ( type Valkey struct { Name string `json:"name"` - Status *ValkeyStatus `json:"status"` + Status *naiscrd.ValkeyStatus `json:"status"` TerminationProtection bool `json:"terminationProtection"` Tier ValkeyTier `json:"tier"` Memory ValkeyMemory `json:"memory"` @@ -158,11 +160,19 @@ func toValkey(u *unstructured.Unstructured, envName string) (*Valkey, error) { return nil, fmt.Errorf("converting to Valkey instance: %w", err) } + if len(obj.GetOwnerReferences()) > 0 { + for _, ownerRef := range obj.GetOwnerReferences() { + if ownerRef.Kind == "Valkey" { + return nil, fmt.Errorf("skipping Valkey %s in namespace %s because it has an owner reference", obj.GetName(), obj.GetNamespace()) + } + } + } + // Liberator doesn't contain this field, so we read it directly from the unstructured object terminationProtection, _, _ := unstructured.NestedBool(u.Object, specTerminationProtection...) maxMemoryPolicyStr, _, _ := unstructured.NestedString(u.Object, specMaxMemoryPolicy...) - maxMemoryPolicy, err := ValkeyMaxMemoryPolicyFromAivenString(maxMemoryPolicyStr) + maxMemoryPolicy, err := ValkeyMaxMemoryPolicyFromAivenString(naiscrd.ValkeyMaxMemoryPolicy(maxMemoryPolicyStr)) if err != nil { maxMemoryPolicy = "" } @@ -188,9 +198,10 @@ func toValkey(u *unstructured.Unstructured, envName string) (*Valkey, error) { Name: name, EnvironmentName: envName, TerminationProtection: terminationProtection, - Status: &ValkeyStatus{ - Conditions: obj.Status.Conditions, - State: obj.Status.State, + Status: &naiscrd.ValkeyStatus{ + BaseStatus: api.BaseStatus{ + Conditions: obj.Status.Conditions, + }, }, TeamSlug: slug.Slug(obj.GetNamespace()), WorkloadReference: workload.ReferenceFromOwnerReferences(obj.GetOwnerReferences()), @@ -203,6 +214,28 @@ func toValkey(u *unstructured.Unstructured, envName string) (*Valkey, error) { }, nil } +func toValkeyFromNais(v *naiscrd.Valkey, envName string) (*Valkey, error) { + var mmp ValkeyMaxMemoryPolicy + if v.Spec.MaxMemoryPolicy != "" { + var err error + mmp, err = ValkeyMaxMemoryPolicyFromAivenString(v.Spec.MaxMemoryPolicy) + if err != nil { + return nil, err + } + } + return &Valkey{ + Name: v.Name, + EnvironmentName: envName, + Status: v.Status, + TeamSlug: slug.Slug(v.Namespace), + WorkloadReference: workload.ReferenceFromOwnerReferences(v.OwnerReferences), + Tier: fromMapperatorTier(v.Spec.Tier), + Memory: fromMapperatorMemory(v.Spec.Memory), + NotifyKeyspaceEvents: v.Spec.NotifyKeyspaceEvents, + MaxMemoryPolicy: mmp, + }, nil +} + type TeamInventoryCountValkeys struct { Total int } @@ -338,9 +371,9 @@ func (e ValkeyMaxMemoryPolicy) MarshalGQL(w io.Writer) { fmt.Fprint(w, strconv.Quote(e.String())) } -func ValkeyMaxMemoryPolicyFromAivenString(s string) (ValkeyMaxMemoryPolicy, error) { +func ValkeyMaxMemoryPolicyFromAivenString(s naiscrd.ValkeyMaxMemoryPolicy) (ValkeyMaxMemoryPolicy, error) { for _, policy := range AllValkeyMaxMemoryPolicy { - if policy.ToAivenString() == s { + if policy.ToAivenString() == string(s) { return policy, nil } } @@ -350,21 +383,21 @@ func ValkeyMaxMemoryPolicyFromAivenString(s string) (ValkeyMaxMemoryPolicy, erro func (e ValkeyMaxMemoryPolicy) ToAivenString() string { switch e { case ValkeyMaxMemoryPolicyAllkeysLfu: - return "allkeys-lfu" + return string(naiscrd.ValkeyMaxMemoryPolicyAllkeysLFU) case ValkeyMaxMemoryPolicyAllkeysLru: - return "allkeys-lru" + return string(naiscrd.ValkeyMaxMemoryPolicyAllkeysLRU) case ValkeyMaxMemoryPolicyAllkeysRandom: - return "allkeys-random" + return string(naiscrd.ValkeyMaxMemoryPolicyAllkeysRandom) case ValkeyMaxMemoryPolicyNoEviction: - return "noeviction" + return string(naiscrd.ValkeyMaxMemoryPolicyNoEviction) case ValkeyMaxMemoryPolicyVolatileLfu: - return "volatile-lfu" + return string(naiscrd.ValkeyMaxMemoryPolicyVolatileLFU) case ValkeyMaxMemoryPolicyVolatileLru: - return "volatile-lru" + return string(naiscrd.ValkeyMaxMemoryPolicyVolatileLRU) case ValkeyMaxMemoryPolicyVolatileRandom: - return "volatile-random" + return string(naiscrd.ValkeyMaxMemoryPolicyVolatileRandom) case ValkeyMaxMemoryPolicyVolatileTTL: - return "volatile-ttl" + return string(naiscrd.ValkeyMaxMemoryPolicyVolatileTTL) default: return "" } diff --git a/internal/persistence/valkey/queries.go b/internal/persistence/valkey/queries.go index 2121daf2f..474834d7f 100644 --- a/internal/persistence/valkey/queries.go +++ b/internal/persistence/valkey/queries.go @@ -2,6 +2,7 @@ package valkey import ( "context" + "errors" "fmt" "regexp" "strconv" @@ -20,6 +21,7 @@ import ( "github.com/nais/api/internal/slug" "github.com/nais/api/internal/thirdparty/aiven" nais_io_v1 "github.com/nais/liberator/pkg/apis/nais.io/v1" + naiscrd "github.com/nais/pgrator/pkg/api/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -43,11 +45,15 @@ func GetByIdent(ctx context.Context, id ident.Ident) (*Valkey, error) { } func Get(ctx context.Context, teamSlug slug.Slug, environment, name string) (*Valkey, error) { - prefix := instanceNamer(teamSlug, "") - if !strings.HasPrefix(name, prefix) { - name = instanceNamer(teamSlug, name) + v, err := fromContext(ctx).naisWatcher.Get(environment, teamSlug.String(), name) + if errors.Is(err, &watcher.ErrorNotFound{}) { + prefix := instanceNamer(teamSlug, "") + if !strings.HasPrefix(name, prefix) { + name = instanceNamer(teamSlug, name) + } + v, err = fromContext(ctx).watcher.Get(environment, teamSlug.String(), name) } - return fromContext(ctx).client.watcher.Get(environment, teamSlug.String(), name) + return v, err } func ListForTeam(ctx context.Context, teamSlug slug.Slug, page *pagination.Pagination, orderBy *ValkeyOrder) (*ValkeyConnection, error) { @@ -59,7 +65,9 @@ func ListForTeam(ctx context.Context, teamSlug slug.Slug, page *pagination.Pagin } func ListAllForTeam(ctx context.Context, teamSlug slug.Slug) []*Valkey { - all := fromContext(ctx).client.watcher.GetByNamespace(teamSlug.String(), watcher.WithoutDeleted()) + all := fromContext(ctx).watcher.GetByNamespace(teamSlug.String(), watcher.WithoutDeleted()) + allNais := fromContext(ctx).naisWatcher.GetByNamespace(teamSlug.String(), watcher.WithoutDeleted()) + all = append(all, allNais...) return watcher.Objects(all) } @@ -93,7 +101,9 @@ func ListAccess(ctx context.Context, valkey *Valkey, page *pagination.Pagination } func ListForWorkload(ctx context.Context, teamSlug slug.Slug, environmentName string, references []nais_io_v1.Valkey, orderBy *ValkeyOrder) (*ValkeyConnection, error) { - all := fromContext(ctx).client.watcher.GetByNamespace(teamSlug.String(), watcher.InCluster(environmentName)) + all := fromContext(ctx).watcher.GetByNamespace(teamSlug.String(), watcher.InCluster(environmentName)) + allNais := fromContext(ctx).naisWatcher.GetByNamespace(teamSlug.String(), watcher.InCluster(environmentName)) + all = append(all, allNais...) ret := make([]*Valkey, 0) for _, ref := range references { @@ -124,77 +134,40 @@ func Create(ctx context.Context, input CreateValkeyInput) (*CreateValkeyPayload, return nil, err } - namespace := input.TeamSlug.String() - client, err := fromContext(ctx).watcher.ImpersonatedClientWithNamespace(ctx, input.EnvironmentName, namespace) - if err != nil { - return nil, err - } - - machine, err := machineTypeFromTierAndMemory(input.Tier, input.Memory) + client, err := newK8sClient(ctx, input.EnvironmentName, input.TeamSlug) if err != nil { return nil, err } - res := &unstructured.Unstructured{} - res.SetAPIVersion("aiven.io/v1alpha1") - res.SetKind("Valkey") - res.SetName(instanceNamer(input.TeamSlug, input.Name)) - res.SetNamespace(namespace) - res.SetAnnotations(kubernetes.WithCommonAnnotations(nil, authz.ActorFromContext(ctx).User.Identity())) - kubernetes.SetManagedByConsoleLabel(res) - - aivenProject, err := aiven.GetProject(ctx, input.EnvironmentName) - if err != nil { - return nil, err + // Ensure there's no existing Aiven Valkey with the same name + // This can be removed when we manage all valkeys through Console + _, err = fromContext(ctx).watcher.Get(input.EnvironmentName, input.TeamSlug.String(), instanceNamer(input.TeamSlug, input.Name)) + if !errors.Is(err, &watcher.ErrorNotFound{}) { + return nil, apierror.Errorf("Valkey with the name %q already exists, but are not yet managed through Console.", input.Name) } - res.Object["spec"] = map[string]any{ - "cloudName": "google-europe-north1", - "plan": machine.AivenPlan, - "project": aivenProject.ID, - "projectVpcId": aivenProject.VPC, - "terminationProtection": true, - "tags": map[string]any{ - "environment": input.EnvironmentName, - "team": namespace, - "tenant": fromContext(ctx).tenantName, + res := &naiscrd.Valkey{ + TypeMeta: metav1.TypeMeta{ + Kind: "Valkey", + APIVersion: "nais.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: input.Name, + Namespace: input.TeamSlug.String(), + }, + Spec: naiscrd.ValkeySpec{ + Tier: toMapperatorTier(input.Tier), + Memory: toMapperatorMemory(input.Memory), }, } + res.SetAnnotations(kubernetes.WithCommonAnnotations(nil, authz.ActorFromContext(ctx).User.Identity())) + kubernetes.SetManagedByConsoleLabel(res) if input.MaxMemoryPolicy != nil { - maxMemoryPolicy := input.MaxMemoryPolicy.ToAivenString() - err := unstructured.SetNestedField(res.Object, maxMemoryPolicy, specMaxMemoryPolicy...) - if err != nil { - return nil, err - } + res.Spec.MaxMemoryPolicy = naiscrd.ValkeyMaxMemoryPolicy(input.MaxMemoryPolicy.ToAivenString()) } - if input.NotifyKeyspaceEvents != nil { - err := unstructured.SetNestedField(res.Object, *input.NotifyKeyspaceEvents, specNotifyKeyspaceEvents...) - if err != nil { - return nil, err - } - } - - if input.Databases != nil { - // Must be stored and read as float64 for the integration tests to work. - err := unstructured.SetNestedField(res.Object, float64(*input.Databases), specNumberOfDatabases...) - if err != nil { - return nil, err - } - } - - ret, err := client.Create(ctx, res, metav1.CreateOptions{}) - if err != nil { - if k8serrors.IsAlreadyExists(err) { - return nil, apierror.ErrAlreadyExists - } - return nil, err - } - - err = aiven.UpsertPrometheusServiceIntegration(ctx, fromContext(ctx).watcher, ret, aivenProject, input.EnvironmentName) - if err != nil { - return nil, fmt.Errorf("creating Prometheus service integration: %w", err) + res.Spec.NotifyKeyspaceEvents = *input.NotifyKeyspaceEvents } err = activitylog.Create(ctx, activitylog.CreateInput{ @@ -205,11 +178,20 @@ func Create(ctx context.Context, input CreateValkeyInput) (*CreateValkeyPayload, EnvironmentName: new(input.EnvironmentName), TeamSlug: new(input.TeamSlug), }) + + obj, err := kubernetes.ToUnstructured(res) if err != nil { return nil, err } - valkey, err := toValkey(ret, input.EnvironmentName) + if _, err = client.Create(ctx, obj, metav1.CreateOptions{}); err != nil { + if k8serrors.IsAlreadyExists(err) { + return nil, apierror.ErrAlreadyExists + } + return nil, err + } + + valkey, err := toValkeyFromNais(res, input.EnvironmentName) if err != nil { return nil, err } @@ -224,44 +206,37 @@ func Update(ctx context.Context, input UpdateValkeyInput) (*UpdateValkeyPayload, return nil, err } - client, err := fromContext(ctx).watcher.ImpersonatedClientWithNamespace(ctx, input.EnvironmentName, input.TeamSlug.String()) - if err != nil { - return nil, err - } - - valkey, err := client.Get(ctx, instanceNamer(input.TeamSlug, input.Name), metav1.GetOptions{}) + client, err := newK8sClient(ctx, input.EnvironmentName, input.TeamSlug) if err != nil { return nil, err } - if !kubernetes.HasManagedByConsoleLabel(valkey) { - return nil, apierror.Errorf("Valkey %s/%s is not managed by Console", input.TeamSlug, input.Name) - } - changes := make([]*ValkeyUpdatedActivityLogEntryDataUpdatedField, 0) - - res, err := updatePlan(valkey, input) + valkey, err := client.Get(ctx, input.Name, metav1.GetOptions{}) if err != nil { return nil, err } - changes = append(changes, res...) - res, err = updateMaxMemoryPolicy(valkey, input) + concreteValkey, err := kubernetes.ToConcrete[naiscrd.Valkey](valkey) if err != nil { return nil, err } - changes = append(changes, res...) - res, err = updateNotifyKeyspaceEvents(valkey, input) - if err != nil { - return nil, err + changes := make([]*ValkeyUpdatedActivityLogEntryDataUpdatedField, 0) + updateFuncs := []func(*naiscrd.Valkey, UpdateValkeyInput) ([]*ValkeyUpdatedActivityLogEntryDataUpdatedField, error){ + updateTier, + updateMemory, + updateMaxMemoryPolicy, + updateNotifyKeyspaceEvents, + updateDatabases, } - changes = append(changes, res...) - res, err = updateDatabases(valkey, input) - if err != nil { - return nil, err + for _, f := range updateFuncs { + res, err := f(concreteValkey, input) + if err != nil { + return nil, err + } + changes = append(changes, res...) } - changes = append(changes, res...) if len(changes) == 0 { vk, err := toValkey(valkey, input.EnvironmentName) @@ -274,21 +249,16 @@ func Update(ctx context.Context, input UpdateValkeyInput) (*UpdateValkeyPayload, }, nil } - valkey.SetAnnotations(kubernetes.WithCommonAnnotations(valkey.GetAnnotations(), authz.ActorFromContext(ctx).User.Identity())) - - ret, err := client.Update(ctx, valkey, metav1.UpdateOptions{}) + obj, err := kubernetes.ToUnstructured(concreteValkey) if err != nil { return nil, err } - aivenProject, err := aiven.GetProject(ctx, input.EnvironmentName) - if err != nil { - return nil, err - } + obj.SetAnnotations(kubernetes.WithCommonAnnotations(obj.GetAnnotations(), authz.ActorFromContext(ctx).User.Identity())) - err = aiven.UpsertPrometheusServiceIntegration(ctx, fromContext(ctx).watcher, ret, aivenProject, input.EnvironmentName) + ret, err := client.Update(ctx, obj, metav1.UpdateOptions{}) if err != nil { - return nil, fmt.Errorf("creating Prometheus service integration: %w", err) + return nil, err } err = activitylog.Create(ctx, activitylog.CreateInput{ @@ -306,7 +276,12 @@ func Update(ctx context.Context, input UpdateValkeyInput) (*UpdateValkeyPayload, return nil, err } - valkeyUpdated, err := toValkey(ret, input.EnvironmentName) + retValkey, err := kubernetes.ToConcrete[naiscrd.Valkey](ret) + if err != nil { + return nil, err + } + + valkeyUpdated, err := toValkeyFromNais(retValkey, input.EnvironmentName) if err != nil { return nil, err } @@ -370,151 +345,117 @@ func valkeyEnvVarSuffix(instanceName string) string { return strings.ToUpper(valkeyNamePattern.ReplaceAllString(instanceName, "_")) } -func updatePlan(valkey *unstructured.Unstructured, input UpdateValkeyInput) ([]*ValkeyUpdatedActivityLogEntryDataUpdatedField, error) { +func updateTier(valkey *naiscrd.Valkey, input UpdateValkeyInput) ([]*ValkeyUpdatedActivityLogEntryDataUpdatedField, error) { changes := make([]*ValkeyUpdatedActivityLogEntryDataUpdatedField, 0) - desired, err := machineTypeFromTierAndMemory(input.Tier, input.Memory) - if err != nil { - return nil, err - } - - oldPlan, found, err := unstructured.NestedString(valkey.Object, specPlan...) - if err != nil { - return nil, err - } - if !found { - // .spec.plan is a required field - return nil, fmt.Errorf("missing .spec.plan in Valkey resource") - } - - if oldPlan == desired.AivenPlan { - return changes, nil - } - - oldMachine, err := machineTypeFromPlan(oldPlan) - if err != nil { - return nil, err - } - - if input.Tier != oldMachine.Tier { + origTier := fromMapperatorTier(valkey.Spec.Tier) + if input.Tier != origTier { changes = append(changes, &ValkeyUpdatedActivityLogEntryDataUpdatedField{ Field: "tier", - OldValue: new(oldMachine.Tier.String()), + OldValue: new(origTier.String()), NewValue: new(input.Tier.String()), }) } - if input.Memory != oldMachine.Memory { + valkey.Spec.Tier = toMapperatorTier(input.Tier) + + return changes, nil +} + +func updateMemory(valkey *naiscrd.Valkey, input UpdateValkeyInput) ([]*ValkeyUpdatedActivityLogEntryDataUpdatedField, error) { + changes := make([]*ValkeyUpdatedActivityLogEntryDataUpdatedField, 0) + + origMemory := fromMapperatorMemory(valkey.Spec.Memory) + if input.Memory != origMemory { changes = append(changes, &ValkeyUpdatedActivityLogEntryDataUpdatedField{ Field: "memory", - OldValue: new(oldMachine.Memory.String()), + OldValue: new(origMemory.String()), NewValue: new(input.Memory.String()), }) } - if err := unstructured.SetNestedField(valkey.Object, desired.AivenPlan, specPlan...); err != nil { - return nil, err - } + valkey.Spec.Memory = toMapperatorMemory(input.Memory) return changes, nil } -func updateMaxMemoryPolicy(valkey *unstructured.Unstructured, input UpdateValkeyInput) ([]*ValkeyUpdatedActivityLogEntryDataUpdatedField, error) { - changes := make([]*ValkeyUpdatedActivityLogEntryDataUpdatedField, 0) - +func updateMaxMemoryPolicy(valkey *naiscrd.Valkey, input UpdateValkeyInput) ([]*ValkeyUpdatedActivityLogEntryDataUpdatedField, error) { if input.MaxMemoryPolicy == nil { - return changes, nil + return nil, nil } - oldAivenPolicy, found, err := unstructured.NestedString(valkey.Object, specMaxMemoryPolicy...) - if err != nil { - return nil, err + if string(valkey.Spec.MaxMemoryPolicy) == input.MaxMemoryPolicy.ToAivenString() { + return nil, nil } - if found && oldAivenPolicy == input.MaxMemoryPolicy.ToAivenString() { - return changes, nil - } - // continue if not found so that we explicitly set the policy on the resource - - var oldValue *string - if found { - oldPolicy, err := ValkeyMaxMemoryPolicyFromAivenString(oldAivenPolicy) + var oldMMP *string + if valkey.Spec.MaxMemoryPolicy != "" { + old, err := ValkeyMaxMemoryPolicyFromAivenString(valkey.Spec.MaxMemoryPolicy) if err != nil { - return nil, err + return nil, fmt.Errorf("parsing existing max memory policy: %w", err) } - oldValue = new(oldPolicy.String()) + oldMMP = ptr.To(old.String()) } + changes := make([]*ValkeyUpdatedActivityLogEntryDataUpdatedField, 0) + changes = append(changes, &ValkeyUpdatedActivityLogEntryDataUpdatedField{ Field: "maxMemoryPolicy", - OldValue: oldValue, + OldValue: oldMMP, NewValue: new(input.MaxMemoryPolicy.String()), }) - maxMemoryPolicy := input.MaxMemoryPolicy.ToAivenString() - if err := unstructured.SetNestedField(valkey.Object, maxMemoryPolicy, specMaxMemoryPolicy...); err != nil { - return nil, err - } + valkey.Spec.MaxMemoryPolicy = naiscrd.ValkeyMaxMemoryPolicy(input.MaxMemoryPolicy.ToAivenString()) return changes, nil } -func updateNotifyKeyspaceEvents(valkey *unstructured.Unstructured, input UpdateValkeyInput) ([]*ValkeyUpdatedActivityLogEntryDataUpdatedField, error) { - changes := make([]*ValkeyUpdatedActivityLogEntryDataUpdatedField, 0) - +func updateNotifyKeyspaceEvents(valkey *naiscrd.Valkey, input UpdateValkeyInput) ([]*ValkeyUpdatedActivityLogEntryDataUpdatedField, error) { if input.NotifyKeyspaceEvents == nil { - return changes, nil + return nil, nil } - oldValue, found, err := unstructured.NestedString(valkey.Object, specNotifyKeyspaceEvents...) - if err != nil { - return nil, err + if valkey.Spec.NotifyKeyspaceEvents == *input.NotifyKeyspaceEvents { + return nil, nil } - if found && oldValue == *input.NotifyKeyspaceEvents { - return changes, nil - } + changes := make([]*ValkeyUpdatedActivityLogEntryDataUpdatedField, 0) - var oldValPtr *string - if found { - oldValPtr = new(oldValue) + var oldValue *string + if valkey.Spec.NotifyKeyspaceEvents != "" { + oldValue = new(valkey.Spec.NotifyKeyspaceEvents)) } - changes = append(changes, &ValkeyUpdatedActivityLogEntryDataUpdatedField{ Field: "notifyKeyspaceEvents", - OldValue: oldValPtr, + OldValue: oldValue, NewValue: input.NotifyKeyspaceEvents, }) - if err := unstructured.SetNestedField(valkey.Object, *input.NotifyKeyspaceEvents, specNotifyKeyspaceEvents...); err != nil { - return nil, err - } - + valkey.Spec.NotifyKeyspaceEvents = *input.NotifyKeyspaceEvents return changes, nil } -func updateDatabases(valkey *unstructured.Unstructured, input UpdateValkeyInput) ([]*ValkeyUpdatedActivityLogEntryDataUpdatedField, error) { +func updateDatabases(valkey *naiscrd.Valkey, input UpdateValkeyInput) ([]*ValkeyUpdatedActivityLogEntryDataUpdatedField, error) { changes := make([]*ValkeyUpdatedActivityLogEntryDataUpdatedField, 0) if input.Databases == nil { return changes, nil } - oldValue, found, _ := unstructured.NestedNumberAsFloat64(valkey.Object, specNumberOfDatabases...) - if found && oldValue == float64(*input.Databases) { + oldValue := valkey.Spec.Databases + if oldValue == input.Databases { return changes, nil } - var oldValPtr *string - if found { - oldValPtr = new(strconv.FormatInt(int64(oldValue), 10)) - } - - newVal := strconv.Itoa(*input.Databases) changes = append(changes, &ValkeyUpdatedActivityLogEntryDataUpdatedField{ Field: "databases", - OldValue: oldValPtr, - NewValue: &newVal, + OldValue: func() *string { + if oldValue != nil { + return new(strconv.FormatInt(int64(*oldValue), 10)) + } + return nil + }(), + NewValue: new(strconv.Itoa(*input.Databases)), }) // Must be stored and read as float64 for the integration tests to work. @@ -530,13 +471,12 @@ func Delete(ctx context.Context, input DeleteValkeyInput) (*DeleteValkeyPayload, return nil, err } - name := instanceNamer(input.TeamSlug, input.Name) - client, err := fromContext(ctx).watcher.ImpersonatedClientWithNamespace(ctx, input.EnvironmentName, input.TeamSlug.String()) + client, err := newK8sClient(ctx, input.EnvironmentName, input.TeamSlug) if err != nil { return nil, err } - valkey, err := client.Get(ctx, name, metav1.GetOptions{}) + valkey, err := client.Get(ctx, input.Name, metav1.GetOptions{}) if err != nil { return nil, err } @@ -560,7 +500,7 @@ func Delete(ctx context.Context, input DeleteValkeyInput) (*DeleteValkeyPayload, } } - if err := fromContext(ctx).watcher.Delete(ctx, input.EnvironmentName, input.TeamSlug.String(), name); err != nil { + if err := fromContext(ctx).naisWatcher.Delete(ctx, input.EnvironmentName, input.TeamSlug.String(), input.Name); err != nil { return nil, err } @@ -604,3 +544,71 @@ func State(ctx context.Context, v *Valkey) (ValkeyState, error) { return ValkeyStateUnknown, nil } } + +func toMapperatorTier(tier ValkeyTier) naiscrd.ValkeyTier { + switch tier { + case ValkeyTierSingleNode: + return naiscrd.ValkeyTierSingleNode + case ValkeyTierHighAvailability: + return naiscrd.ValkeyTierHighAvailability + default: + return "" + } +} + +func fromMapperatorTier(tier naiscrd.ValkeyTier) ValkeyTier { + switch tier { + case naiscrd.ValkeyTierSingleNode: + return ValkeyTierSingleNode + case naiscrd.ValkeyTierHighAvailability: + return ValkeyTierHighAvailability + default: + return "" + } +} + +func toMapperatorMemory(memory ValkeyMemory) naiscrd.ValkeyMemory { + switch memory { + case ValkeyMemoryGB1: + return naiscrd.ValkeyMemory1GB + case ValkeyMemoryGB4: + return naiscrd.ValkeyMemory4GB + case ValkeyMemoryGB8: + return naiscrd.ValkeyMemory8GB + case ValkeyMemoryGB14: + return naiscrd.ValkeyMemory14GB + case ValkeyMemoryGB28: + return naiscrd.ValkeyMemory28GB + case ValkeyMemoryGB56: + return naiscrd.ValkeyMemory56GB + case ValkeyMemoryGB112: + return naiscrd.ValkeyMemory112GB + case ValkeyMemoryGB200: + return naiscrd.ValkeyMemory200GB + default: + return "" + } +} + +func fromMapperatorMemory(memory naiscrd.ValkeyMemory) ValkeyMemory { + switch memory { + case naiscrd.ValkeyMemory1GB: + return ValkeyMemoryGB1 + case naiscrd.ValkeyMemory4GB: + return ValkeyMemoryGB4 + case naiscrd.ValkeyMemory8GB: + return ValkeyMemoryGB8 + case naiscrd.ValkeyMemory14GB: + return ValkeyMemoryGB14 + case naiscrd.ValkeyMemory28GB: + return ValkeyMemoryGB28 + case naiscrd.ValkeyMemory56GB: + return ValkeyMemoryGB56 + case naiscrd.ValkeyMemory112GB: + return ValkeyMemoryGB112 + case naiscrd.ValkeyMemory200GB: + return ValkeyMemoryGB200 + default: + return "" + } +} From 488fd2abf4c439d133d604560b82b72d4b849501 Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Wed, 11 Feb 2026 11:49:59 +0100 Subject: [PATCH 02/17] Fix checks --- internal/kubernetes/scheme.go | 2 -- internal/persistence/valkey/machines.go | 28 ------------------------- internal/persistence/valkey/queries.go | 4 +++- 3 files changed, 3 insertions(+), 31 deletions(-) diff --git a/internal/kubernetes/scheme.go b/internal/kubernetes/scheme.go index 515b73e25..60e0a63e2 100644 --- a/internal/kubernetes/scheme.go +++ b/internal/kubernetes/scheme.go @@ -10,7 +10,6 @@ import ( nais_io_v1 "github.com/nais/liberator/pkg/apis/nais.io/v1" nais_io_v1alpha1 "github.com/nais/liberator/pkg/apis/nais.io/v1alpha1" data_nais_io_v1 "github.com/nais/pgrator/pkg/api/datav1" - mapperatordatav1 "github.com/nais/pgrator/pkg/api/v1" mapperatorv1 "github.com/nais/pgrator/pkg/api/v1" unleash_nais_io_v1 "github.com/nais/unleasherator/api/v1" appsv1 "k8s.io/api/apps/v1" @@ -43,7 +42,6 @@ func NewScheme() (*runtime.Scheme, error) { data_nais_io_v1.AddToScheme, authorizationv1.AddToScheme, mapperatorv1.AddToScheme, - mapperatordatav1.AddToScheme, } for _, f := range funcs { diff --git a/internal/persistence/valkey/machines.go b/internal/persistence/valkey/machines.go index 62353cf30..081caf9b1 100644 --- a/internal/persistence/valkey/machines.go +++ b/internal/persistence/valkey/machines.go @@ -27,24 +27,10 @@ var machineTypes = []machineType{ {AivenPlan: "business-200", Tier: ValkeyTierHighAvailability, Memory: ValkeyMemoryGB200}, } -// tierAndMemory transposes machineTypes for lookup by ValkeyTier and ValkeyMemory -var tierAndMemory map[ValkeyTier]map[ValkeyMemory]machineType - // aivenPlans transposes machineTypes for lookup by an Aiven plan string var aivenPlans map[string]machineType func init() { - tierAndMemory = make(map[ValkeyTier]map[ValkeyMemory]machineType) - for _, m := range machineTypes { - if _, ok := tierAndMemory[m.Tier]; !ok { - tierAndMemory[m.Tier] = make(map[ValkeyMemory]machineType) - } - if _, ok := tierAndMemory[m.Tier][m.Memory]; ok { - panic("duplicate tier and memory combination [" + string(m.Tier) + ", " + string(m.Memory) + "] in machineTypes") - } - tierAndMemory[m.Tier][m.Memory] = m - } - aivenPlans = make(map[string]machineType) for _, m := range machineTypes { if _, ok := aivenPlans[m.AivenPlan]; ok { @@ -54,20 +40,6 @@ func init() { } } -func machineTypeFromTierAndMemory(tier ValkeyTier, memory ValkeyMemory) (*machineType, error) { - memories, ok := tierAndMemory[tier] - if !ok { - return nil, apierror.Errorf("Invalid Valkey tier: %s", tier) - } - - machine, ok := memories[memory] - if !ok { - return nil, apierror.Errorf("Invalid Valkey memory for tier. %v cannot have memory %v", tier, memory) - } - - return &machine, nil -} - func machineTypeFromPlan(plan string) (*machineType, error) { machine, ok := aivenPlans[plan] if !ok { diff --git a/internal/persistence/valkey/queries.go b/internal/persistence/valkey/queries.go index 474834d7f..d5456ce46 100644 --- a/internal/persistence/valkey/queries.go +++ b/internal/persistence/valkey/queries.go @@ -28,7 +28,6 @@ import ( ) var ( - specPlan = []string{"spec", "plan"} specTerminationProtection = []string{"spec", "terminationProtection"} specMaxMemoryPolicy = []string{"spec", "userConfig", "valkey_maxmemory_policy"} specNotifyKeyspaceEvents = []string{"spec", "userConfig", "valkey_notify_keyspace_events"} @@ -178,6 +177,9 @@ func Create(ctx context.Context, input CreateValkeyInput) (*CreateValkeyPayload, EnvironmentName: new(input.EnvironmentName), TeamSlug: new(input.TeamSlug), }) + if err != nil { + return nil, err + } obj, err := kubernetes.ToUnstructured(res) if err != nil { From e4bef20fdf766bcc28b009c28c6ecf15adc108b7 Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Wed, 11 Feb 2026 12:54:40 +0100 Subject: [PATCH 03/17] Review comments --- internal/persistence/valkey/queries.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/internal/persistence/valkey/queries.go b/internal/persistence/valkey/queries.go index d5456ce46..65f365417 100644 --- a/internal/persistence/valkey/queries.go +++ b/internal/persistence/valkey/queries.go @@ -141,8 +141,10 @@ func Create(ctx context.Context, input CreateValkeyInput) (*CreateValkeyPayload, // Ensure there's no existing Aiven Valkey with the same name // This can be removed when we manage all valkeys through Console _, err = fromContext(ctx).watcher.Get(input.EnvironmentName, input.TeamSlug.String(), instanceNamer(input.TeamSlug, input.Name)) - if !errors.Is(err, &watcher.ErrorNotFound{}) { + if err == nil { return nil, apierror.Errorf("Valkey with the name %q already exists, but are not yet managed through Console.", input.Name) + } else if !errors.Is(err, &watcher.ErrorNotFound{}) { + return nil, err } res := &naiscrd.Valkey{ @@ -241,7 +243,11 @@ func Update(ctx context.Context, input UpdateValkeyInput) (*UpdateValkeyPayload, } if len(changes) == 0 { - vk, err := toValkey(valkey, input.EnvironmentName) + v, err := kubernetes.ToConcrete[naiscrd.Valkey](valkey) + if err != nil { + return nil, err + } + vk, err := toValkeyFromNais(v, input.EnvironmentName) if err != nil { return nil, err } From edb2e565e3c14d991ac99a34b55d6ea343c6fb94 Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Fri, 13 Feb 2026 10:52:42 +0100 Subject: [PATCH 04/17] Change delete logic for valkey delete Co-Authored-By: Trong Huu Nguyen --- internal/persistence/valkey/queries.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/persistence/valkey/queries.go b/internal/persistence/valkey/queries.go index 65f365417..381e6a6ed 100644 --- a/internal/persistence/valkey/queries.go +++ b/internal/persistence/valkey/queries.go @@ -21,6 +21,7 @@ import ( "github.com/nais/api/internal/slug" "github.com/nais/api/internal/thirdparty/aiven" nais_io_v1 "github.com/nais/liberator/pkg/apis/nais.io/v1" + "github.com/nais/pgrator/pkg/api" naiscrd "github.com/nais/pgrator/pkg/api/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -493,18 +494,17 @@ func Delete(ctx context.Context, input DeleteValkeyInput) (*DeleteValkeyPayload, return nil, apierror.Errorf("Valkey %s/%s is not managed by Console", input.TeamSlug, input.Name) } - terminationProtection, found, err := unstructured.NestedBool(valkey.Object, specTerminationProtection...) - if err != nil { - return nil, err + annotations := valkey.GetAnnotations() + if annotations == nil { + annotations = make(map[string]string) } - if found && terminationProtection { - if err := unstructured.SetNestedField(valkey.Object, false, specTerminationProtection...); err != nil { - return nil, err - } + if annotations[api.AllowDeletionAnnotation] != "true" { + annotations[api.AllowDeletionAnnotation] = "true" + valkey.SetAnnotations(annotations) _, err = client.Update(ctx, valkey, metav1.UpdateOptions{}) if err != nil { - return nil, fmt.Errorf("removing deletion protection: %w", err) + return nil, fmt.Errorf("set allow deletion annotation: %w", err) } } From 149930b4a14379459ae2dd31f049f3cfa99c23fc Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Fri, 13 Feb 2026 11:18:09 +0100 Subject: [PATCH 05/17] Use correct client for CUD. Co-authored-by: Trong Huu Nguyen --- internal/persistence/valkey/dataloader.go | 15 ++++++--------- internal/persistence/valkey/queries.go | 8 +++----- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/internal/persistence/valkey/dataloader.go b/internal/persistence/valkey/dataloader.go index 4f7adfffc..372694c2f 100644 --- a/internal/persistence/valkey/dataloader.go +++ b/internal/persistence/valkey/dataloader.go @@ -17,12 +17,6 @@ type ctxKey int const loadersKey ctxKey = iota -var naisGVR = schema.GroupVersionResource{ - Group: "nais.io", - Version: "v1", - Resource: "valkeys", -} - func NewLoaderContext(ctx context.Context, tenantName string, valkeyWatcher, naisValkeyWatcher *watcher.Watcher[*Valkey], aivenClient aiven.AivenClient) context.Context { return context.WithValue(ctx, loadersKey, newLoaders(tenantName, valkeyWatcher, naisValkeyWatcher, aivenClient)) } @@ -54,7 +48,11 @@ func NewNaisValkeyWatcher(ctx context.Context, mgr *watcher.Manager) *watcher.Wa return nil, false } return ret, true - }), watcher.WithGVR(naisGVR)) + }), watcher.WithGVR(schema.GroupVersionResource{ + Group: "nais.io", + Version: "v1", + Resource: "valkeys", + })) w.Start(ctx) return w } @@ -84,10 +82,9 @@ func newLoaders(tenantName string, watcher, naisValkeyWatcher *watcher.Watcher[* } func newK8sClient(ctx context.Context, environmentName string, teamSlug slug.Slug) (dynamic.ResourceInterface, error) { - sysClient, err := fromContext(ctx).watcher.ImpersonatedClient( + sysClient, err := fromContext(ctx).naisWatcher.SystemAuthenticatedClient( ctx, environmentName, - watcher.WithImpersonatedClientGVR(naisGVR), ) if err != nil { return nil, err diff --git a/internal/persistence/valkey/queries.go b/internal/persistence/valkey/queries.go index 381e6a6ed..356b28912 100644 --- a/internal/persistence/valkey/queries.go +++ b/internal/persistence/valkey/queries.go @@ -101,15 +101,13 @@ func ListAccess(ctx context.Context, valkey *Valkey, page *pagination.Pagination } func ListForWorkload(ctx context.Context, teamSlug slug.Slug, environmentName string, references []nais_io_v1.Valkey, orderBy *ValkeyOrder) (*ValkeyConnection, error) { - all := fromContext(ctx).watcher.GetByNamespace(teamSlug.String(), watcher.InCluster(environmentName)) - allNais := fromContext(ctx).naisWatcher.GetByNamespace(teamSlug.String(), watcher.InCluster(environmentName)) - all = append(all, allNais...) + all := ListAllForTeam(ctx, teamSlug) ret := make([]*Valkey, 0) for _, ref := range references { for _, d := range all { - if d.Obj.FullyQualifiedName() == instanceNamer(teamSlug, ref.Instance) { - ret = append(ret, d.Obj) + if d.FullyQualifiedName() == instanceNamer(teamSlug, ref.Instance) || d.FullyQualifiedName() == ref.Instance { + ret = append(ret, d) } } } From 93198c527b72cb3263f123b541a495a027f872f4 Mon Sep 17 00:00:00 2001 From: Thomas Krampl <85170275+thokra-nav@users.noreply.github.com> Date: Fri, 13 Feb 2026 11:28:40 +0100 Subject: [PATCH 06/17] Pgrator opensearch (#335) * Make claude do what I did with valkey for opensearch * Fix checks * Fix missing fix from other branch * Change delete logic for OpenSearch instances Co-Authored-By: Trong Huu Nguyen * Use correct client for CUD operations Co-authored-by: Trong Huu Nguyen * Use Get function to reduce boilerplate Co-authored-by: Trong Huu Nguyen * Remove unused arg --------- Co-authored-by: Trong Huu Nguyen --- .../someteamname/opensearch_noversion.yaml | 46 +- integration_tests/opensearch_crud.lua | 260 ++--------- internal/cmd/api/http.go | 2 +- internal/kubernetes/fake/fake.go | 3 +- internal/kubernetes/watcher/watcher.go | 2 +- internal/kubernetes/watchers/watchers.go | 3 + internal/persistence/opensearch/client.go | 5 +- internal/persistence/opensearch/dataloader.go | 47 +- internal/persistence/opensearch/models.go | 55 ++- internal/persistence/opensearch/queries.go | 417 +++++++++--------- 10 files changed, 354 insertions(+), 486 deletions(-) diff --git a/integration_tests/k8s_resources/opensearch_crud/dev/someteamname/opensearch_noversion.yaml b/integration_tests/k8s_resources/opensearch_crud/dev/someteamname/opensearch_noversion.yaml index b1ceebab6..e0c455ecd 100644 --- a/integration_tests/k8s_resources/opensearch_crud/dev/someteamname/opensearch_noversion.yaml +++ b/integration_tests/k8s_resources/opensearch_crud/dev/someteamname/opensearch_noversion.yaml @@ -1,44 +1,12 @@ -apiVersion: aiven.io/v1alpha1 +apiVersion: nais.io/v1 kind: OpenSearch metadata: - annotations: - controllers.aiven.io/generation-was-processed: "2" - controllers.aiven.io/instance-is-running: "true" - nais.io/created_by: aiven-iac-migration - creationTimestamp: "2023-11-08T10:35:59Z" - finalizers: - - finalizers.aiven.io/delete-remote-resource - generation: 2 labels: - team: teampam nais.io/managed-by: console - name: opensearch-someteamname-noversion - namespace: teampam - resourceVersion: "3990043290" - uid: 2a8d4d8a-2bf4-4b2f-99fc-814f6a937ecd + name: noversion + namespace: someteamname spec: - cloudName: google-europe-north1 - connInfoSecretTarget: - name: "" - disk_space: 525G - plan: hobbyist - project: nav-prod - projectVpcId: fff21e17-95d5-408b-8df5-15aacf38f5de - tags: - environment: prod - team: teampam - tenant: nav - terminationProtection: true -status: - conditions: - - lastTransitionTime: "2023-11-08T10:36:06Z" - message: Instance was created or update on Aiven side - reason: Updated - status: "True" - type: Initialized - - lastTransitionTime: "2024-01-10T09:40:58Z" - message: Instance is running on Aiven side - reason: CheckRunning - status: "True" - type: Running - state: RUNNING + tier: SingleNode + memory: "2GB" + version: "2" + storageGB: 16 diff --git a/integration_tests/opensearch_crud.lua b/integration_tests/opensearch_crud.lua index e54f2bb34..10a6c19f0 100644 --- a/integration_tests/opensearch_crud.lua +++ b/integration_tests/opensearch_crud.lua @@ -137,7 +137,7 @@ Test.gql("Create opensearch as team member with existing name", function(t) errors = { { locations = NotNull(), - message = "Resource already exists.", + message = "OpenSearch with the name \"not-managed\" already exists, but are not yet managed through Console.", path = { "createOpenSearch", }, @@ -262,73 +262,26 @@ Test.gql("Create opensearch with invalid storage capacity increment", function(t end) Test.k8s("Validate OpenSearch resource", function(t) - local resourceName = string.format("opensearch-%s-foobar", mainTeam:slug()) - - t.check("aiven.io/v1alpha1", "opensearches", "dev", mainTeam:slug(), resourceName, { - apiVersion = "aiven.io/v1alpha1", + t.check("nais.io/v1", "opensearches", "dev", mainTeam:slug(), "foobar", { + apiVersion = "nais.io/v1", kind = "OpenSearch", metadata = { - name = resourceName, - namespace = mainTeam:slug(), annotations = { ["console.nais.io/last-modified-at"] = NotNull(), - ["console.nais.io/last-modified-by"] = user:email(), + ["console.nais.io/last-modified-by"] = "user@usersen.com", }, labels = { ["app.kubernetes.io/managed-by"] = "console", ["nais.io/managed-by"] = "console", }, + name = "foobar", + namespace = "someteamname", }, spec = { - project = "aiven-dev", - projectVpcId = "aiven-vpc", - plan = "startup-16", - cloudName = "google-europe-north1", - disk_space = "350G", - terminationProtection = true, - tags = { - environment = "dev", - team = mainTeam:slug(), - tenant = "some-tenant", - }, - userConfig = { - opensearch_version = "2", - }, - }, - }) -end) - -Test.k8s("Validate serviceintegration", function(t) - local resourceName = string.format("opensearch-%s-foobar", mainTeam:slug()) - - t.check("aiven.io/v1alpha1", "serviceintegrations", "dev", mainTeam:slug(), resourceName, { - apiVersion = "aiven.io/v1alpha1", - kind = "ServiceIntegration", - metadata = { - name = resourceName, - namespace = mainTeam:slug(), - annotations = { - ["console.nais.io/last-modified-at"] = NotNull(), - ["console.nais.io/last-modified-by"] = user:email(), - }, - labels = { - ["app.kubernetes.io/managed-by"] = "console", - ["nais.io/managed-by"] = "console", - }, - ownerReferences = { - { - apiVersion = "aiven.io/v1alpha1", - kind = "OpenSearch", - name = resourceName, - uid = NotNull(), - }, - }, - }, - spec = { - project = "aiven-dev", - destinationEndpointId = "endpoint-id", - integrationType = "prometheus", - sourceServiceName = resourceName, + memory = "16GB", + tier = "SingleNode", + version = "2", + storageGB = NotNull(), }, }) end) @@ -367,73 +320,26 @@ Test.gql("Create opensearch with tier and memory equivalent to hobbyist plan", f end) Test.k8s("Validate hobbyist OpenSearch resource", function(t) - local resourceName = string.format("opensearch-%s-foobar-hobbyist", mainTeam:slug()) - - t.check("aiven.io/v1alpha1", "opensearches", "dev", mainTeam:slug(), resourceName, { - apiVersion = "aiven.io/v1alpha1", + t.check("nais.io/v1", "opensearches", "dev", mainTeam:slug(), "foobar-hobbyist", { + apiVersion = "nais.io/v1", kind = "OpenSearch", metadata = { - name = resourceName, - namespace = mainTeam:slug(), - annotations = { - ["console.nais.io/last-modified-at"] = NotNull(), - ["console.nais.io/last-modified-by"] = user:email(), - }, - labels = { - ["app.kubernetes.io/managed-by"] = "console", - ["nais.io/managed-by"] = "console", - }, - }, - spec = { - project = "aiven-dev", - projectVpcId = "aiven-vpc", - plan = "hobbyist", - cloudName = "google-europe-north1", - disk_space = "16G", - terminationProtection = true, - tags = { - environment = "dev", - team = mainTeam:slug(), - tenant = "some-tenant", - }, - userConfig = { - opensearch_version = "2", - }, - }, - }) -end) - -Test.k8s("Validate hobbyist serviceintegration", function(t) - local resourceName = string.format("opensearch-%s-foobar-hobbyist", mainTeam:slug()) - - t.check("aiven.io/v1alpha1", "serviceintegrations", "dev", mainTeam:slug(), resourceName, { - apiVersion = "aiven.io/v1alpha1", - kind = "ServiceIntegration", - metadata = { - name = resourceName, - namespace = mainTeam:slug(), annotations = { ["console.nais.io/last-modified-at"] = NotNull(), - ["console.nais.io/last-modified-by"] = user:email(), + ["console.nais.io/last-modified-by"] = "user@usersen.com", }, labels = { ["app.kubernetes.io/managed-by"] = "console", ["nais.io/managed-by"] = "console", }, - ownerReferences = { - { - apiVersion = "aiven.io/v1alpha1", - kind = "OpenSearch", - name = resourceName, - uid = NotNull(), - }, - }, + name = "foobar-hobbyist", + namespace = "someteamname", }, spec = { - project = "aiven-dev", - destinationEndpointId = "endpoint-id", - integrationType = "prometheus", - sourceServiceName = resourceName, + memory = "2GB", + tier = "SingleNode", + version = "2", + storageGB = NotNull(), }, }) end) @@ -544,38 +450,26 @@ Test.gql("Update OpenSearch as team-member", function(t) end) Test.k8s("Validate OpenSearch resource after update", function(t) - local resourceName = string.format("opensearch-%s-foobar", mainTeam:slug()) - - t.check("aiven.io/v1alpha1", "opensearches", "dev", mainTeam:slug(), resourceName, { - apiVersion = "aiven.io/v1alpha1", + t.check("nais.io/v1", "opensearches", "dev", mainTeam:slug(), "foobar", { + apiVersion = "nais.io/v1", kind = "OpenSearch", metadata = { - name = resourceName, - namespace = mainTeam:slug(), annotations = { ["console.nais.io/last-modified-at"] = NotNull(), - ["console.nais.io/last-modified-by"] = user:email(), + ["console.nais.io/last-modified-by"] = "user@usersen.com", }, labels = { ["app.kubernetes.io/managed-by"] = "console", ["nais.io/managed-by"] = "console", }, + name = "foobar", + namespace = "someteamname", }, spec = { - project = "aiven-dev", - projectVpcId = "aiven-vpc", - plan = "business-4", - cloudName = "google-europe-north1", - disk_space = "1020G", - terminationProtection = true, - tags = { - environment = "dev", - team = mainTeam:slug(), - tenant = "some-tenant", - }, - userConfig = { - opensearch_version = "2", - }, + memory = "4GB", + tier = "HighAvailability", + version = "2", + storageGB = NotNull(), }, }) end) @@ -670,7 +564,7 @@ Test.gql("Downgrade OpenSearch as team-member", function(t) } end) -Test.gql("Downgrade OpenSearch without explicit version set", function(t) +Test.gql("Downgrade OpenSearch noversion instance", function(t) t.addHeader("x-user-email", user:email()) t.query [[ mutation UpdateOpenSearch { @@ -706,42 +600,6 @@ Test.gql("Downgrade OpenSearch without explicit version set", function(t) } end) -Test.gql("Update non-console managed OpenSearch as team-member", function(t) - t.addHeader("x-user-email", user:email()) - t.query [[ - mutation UpdateOpenSearch { - updateOpenSearch( - input: { - name: "not-managed" - environmentName: "dev" - teamSlug: "someteamname" - tier: HIGH_AVAILABILITY - memory: GB_4 - version: V2 - storageGB: 240 - } - ) { - openSearch { - name - } - } - } - ]] - - t.check { - errors = { - { - locations = NotNull(), - message = "OpenSearch someteamname/not-managed is not managed by Console", - path = { - "updateOpenSearch", - }, - }, - }, - data = Null, - } -end) - Test.gql("Update OpenSearch with tier and memory equivalent to hobbyist plan", function(t) t.addHeader("x-user-email", user:email()) t.query [[ @@ -776,38 +634,26 @@ Test.gql("Update OpenSearch with tier and memory equivalent to hobbyist plan", f end) Test.k8s("Validate hobbyist OpenSearch resource after update", function(t) - local resourceName = string.format("opensearch-%s-foobar", mainTeam:slug()) - - t.check("aiven.io/v1alpha1", "opensearches", "dev", mainTeam:slug(), resourceName, { - apiVersion = "aiven.io/v1alpha1", + t.check("nais.io/v1", "opensearches", "dev", mainTeam:slug(), "foobar", { + apiVersion = "nais.io/v1", kind = "OpenSearch", metadata = { - name = resourceName, - namespace = mainTeam:slug(), annotations = { ["console.nais.io/last-modified-at"] = NotNull(), - ["console.nais.io/last-modified-by"] = user:email(), + ["console.nais.io/last-modified-by"] = "user@usersen.com", }, labels = { ["app.kubernetes.io/managed-by"] = "console", ["nais.io/managed-by"] = "console", }, + name = "foobar", + namespace = "someteamname", }, spec = { - project = "aiven-dev", - projectVpcId = "aiven-vpc", - plan = "hobbyist", - cloudName = "google-europe-north1", - disk_space = "16G", - terminationProtection = true, - tags = { - environment = "dev", - team = mainTeam:slug(), - tenant = "some-tenant", - }, - userConfig = { - opensearch_version = "2", - }, + memory = "2GB", + tier = "SingleNode", + version = "2", + storageGB = NotNull(), }, }) end) @@ -1106,33 +952,3 @@ Test.gql("Verify activity log for opensearch operations", function(t) }, } end) - -Test.gql("Delete non-managed opensearch as team-member", function(t) - t.addHeader("x-user-email", user:email()) - t.query [[ - mutation DeleteOpenSearch { - deleteOpenSearch( - input: { - name: "not-managed" - environmentName: "dev" - teamSlug: "someteamname" - } - ) { - openSearchDeleted - } - } - ]] - - t.check { - errors = { - { - locations = NotNull(), - message = "OpenSearch someteamname/not-managed is not managed by Console", - path = { - "deleteOpenSearch", - }, - }, - }, - data = Null, - } -end) diff --git a/internal/cmd/api/http.go b/internal/cmd/api/http.go index d6d9307d6..f2f299e60 100644 --- a/internal/cmd/api/http.go +++ b/internal/cmd/api/http.go @@ -348,7 +348,7 @@ func ConfigureGraph( ctx = config.NewLoaderContext(ctx, watchers.ConfigWatcher, log) ctx = instancegroup.NewLoaderContext(ctx, watchers.ReplicaSetWatcher, watchers.PodWatcher, watchers.AppWatcher, dynamicClients, log) ctx = aiven.NewLoaderContext(ctx, aivenProjects) - ctx = opensearch.NewLoaderContext(ctx, tenantName, watchers.OpenSearchWatcher, aivenClient, log) + ctx = opensearch.NewLoaderContext(ctx, tenantName, watchers.OpenSearchWatcher, watchers.NaisOpenSearchWatcher, aivenClient, log) ctx = valkey.NewLoaderContext(ctx, tenantName, watchers.ValkeyWatcher, watchers.NaisValkeyWatcher, aivenClient) ctx = price.NewLoaderContext(ctx, priceRetriever, log) ctx = utilization.NewLoaderContext(ctx, prometheusClient, log) diff --git a/internal/kubernetes/fake/fake.go b/internal/kubernetes/fake/fake.go index 626c0c0e0..bb0e3848a 100644 --- a/internal/kubernetes/fake/fake.go +++ b/internal/kubernetes/fake/fake.go @@ -217,8 +217,9 @@ func NewDynamicClient(scheme *runtime.Scheme) *dynfake.FakeDynamicClient { unleash_nais_io_v1.GroupVersion.WithResource("unleashes"): "UnleashList", unleash_nais_io_v1.GroupVersion.WithResource("remoteunleashes"): "RemoteUnleashList", data_nais_io_v1.GroupVersion.WithResource("postgres"): "PostgresList", - mapperatorv1.GroupVersion.WithResource("valkeys"): "ValkeyList", nais_io_v1alpha1.GroupVersion.WithResource("tunnels"): "TunnelList", + mapperatorv1.GroupVersion.WithResource("valkeys"): "ValkeyList", + mapperatorv1.GroupVersion.WithResource("opensearches"): "OpenSearchList", }) client.PrependReactor("patch", "*", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) { diff --git a/internal/kubernetes/watcher/watcher.go b/internal/kubernetes/watcher/watcher.go index 2f1bd0dd0..8d53a6222 100644 --- a/internal/kubernetes/watcher/watcher.go +++ b/internal/kubernetes/watcher/watcher.go @@ -309,7 +309,7 @@ func (w *Watcher[T]) ImpersonatedClientWithNamespace(ctx context.Context, cluste return c.Namespace(namespace), nil } -func (w *Watcher[T]) SystemAuthenticatedClient(ctx context.Context, cluster string, opts ...ImpersonatedClientOption) (dynamic.NamespaceableResourceInterface, error) { +func (w *Watcher[T]) SystemAuthenticatedClient(ctx context.Context, cluster string) (dynamic.NamespaceableResourceInterface, error) { for _, watcher := range w.watchers { if watcher.cluster == cluster { return watcher.SystemAuthenticatedClient(ctx, opts...) diff --git a/internal/kubernetes/watchers/watchers.go b/internal/kubernetes/watchers/watchers.go index 9f882e35b..cfda14f0b 100644 --- a/internal/kubernetes/watchers/watchers.go +++ b/internal/kubernetes/watchers/watchers.go @@ -35,6 +35,7 @@ type ( BqWatcher = watcher.Watcher[*bigquery.BigQueryDataset] ValkeyWatcher = watcher.Watcher[*valkey.Valkey] OpenSearchWatcher = watcher.Watcher[*opensearch.OpenSearch] + NaisOpenSearchWatcher = watcher.Watcher[*opensearch.OpenSearch] BucketWatcher = watcher.Watcher[*bucket.Bucket] SqlDatabaseWatcher = watcher.Watcher[*sqlinstance.SQLDatabase] SqlInstanceWatcher = watcher.Watcher[*sqlinstance.SQLInstance] @@ -58,6 +59,7 @@ type Watchers struct { BqWatcher *BqWatcher ValkeyWatcher *ValkeyWatcher OpenSearchWatcher *OpenSearchWatcher + NaisOpenSearchWatcher *NaisOpenSearchWatcher BucketWatcher *BucketWatcher SqlDatabaseWatcher *SqlDatabaseWatcher SqlInstanceWatcher *SqlInstanceWatcher @@ -86,6 +88,7 @@ func SetupWatchers( BqWatcher: bigquery.NewWatcher(ctx, watcherMgr), ValkeyWatcher: valkey.NewWatcher(ctx, watcherMgr), OpenSearchWatcher: opensearch.NewWatcher(ctx, watcherMgr), + NaisOpenSearchWatcher: opensearch.NewNaisOpenSearchWatcher(ctx, watcherMgr), BucketWatcher: bucket.NewWatcher(ctx, watcherMgr), SqlDatabaseWatcher: sqlinstance.NewDatabaseWatcher(ctx, watcherMgr), SqlInstanceWatcher: sqlinstance.NewInstanceWatcher(ctx, watcherMgr), diff --git a/internal/persistence/opensearch/client.go b/internal/persistence/opensearch/client.go index 344baf330..9a0e0b4b2 100644 --- a/internal/persistence/opensearch/client.go +++ b/internal/persistence/opensearch/client.go @@ -3,16 +3,13 @@ package opensearch import ( "context" - "github.com/nais/api/internal/kubernetes/watcher" "github.com/nais/api/internal/slug" "github.com/nais/api/internal/workload" "github.com/nais/api/internal/workload/application" "github.com/nais/api/internal/workload/job" ) -type client struct { - watcher *watcher.Watcher[*OpenSearch] -} +type client struct{} func instanceNamer(teamSlug slug.Slug, instanceName string) string { return NamePrefix(teamSlug) + instanceName diff --git a/internal/persistence/opensearch/dataloader.go b/internal/persistence/opensearch/dataloader.go index 474d6deba..64d6b54d0 100644 --- a/internal/persistence/opensearch/dataloader.go +++ b/internal/persistence/opensearch/dataloader.go @@ -4,13 +4,17 @@ import ( "context" "github.com/nais/api/internal/graph/loader" + "github.com/nais/api/internal/kubernetes" "github.com/nais/api/internal/kubernetes/watcher" + "github.com/nais/api/internal/slug" "github.com/nais/api/internal/thirdparty/aiven" + naiscrd "github.com/nais/pgrator/pkg/api/v1" "github.com/sirupsen/logrus" "github.com/sourcegraph/conc/pool" "github.com/vikstrous/dataloadgen" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" ) type ctxKey int @@ -22,8 +26,8 @@ type AivenDataLoaderKey struct { ServiceName string } -func NewLoaderContext(ctx context.Context, tenantName string, watcher *watcher.Watcher[*OpenSearch], aivenClient aiven.AivenClient, logger logrus.FieldLogger) context.Context { - return context.WithValue(ctx, loadersKey, newLoaders(tenantName, watcher, aivenClient, logger)) +func NewLoaderContext(ctx context.Context, tenantName string, openSearchWatcher, naisOpenSearchWatcher *watcher.Watcher[*OpenSearch], aivenClient aiven.AivenClient, logger logrus.FieldLogger) context.Context { + return context.WithValue(ctx, loadersKey, newLoaders(tenantName, openSearchWatcher, naisOpenSearchWatcher, aivenClient, logger)) } func NewWatcher(ctx context.Context, mgr *watcher.Manager) *watcher.Watcher[*OpenSearch] { @@ -42,6 +46,26 @@ func NewWatcher(ctx context.Context, mgr *watcher.Manager) *watcher.Watcher[*Ope return w } +func NewNaisOpenSearchWatcher(ctx context.Context, mgr *watcher.Manager) *watcher.Watcher[*OpenSearch] { + w := watcher.Watch(mgr, &OpenSearch{}, watcher.WithConverter(func(o *unstructured.Unstructured, environmentName string) (obj any, ok bool) { + v, err := kubernetes.ToConcrete[naiscrd.OpenSearch](o) + if err != nil { + return nil, false + } + ret, err := toOpenSearchFromNais(v, environmentName) + if err != nil { + return nil, false + } + return ret, true + }), watcher.WithGVR(schema.GroupVersionResource{ + Group: "nais.io", + Version: "v1", + Resource: "opensearches", + })) + w.Start(ctx) + return w +} + func fromContext(ctx context.Context) *loaders { return ctx.Value(loadersKey).(*loaders) } @@ -49,27 +73,38 @@ func fromContext(ctx context.Context) *loaders { type loaders struct { client *client watcher *watcher.Watcher[*OpenSearch] + naisWatcher *watcher.Watcher[*OpenSearch] versionLoader *dataloadgen.Loader[*AivenDataLoaderKey, string] tenantName string aivenClient aiven.AivenClient } -func newLoaders(tenantName string, watcher *watcher.Watcher[*OpenSearch], aivenClient aiven.AivenClient, logger logrus.FieldLogger) *loaders { - client := &client{ - watcher: watcher, - } +func newLoaders(tenantName string, watcher, naisOpenSearchWatcher *watcher.Watcher[*OpenSearch], aivenClient aiven.AivenClient, logger logrus.FieldLogger) *loaders { + client := &client{} versionLoader := &dataloader{aivenClient: aivenClient, log: logger} return &loaders{ client: client, watcher: watcher, + naisWatcher: naisOpenSearchWatcher, tenantName: tenantName, versionLoader: dataloadgen.NewLoader(versionLoader.getVersions, loader.DefaultDataLoaderOptions...), aivenClient: aivenClient, } } +func newK8sClient(ctx context.Context, environmentName string, teamSlug slug.Slug) (dynamic.ResourceInterface, error) { + sysClient, err := fromContext(ctx).naisWatcher.SystemAuthenticatedClient( + ctx, + environmentName, + ) + if err != nil { + return nil, err + } + return sysClient.Namespace(teamSlug.String()), nil +} + type dataloader struct { aivenClient aiven.AivenClient log logrus.FieldLogger diff --git a/internal/persistence/opensearch/models.go b/internal/persistence/opensearch/models.go index 0c1dce0db..40b4d3ba1 100644 --- a/internal/persistence/opensearch/models.go +++ b/internal/persistence/opensearch/models.go @@ -19,6 +19,8 @@ import ( "github.com/nais/api/internal/validate" "github.com/nais/api/internal/workload" aiven_io_v1alpha1 "github.com/nais/liberator/pkg/apis/aiven.io/v1alpha1" + "github.com/nais/pgrator/pkg/api" + naiscrd "github.com/nais/pgrator/pkg/api/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -34,17 +36,17 @@ type ( ) type OpenSearch struct { - Name string `json:"name"` - Status *OpenSearchStatus `json:"status"` - TerminationProtection bool `json:"terminationProtection"` - Tier OpenSearchTier `json:"tier"` - Memory OpenSearchMemory `json:"memory"` - StorageGB StorageGB `json:"storageGB"` - TeamSlug slug.Slug `json:"-"` - EnvironmentName string `json:"-"` - WorkloadReference *workload.Reference `json:"-"` - AivenProject string `json:"-"` - MajorVersion OpenSearchMajorVersion `json:"-"` + Name string `json:"name"` + Status *naiscrd.OpenSearchStatus `json:"status"` + TerminationProtection bool `json:"terminationProtection"` + Tier OpenSearchTier `json:"tier"` + Memory OpenSearchMemory `json:"memory"` + StorageGB StorageGB `json:"storageGB"` + TeamSlug slug.Slug `json:"-"` + EnvironmentName string `json:"-"` + WorkloadReference *workload.Reference `json:"-"` + AivenProject string `json:"-"` + MajorVersion OpenSearchMajorVersion `json:"-"` } func (OpenSearch) IsPersistence() {} @@ -160,6 +162,14 @@ func toOpenSearch(u *unstructured.Unstructured, envName string) (*OpenSearch, er return nil, fmt.Errorf("converting to OpenSearch: %w", err) } + if len(obj.GetOwnerReferences()) > 0 { + for _, ownerRef := range obj.GetOwnerReferences() { + if ownerRef.Kind == "OpenSearch" { + return nil, fmt.Errorf("skipping OpenSearch %s in namespace %s because it has an owner reference", obj.GetName(), obj.GetNamespace()) + } + } + } + // Liberator doesn't contain this field, so we read it directly from the unstructured object terminationProtection, _, _ := unstructured.NestedBool(u.Object, specTerminationProtection...) @@ -194,9 +204,10 @@ func toOpenSearch(u *unstructured.Unstructured, envName string) (*OpenSearch, er Name: name, EnvironmentName: envName, TerminationProtection: terminationProtection, - Status: &OpenSearchStatus{ - Conditions: obj.Status.Conditions, - State: obj.Status.State, + Status: &naiscrd.OpenSearchStatus{ + BaseStatus: api.BaseStatus{ + Conditions: obj.Status.Conditions, + }, }, TeamSlug: slug.Slug(obj.GetNamespace()), WorkloadReference: workload.ReferenceFromOwnerReferences(obj.GetOwnerReferences()), @@ -208,6 +219,22 @@ func toOpenSearch(u *unstructured.Unstructured, envName string) (*OpenSearch, er }, nil } +func toOpenSearchFromNais(o *naiscrd.OpenSearch, envName string) (*OpenSearch, error) { + majorVersion := fromMapperatorVersion(o.Spec.Version) + + return &OpenSearch{ + Name: o.Name, + EnvironmentName: envName, + Status: o.Status, + TeamSlug: slug.Slug(o.Namespace), + WorkloadReference: workload.ReferenceFromOwnerReferences(o.OwnerReferences), + Tier: fromMapperatorTier(o.Spec.Tier), + Memory: fromMapperatorMemory(o.Spec.Memory), + MajorVersion: majorVersion, + StorageGB: StorageGB(o.Spec.StorageGB), + }, nil +} + type TeamInventoryCountOpenSearches struct { Total int } diff --git a/internal/persistence/opensearch/queries.go b/internal/persistence/opensearch/queries.go index 6d87e2ece..a132db683 100644 --- a/internal/persistence/opensearch/queries.go +++ b/internal/persistence/opensearch/queries.go @@ -2,6 +2,7 @@ package opensearch import ( "context" + "errors" "fmt" "strconv" "strings" @@ -19,14 +20,14 @@ import ( "github.com/nais/api/internal/slug" "github.com/nais/api/internal/thirdparty/aiven" nais_io_v1 "github.com/nais/liberator/pkg/apis/nais.io/v1" + "github.com/nais/pgrator/pkg/api" + naiscrd "github.com/nais/pgrator/pkg/api/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) var ( specDiskSpace = []string{"spec", "disk_space"} - specPlan = []string{"spec", "plan"} specTerminationProtection = []string{"spec", "terminationProtection"} specOpenSearchVersion = []string{"spec", "userConfig", "opensearch_version"} ) @@ -41,11 +42,15 @@ func GetByIdent(ctx context.Context, id ident.Ident) (*OpenSearch, error) { } func Get(ctx context.Context, teamSlug slug.Slug, environment, name string) (*OpenSearch, error) { - prefix := instanceNamer(teamSlug, "") - if !strings.HasPrefix(name, prefix) { - name = instanceNamer(teamSlug, name) + v, err := fromContext(ctx).naisWatcher.Get(environment, teamSlug.String(), name) + if errors.Is(err, &watcher.ErrorNotFound{}) { + prefix := instanceNamer(teamSlug, "") + if !strings.HasPrefix(name, prefix) { + name = instanceNamer(teamSlug, name) + } + v, err = fromContext(ctx).watcher.Get(environment, teamSlug.String(), name) } - return fromContext(ctx).client.watcher.Get(environment, teamSlug.String(), name) + return v, err } func State(ctx context.Context, os *OpenSearch) (OpenSearchState, error) { @@ -82,7 +87,9 @@ func ListForTeam(ctx context.Context, teamSlug slug.Slug, page *pagination.Pagin } func ListAllForTeam(ctx context.Context, teamSlug slug.Slug) []*OpenSearch { - all := fromContext(ctx).client.watcher.GetByNamespace(teamSlug.String(), watcher.WithoutDeleted()) + all := fromContext(ctx).watcher.GetByNamespace(teamSlug.String(), watcher.WithoutDeleted()) + allNais := fromContext(ctx).naisWatcher.GetByNamespace(teamSlug.String(), watcher.WithoutDeleted()) + all = append(all, allNais...) return watcher.Objects(all) } @@ -150,7 +157,7 @@ func GetForWorkload(ctx context.Context, teamSlug slug.Slug, environment string, return nil, nil } - return fromContext(ctx).client.watcher.Get(environment, teamSlug.String(), instanceNamer(teamSlug, reference.Instance)) + return Get(ctx, teamSlug, environment, reference.Instance) } func orderOpenSearch(ctx context.Context, ret []*OpenSearch, orderBy *OpenSearchOrder) { @@ -169,63 +176,36 @@ func Create(ctx context.Context, input CreateOpenSearchInput) (*CreateOpenSearch return nil, err } - namespace := input.TeamSlug.String() - client, err := fromContext(ctx).watcher.ImpersonatedClientWithNamespace(ctx, input.EnvironmentName, namespace) - if err != nil { - return nil, err - } - - machine, err := machineTypeFromTierAndMemory(input.Tier, input.Memory) + client, err := newK8sClient(ctx, input.EnvironmentName, input.TeamSlug) if err != nil { return nil, err } - res := &unstructured.Unstructured{} - res.SetAPIVersion("aiven.io/v1alpha1") - res.SetKind("OpenSearch") - res.SetName(instanceNamer(input.TeamSlug, input.Name)) - res.SetNamespace(namespace) - res.SetAnnotations(kubernetes.WithCommonAnnotations(nil, authz.ActorFromContext(ctx).User.Identity())) - kubernetes.SetManagedByConsoleLabel(res) - - aivenProject, err := aiven.GetProject(ctx, input.EnvironmentName) - if err != nil { - return nil, err - } - version, err := input.Version.ToAivenString() - if err != nil { - return nil, err + // Ensure there's no existing Aiven OpenSearch with the same name + // This can be removed when we manage all opensearches through Console + _, err = fromContext(ctx).watcher.Get(input.EnvironmentName, input.TeamSlug.String(), instanceNamer(input.TeamSlug, input.Name)) + if !errors.Is(err, &watcher.ErrorNotFound{}) { + return nil, apierror.Errorf("OpenSearch with the name %q already exists, but are not yet managed through Console.", input.Name) } - res.Object["spec"] = map[string]any{ - "cloudName": "google-europe-north1", - "plan": machine.AivenPlan, - "project": aivenProject.ID, - "projectVpcId": aivenProject.VPC, - "disk_space": input.StorageGB.ToAivenString(), - "terminationProtection": true, - "tags": map[string]any{ - "environment": input.EnvironmentName, - "team": namespace, - "tenant": fromContext(ctx).tenantName, + res := &naiscrd.OpenSearch{ + TypeMeta: metav1.TypeMeta{ + Kind: "OpenSearch", + APIVersion: "nais.io/v1", }, - "userConfig": map[string]any{ - "opensearch_version": version, + ObjectMeta: metav1.ObjectMeta{ + Name: input.Name, + Namespace: input.TeamSlug.String(), + }, + Spec: naiscrd.OpenSearchSpec{ + Tier: toMapperatorTier(input.Tier), + Memory: toMapperatorMemory(input.Memory), + Version: toMapperatorVersion(input.Version), + StorageGB: int(input.StorageGB), }, } - - ret, err := client.Create(ctx, res, metav1.CreateOptions{}) - if err != nil { - if k8serrors.IsAlreadyExists(err) { - return nil, apierror.ErrAlreadyExists - } - return nil, err - } - - err = aiven.UpsertPrometheusServiceIntegration(ctx, fromContext(ctx).watcher, ret, aivenProject, input.EnvironmentName) - if err != nil { - return nil, fmt.Errorf("creating Prometheus service integration: %w", err) - } + res.SetAnnotations(kubernetes.WithCommonAnnotations(nil, authz.ActorFromContext(ctx).User.Identity())) + kubernetes.SetManagedByConsoleLabel(res) err = activitylog.Create(ctx, activitylog.CreateInput{ Action: activitylog.ActivityLogEntryActionCreated, @@ -239,7 +219,19 @@ func Create(ctx context.Context, input CreateOpenSearchInput) (*CreateOpenSearch return nil, err } - os, err := toOpenSearch(ret, input.EnvironmentName) + obj, err := kubernetes.ToUnstructured(res) + if err != nil { + return nil, err + } + + if _, err = client.Create(ctx, obj, metav1.CreateOptions{}); err != nil { + if k8serrors.IsAlreadyExists(err) { + return nil, apierror.ErrAlreadyExists + } + return nil, err + } + + os, err := toOpenSearchFromNais(res, input.EnvironmentName) if err != nil { return nil, err } @@ -254,41 +246,38 @@ func Update(ctx context.Context, input UpdateOpenSearchInput) (*UpdateOpenSearch return nil, err } - client, err := fromContext(ctx).watcher.ImpersonatedClientWithNamespace(ctx, input.EnvironmentName, input.TeamSlug.String()) + client, err := newK8sClient(ctx, input.EnvironmentName, input.TeamSlug) if err != nil { return nil, err } - openSearch, err := client.Get(ctx, instanceNamer(input.TeamSlug, input.Name), metav1.GetOptions{}) + openSearch, err := client.Get(ctx, input.Name, metav1.GetOptions{}) if err != nil { return nil, err } - if !kubernetes.HasManagedByConsoleLabel(openSearch) { - return nil, apierror.Errorf("OpenSearch %s/%s is not managed by Console", input.TeamSlug, input.Name) - } - changes := make([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, 0) - - res, err := updatePlan(openSearch, input) + concreteOpenSearch, err := kubernetes.ToConcrete[naiscrd.OpenSearch](openSearch) if err != nil { return nil, err } - changes = append(changes, res...) - res, err = updateVersion(ctx, openSearch, input) - if err != nil { - return nil, err + changes := make([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, 0) + updateFuncs := []func(*naiscrd.OpenSearch, UpdateOpenSearchInput) ([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, error){ + updateTier, + updateMemory, + updateVersion, + updateStorage, } - changes = append(changes, res...) - res, err = updateStorage(openSearch, input) - if err != nil { - return nil, err + for _, f := range updateFuncs { + res, err := f(concreteOpenSearch, input) + if err != nil { + return nil, err + } + changes = append(changes, res...) } - changes = append(changes, res...) if len(changes) == 0 { - // No changes to update os, err := toOpenSearch(openSearch, input.EnvironmentName) if err != nil { return nil, err @@ -299,21 +288,16 @@ func Update(ctx context.Context, input UpdateOpenSearchInput) (*UpdateOpenSearch }, nil } - openSearch.SetAnnotations(kubernetes.WithCommonAnnotations(openSearch.GetAnnotations(), authz.ActorFromContext(ctx).User.Identity())) - - ret, err := client.Update(ctx, openSearch, metav1.UpdateOptions{}) + obj, err := kubernetes.ToUnstructured(concreteOpenSearch) if err != nil { return nil, err } - aivenProject, err := aiven.GetProject(ctx, input.EnvironmentName) - if err != nil { - return nil, err - } + obj.SetAnnotations(kubernetes.WithCommonAnnotations(obj.GetAnnotations(), authz.ActorFromContext(ctx).User.Identity())) - err = aiven.UpsertPrometheusServiceIntegration(ctx, fromContext(ctx).watcher, ret, aivenProject, input.EnvironmentName) + ret, err := client.Update(ctx, obj, metav1.UpdateOptions{}) if err != nil { - return nil, fmt.Errorf("creating Prometheus service integration: %w", err) + return nil, err } err = activitylog.Create(ctx, activitylog.CreateInput{ @@ -331,13 +315,18 @@ func Update(ctx context.Context, input UpdateOpenSearchInput) (*UpdateOpenSearch return nil, err } - os, err := toOpenSearch(ret, input.EnvironmentName) + retOpenSearch, err := kubernetes.ToConcrete[naiscrd.OpenSearch](ret) + if err != nil { + return nil, err + } + + osUpdated, err := toOpenSearchFromNais(retOpenSearch, input.EnvironmentName) if err != nil { return nil, err } return &UpdateOpenSearchPayload{ - OpenSearch: os, + OpenSearch: osUpdated, }, nil } @@ -382,148 +371,91 @@ func CreateOpenSearchCredentials(ctx context.Context, input CreateOpenSearchCred return &CreateOpenSearchCredentialsPayload{Credentials: result.(*OpenSearchCredentials)}, nil } -func updatePlan(openSearch *unstructured.Unstructured, input UpdateOpenSearchInput) ([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, error) { +func updateTier(openSearch *naiscrd.OpenSearch, input UpdateOpenSearchInput) ([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, error) { changes := make([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, 0) - desired, err := machineTypeFromTierAndMemory(input.Tier, input.Memory) - if err != nil { - return nil, err - } - - oldPlan, found, err := unstructured.NestedString(openSearch.Object, specPlan...) - if err != nil { - return nil, err - } - if !found { - // .spec.plan is a required field - return nil, fmt.Errorf("missing .spec.plan in OpenSearch resource") - } - - if oldPlan == desired.AivenPlan { - return changes, nil - } - - oldMachine, err := machineTypeFromPlan(oldPlan) - if err != nil { - return nil, err - } - - if input.Tier != oldMachine.Tier { + origTier := fromMapperatorTier(openSearch.Spec.Tier) + if input.Tier != origTier { changes = append(changes, &OpenSearchUpdatedActivityLogEntryDataUpdatedField{ Field: "tier", - OldValue: new(oldMachine.Tier.String()), + OldValue: new(origTier.String()), NewValue: new(input.Tier.String()), }) } - if input.Memory != oldMachine.Memory { + openSearch.Spec.Tier = toMapperatorTier(input.Tier) + + return changes, nil +} + +func updateMemory(openSearch *naiscrd.OpenSearch, input UpdateOpenSearchInput) ([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, error) { + changes := make([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, 0) + + origMemory := fromMapperatorMemory(openSearch.Spec.Memory) + if input.Memory != origMemory { changes = append(changes, &OpenSearchUpdatedActivityLogEntryDataUpdatedField{ Field: "memory", - OldValue: new(oldMachine.Memory.String()), + OldValue: new(origMemory.String()), NewValue: new(input.Memory.String()), }) } - if err := unstructured.SetNestedField(openSearch.Object, desired.AivenPlan, specPlan...); err != nil { - return nil, err - } + openSearch.Spec.Memory = toMapperatorMemory(input.Memory) + return changes, nil } -func updateVersion(ctx context.Context, openSearch *unstructured.Unstructured, input UpdateOpenSearchInput) ([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, error) { - changes := make([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, 0) - - oldVersion, found, err := unstructured.NestedString(openSearch.Object, specOpenSearchVersion...) - if err != nil { - return nil, err - } - if !found { - os, err := toOpenSearch(openSearch, input.EnvironmentName) - if err != nil { - return nil, err - } - version, err := GetOpenSearchVersion(ctx, os) - if err != nil { - return nil, err - } +func updateVersion(openSearch *naiscrd.OpenSearch, input UpdateOpenSearchInput) ([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, error) { + origVersion := fromMapperatorVersion(openSearch.Spec.Version) - oldVersion = *version.Actual + if origVersion == input.Version { + return nil, nil } - oldMajorVersion, err := OpenSearchMajorVersionFromAivenString(oldVersion) - if err != nil { + if err := input.Version.ValidateUpgradePath(origVersion); err != nil { return nil, err } - if oldMajorVersion == input.Version { - return changes, nil - } + changes := make([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, 0) - if err := input.Version.ValidateUpgradePath(oldMajorVersion); err != nil { - return nil, err + var oldValue *string + if origVersion != "" { + oldValue = new(origVersion.String()) } changes = append(changes, &OpenSearchUpdatedActivityLogEntryDataUpdatedField{ - Field: "version", - OldValue: func() *string { - if found { - return new(oldVersion) - } - return nil - }(), + Field: "version", + OldValue: oldValue, NewValue: new(input.Version.String()), }) - version, err := input.Version.ToAivenString() - if err != nil { - return nil, err - } + openSearch.Spec.Version = toMapperatorVersion(input.Version) - if err := unstructured.SetNestedField(openSearch.Object, version, specOpenSearchVersion...); err != nil { - return nil, err - } return changes, nil } -func updateStorage(openSearch *unstructured.Unstructured, input UpdateOpenSearchInput) ([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, error) { - changes := make([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, 0) +func updateStorage(openSearch *naiscrd.OpenSearch, input UpdateOpenSearchInput) ([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, error) { + oldStorageGB := StorageGB(openSearch.Spec.StorageGB) - desired, err := machineTypeFromTierAndMemory(input.Tier, input.Memory) - if err != nil { - return nil, err + if oldStorageGB == input.StorageGB { + return nil, nil } - oldAivenDiskSpace, found, err := unstructured.NestedString(openSearch.Object, specDiskSpace...) - if err != nil { - return nil, err - } - // default to minimum storage capacity for the selected plan, in case the field is not set explicitly - oldStorageGB := desired.StorageMin - if found { - oldStorageGB, err = StorageGBFromAivenString(oldAivenDiskSpace) - if err != nil { - return nil, err - } - } + changes := make([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, 0) - if oldStorageGB == input.StorageGB { - return changes, nil + var oldValue *string + if oldStorageGB > 0 { + oldValue = new(oldStorageGB.String()) } changes = append(changes, &OpenSearchUpdatedActivityLogEntryDataUpdatedField{ - Field: "storageGB", - OldValue: func() *string { - if found { - return new(oldStorageGB.String()) - } - return nil - }(), + Field: "storageGB", + OldValue: oldValue, NewValue: new(input.StorageGB.String()), }) - if err := unstructured.SetNestedField(openSearch.Object, input.StorageGB.ToAivenString(), specDiskSpace...); err != nil { - return nil, err - } + openSearch.Spec.StorageGB = int(input.StorageGB) + return changes, nil } @@ -532,36 +464,35 @@ func Delete(ctx context.Context, input DeleteOpenSearchInput) (*DeleteOpenSearch return nil, err } - name := instanceNamer(input.TeamSlug, input.Name) - client, err := fromContext(ctx).watcher.ImpersonatedClientWithNamespace(ctx, input.EnvironmentName, input.TeamSlug.String()) + client, err := newK8sClient(ctx, input.EnvironmentName, input.TeamSlug) if err != nil { return nil, err } - os, err := client.Get(ctx, name, metav1.GetOptions{}) + openSearch, err := client.Get(ctx, input.Name, metav1.GetOptions{}) if err != nil { return nil, err } - if !kubernetes.HasManagedByConsoleLabel(os) { + if !kubernetes.HasManagedByConsoleLabel(openSearch) { return nil, apierror.Errorf("OpenSearch %s/%s is not managed by Console", input.TeamSlug, input.Name) } - terminationProtection, found, err := unstructured.NestedBool(os.Object, specTerminationProtection...) - if err != nil { - return nil, err + annotations := openSearch.GetAnnotations() + if annotations == nil { + annotations = make(map[string]string) } - if found && terminationProtection { - if err := unstructured.SetNestedField(os.Object, false, specTerminationProtection...); err != nil { - return nil, err - } + if annotations[api.AllowDeletionAnnotation] != "true" { + annotations[api.AllowDeletionAnnotation] = "true" + openSearch.SetAnnotations(annotations) - if _, err = client.Update(ctx, os, metav1.UpdateOptions{}); err != nil { - return nil, fmt.Errorf("removing deletion protection: %w", err) + _, err = client.Update(ctx, openSearch, metav1.UpdateOptions{}) + if err != nil { + return nil, fmt.Errorf("set allow deletion annotation: %w", err) } } - if err := fromContext(ctx).watcher.Delete(ctx, input.EnvironmentName, input.TeamSlug.String(), name); err != nil { + if err := fromContext(ctx).naisWatcher.Delete(ctx, input.EnvironmentName, input.TeamSlug.String(), input.Name); err != nil { return nil, err } @@ -580,3 +511,93 @@ func Delete(ctx context.Context, input DeleteOpenSearchInput) (*DeleteOpenSearch OpenSearchDeleted: new(true), }, nil } + +func toMapperatorTier(tier OpenSearchTier) naiscrd.OpenSearchTier { + switch tier { + case OpenSearchTierSingleNode: + return naiscrd.OpenSearchTierSingleNode + case OpenSearchTierHighAvailability: + return naiscrd.OpenSearchTierHighAvailability + default: + return "" + } +} + +func fromMapperatorTier(tier naiscrd.OpenSearchTier) OpenSearchTier { + switch tier { + case naiscrd.OpenSearchTierSingleNode: + return OpenSearchTierSingleNode + case naiscrd.OpenSearchTierHighAvailability: + return OpenSearchTierHighAvailability + default: + return "" + } +} + +func toMapperatorMemory(memory OpenSearchMemory) naiscrd.OpenSearchMemory { + switch memory { + case OpenSearchMemoryGB2: + return naiscrd.OpenSearchMemory2GB + case OpenSearchMemoryGB4: + return naiscrd.OpenSearchMemory4GB + case OpenSearchMemoryGB8: + return naiscrd.OpenSearchMemory8GB + case OpenSearchMemoryGB16: + return naiscrd.OpenSearchMemory16GB + case OpenSearchMemoryGB32: + return naiscrd.OpenSearchMemory32GB + case OpenSearchMemoryGB64: + return naiscrd.OpenSearchMemory64GB + default: + return "" + } +} + +func fromMapperatorMemory(memory naiscrd.OpenSearchMemory) OpenSearchMemory { + switch memory { + case naiscrd.OpenSearchMemory2GB: + return OpenSearchMemoryGB2 + case naiscrd.OpenSearchMemory4GB: + return OpenSearchMemoryGB4 + case naiscrd.OpenSearchMemory8GB: + return OpenSearchMemoryGB8 + case naiscrd.OpenSearchMemory16GB: + return OpenSearchMemoryGB16 + case naiscrd.OpenSearchMemory32GB: + return OpenSearchMemoryGB32 + case naiscrd.OpenSearchMemory64GB: + return OpenSearchMemoryGB64 + default: + return "" + } +} + +func toMapperatorVersion(version OpenSearchMajorVersion) naiscrd.OpenSearchVersion { + switch version { + case OpenSearchMajorVersionV1: + return naiscrd.OpenSearchVersionV1 + case OpenSearchMajorVersionV2: + return naiscrd.OpenSearchVersionV2 + case OpenSearchMajorVersionV2_19: + return naiscrd.OpenSearchVersionV2_19 + case OpenSearchMajorVersionV3_3: + return naiscrd.OpenSearchVersionV3_3 + default: + return "" + } +} + +func fromMapperatorVersion(version naiscrd.OpenSearchVersion) OpenSearchMajorVersion { + switch version { + case naiscrd.OpenSearchVersionV1: + return OpenSearchMajorVersionV1 + case naiscrd.OpenSearchVersionV2: + return OpenSearchMajorVersionV2 + case naiscrd.OpenSearchVersionV2_19: + return OpenSearchMajorVersionV2_19 + case naiscrd.OpenSearchVersionV3_3: + return OpenSearchMajorVersionV3_3 + default: + return "" + } +} From 55c79878c6201c7aa13365e3153ccd24bb4f93de Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Fri, 13 Feb 2026 12:01:03 +0100 Subject: [PATCH 07/17] Create activitylog after successfull creation Add a debug log Co-authored-by: Trong Huu Nguyen --- internal/persistence/opensearch/queries.go | 24 +++++++++---------- internal/persistence/valkey/queries.go | 27 ++++++++++++---------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/internal/persistence/opensearch/queries.go b/internal/persistence/opensearch/queries.go index a132db683..4c8f4a5eb 100644 --- a/internal/persistence/opensearch/queries.go +++ b/internal/persistence/opensearch/queries.go @@ -207,18 +207,6 @@ func Create(ctx context.Context, input CreateOpenSearchInput) (*CreateOpenSearch res.SetAnnotations(kubernetes.WithCommonAnnotations(nil, authz.ActorFromContext(ctx).User.Identity())) kubernetes.SetManagedByConsoleLabel(res) - err = activitylog.Create(ctx, activitylog.CreateInput{ - Action: activitylog.ActivityLogEntryActionCreated, - Actor: authz.ActorFromContext(ctx).User, - ResourceType: ActivityLogEntryResourceTypeOpenSearch, - ResourceName: input.Name, - EnvironmentName: new(input.EnvironmentName), - TeamSlug: new(input.TeamSlug), - }) - if err != nil { - return nil, err - } - obj, err := kubernetes.ToUnstructured(res) if err != nil { return nil, err @@ -231,6 +219,18 @@ func Create(ctx context.Context, input CreateOpenSearchInput) (*CreateOpenSearch return nil, err } + err = activitylog.Create(ctx, activitylog.CreateInput{ + Action: activitylog.ActivityLogEntryActionCreated, + Actor: authz.ActorFromContext(ctx).User, + ResourceType: ActivityLogEntryResourceTypeOpenSearch, + ResourceName: input.Name, + EnvironmentName: new(input.EnvironmentName), + TeamSlug: new(input.TeamSlug), + }) + if err != nil { + return nil, err + } + os, err := toOpenSearchFromNais(res, input.EnvironmentName) if err != nil { return nil, err diff --git a/internal/persistence/valkey/queries.go b/internal/persistence/valkey/queries.go index 356b28912..578f85c83 100644 --- a/internal/persistence/valkey/queries.go +++ b/internal/persistence/valkey/queries.go @@ -170,18 +170,6 @@ func Create(ctx context.Context, input CreateValkeyInput) (*CreateValkeyPayload, res.Spec.NotifyKeyspaceEvents = *input.NotifyKeyspaceEvents } - err = activitylog.Create(ctx, activitylog.CreateInput{ - Action: activitylog.ActivityLogEntryActionCreated, - Actor: authz.ActorFromContext(ctx).User, - ResourceType: ActivityLogEntryResourceTypeValkey, - ResourceName: input.Name, - EnvironmentName: new(input.EnvironmentName), - TeamSlug: new(input.TeamSlug), - }) - if err != nil { - return nil, err - } - obj, err := kubernetes.ToUnstructured(res) if err != nil { return nil, err @@ -194,6 +182,18 @@ func Create(ctx context.Context, input CreateValkeyInput) (*CreateValkeyPayload, return nil, err } + err = activitylog.Create(ctx, activitylog.CreateInput{ + Action: activitylog.ActivityLogEntryActionCreated, + Actor: authz.ActorFromContext(ctx).User, + ResourceType: ActivityLogEntryResourceTypeValkey, + ResourceName: input.Name, + EnvironmentName: new(input.EnvironmentName), + TeamSlug: new(input.TeamSlug), + }) + if err != nil { + return nil, err + } + valkey, err := toValkeyFromNais(res, input.EnvironmentName) if err != nil { return nil, err @@ -527,8 +527,11 @@ func Delete(ctx context.Context, input DeleteValkeyInput) (*DeleteValkeyPayload, } func State(ctx context.Context, v *Valkey) (ValkeyState, error) { + fmt.Println("Getting state for", v.FullyQualifiedName()) + s, err := fromContext(ctx).aivenClient.ServiceGet(ctx, v.AivenProject, v.FullyQualifiedName()) if err != nil { + fmt.Println("Error getting state", err) // The Valkey instance may not have been created in Aiven yet, or it has been deleted. // In both cases, we return "unknown" state rather than an error. if aiven.IsNotFound(err) { From 3ac0d1ffcca8322764d14f286fd1792100ab3271 Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Fri, 13 Feb 2026 13:13:57 +0100 Subject: [PATCH 08/17] Remove AivenProject from valkey and opensearch Co-authored-by: Trong Huu Nguyen --- internal/graph/servicemaintenance.resolvers.go | 13 +++++++++++-- internal/persistence/opensearch/models.go | 2 -- internal/persistence/opensearch/queries.go | 12 ++++++++++-- internal/persistence/valkey/models.go | 2 -- internal/persistence/valkey/queries.go | 9 ++++++--- internal/servicemaintenance/queries.go | 14 ++++++++++++-- 6 files changed, 39 insertions(+), 13 deletions(-) diff --git a/internal/graph/servicemaintenance.resolvers.go b/internal/graph/servicemaintenance.resolvers.go index 403191feb..53aa00d02 100644 --- a/internal/graph/servicemaintenance.resolvers.go +++ b/internal/graph/servicemaintenance.resolvers.go @@ -11,6 +11,7 @@ import ( "github.com/nais/api/internal/persistence/opensearch" "github.com/nais/api/internal/persistence/valkey" "github.com/nais/api/internal/servicemaintenance" + "github.com/nais/api/internal/thirdparty/aiven" ) func (r *mutationResolver) StartValkeyMaintenance(ctx context.Context, input servicemaintenance.StartValkeyMaintenanceInput) (*servicemaintenance.StartValkeyMaintenancePayload, error) { @@ -42,8 +43,12 @@ func (r *mutationResolver) StartOpenSearchMaintenance(ctx context.Context, input } func (r *openSearchResolver) Maintenance(ctx context.Context, obj *opensearch.OpenSearch) (*servicemaintenance.OpenSearchMaintenance, error) { + project, err := aiven.GetProject(ctx, obj.EnvironmentName) + if err != nil { + return nil, err + } return &servicemaintenance.OpenSearchMaintenance{ - AivenProject: obj.AivenProject, + AivenProject: project.ID, ServiceName: obj.FullyQualifiedName(), }, nil } @@ -88,8 +93,12 @@ func (r *openSearchMaintenanceResolver) Updates(ctx context.Context, obj *servic } func (r *valkeyResolver) Maintenance(ctx context.Context, obj *valkey.Valkey) (*servicemaintenance.ValkeyMaintenance, error) { + project, err := aiven.GetProject(ctx, obj.EnvironmentName) + if err != nil { + return nil, err + } return &servicemaintenance.ValkeyMaintenance{ - AivenProject: obj.AivenProject, + AivenProject: project.ID, ServiceName: obj.FullyQualifiedName(), }, nil } diff --git a/internal/persistence/opensearch/models.go b/internal/persistence/opensearch/models.go index 40b4d3ba1..ea9093792 100644 --- a/internal/persistence/opensearch/models.go +++ b/internal/persistence/opensearch/models.go @@ -45,7 +45,6 @@ type OpenSearch struct { TeamSlug slug.Slug `json:"-"` EnvironmentName string `json:"-"` WorkloadReference *workload.Reference `json:"-"` - AivenProject string `json:"-"` MajorVersion OpenSearchMajorVersion `json:"-"` } @@ -211,7 +210,6 @@ func toOpenSearch(u *unstructured.Unstructured, envName string) (*OpenSearch, er }, TeamSlug: slug.Slug(obj.GetNamespace()), WorkloadReference: workload.ReferenceFromOwnerReferences(obj.GetOwnerReferences()), - AivenProject: obj.Spec.Project, Tier: machine.Tier, Memory: machine.Memory, MajorVersion: majorVersion, diff --git a/internal/persistence/opensearch/queries.go b/internal/persistence/opensearch/queries.go index 4c8f4a5eb..10313228b 100644 --- a/internal/persistence/opensearch/queries.go +++ b/internal/persistence/opensearch/queries.go @@ -54,7 +54,11 @@ func Get(ctx context.Context, teamSlug slug.Slug, environment, name string) (*Op } func State(ctx context.Context, os *OpenSearch) (OpenSearchState, error) { - s, err := fromContext(ctx).aivenClient.ServiceGet(ctx, os.AivenProject, os.FullyQualifiedName()) + project, err := aiven.GetProject(ctx, os.EnvironmentName) + if err != nil { + return OpenSearchStateUnknown, err + } + s, err := fromContext(ctx).aivenClient.ServiceGet(ctx, project.ID, os.FullyQualifiedName()) if err != nil { // The OpenSearch instance may not have been created in Aiven yet, or it has been deleted. // In both cases, we return "unknown" state rather than an error. @@ -123,8 +127,12 @@ func ListAccess(ctx context.Context, openSearch *OpenSearch, page *pagination.Pa } func GetOpenSearchVersion(ctx context.Context, os *OpenSearch) (*OpenSearchVersion, error) { + project, err := aiven.GetProject(ctx, os.EnvironmentName) + if err != nil { + return nil, err + } key := AivenDataLoaderKey{ - Project: os.AivenProject, + Project: project.ID, ServiceName: os.FullyQualifiedName(), } diff --git a/internal/persistence/valkey/models.go b/internal/persistence/valkey/models.go index 33c4c7dea..d569cb6dd 100644 --- a/internal/persistence/valkey/models.go +++ b/internal/persistence/valkey/models.go @@ -44,7 +44,6 @@ type Valkey struct { TeamSlug slug.Slug `json:"-"` EnvironmentName string `json:"-"` WorkloadReference *workload.Reference `json:"-"` - AivenProject string `json:"-"` } func (Valkey) IsPersistence() {} @@ -205,7 +204,6 @@ func toValkey(u *unstructured.Unstructured, envName string) (*Valkey, error) { }, TeamSlug: slug.Slug(obj.GetNamespace()), WorkloadReference: workload.ReferenceFromOwnerReferences(obj.GetOwnerReferences()), - AivenProject: obj.Spec.Project, Tier: machine.Tier, Memory: machine.Memory, MaxMemoryPolicy: maxMemoryPolicy, diff --git a/internal/persistence/valkey/queries.go b/internal/persistence/valkey/queries.go index 578f85c83..d57977ace 100644 --- a/internal/persistence/valkey/queries.go +++ b/internal/persistence/valkey/queries.go @@ -527,11 +527,14 @@ func Delete(ctx context.Context, input DeleteValkeyInput) (*DeleteValkeyPayload, } func State(ctx context.Context, v *Valkey) (ValkeyState, error) { - fmt.Println("Getting state for", v.FullyQualifiedName()) + project, err := aiven.GetProject(ctx, v.EnvironmentName) + if err != nil { + return ValkeyStateUnknown, err + } + aivenProject := project.ID - s, err := fromContext(ctx).aivenClient.ServiceGet(ctx, v.AivenProject, v.FullyQualifiedName()) + s, err := fromContext(ctx).aivenClient.ServiceGet(ctx, aivenProject, v.FullyQualifiedName()) if err != nil { - fmt.Println("Error getting state", err) // The Valkey instance may not have been created in Aiven yet, or it has been deleted. // In both cases, we return "unknown" state rather than an error. if aiven.IsNotFound(err) { diff --git a/internal/servicemaintenance/queries.go b/internal/servicemaintenance/queries.go index 1af0f5032..3467042b6 100644 --- a/internal/servicemaintenance/queries.go +++ b/internal/servicemaintenance/queries.go @@ -12,6 +12,7 @@ import ( "github.com/nais/api/internal/persistence/opensearch" "github.com/nais/api/internal/persistence/valkey" servicemaintenanceal "github.com/nais/api/internal/servicemaintenance/activitylog" + "github.com/nais/api/internal/thirdparty/aiven" ) func StartValkeyMaintenance(ctx context.Context, input StartValkeyMaintenanceInput) error { @@ -20,7 +21,12 @@ func StartValkeyMaintenance(ctx context.Context, input StartValkeyMaintenanceInp return err } - if err := fromContext(ctx).maintenanceMutator.aivenClient.ServiceMaintenanceStart(ctx, vk.AivenProject, vk.FullyQualifiedName()); err != nil { + project, err := aiven.GetProject(ctx, input.EnvironmentName) + if err != nil { + return err + } + + if err := fromContext(ctx).maintenanceMutator.aivenClient.ServiceMaintenanceStart(ctx, project.ID, vk.FullyQualifiedName()); err != nil { fromContext(ctx).log.WithError(err).Error("Failed to start Valkey maintenance") return err } @@ -41,7 +47,11 @@ func StartOpenSearchMaintenance(ctx context.Context, input StartOpenSearchMainte return err } - if err := fromContext(ctx).maintenanceMutator.aivenClient.ServiceMaintenanceStart(ctx, instance.AivenProject, instance.FullyQualifiedName()); err != nil { + project, err := aiven.GetProject(ctx, input.EnvironmentName) + if err != nil { + return err + } + if err := fromContext(ctx).maintenanceMutator.aivenClient.ServiceMaintenanceStart(ctx, project.ID, instance.FullyQualifiedName()); err != nil { fromContext(ctx).log.WithError(err).Error("Failed to start OpenSearch maintenance") return err } From 8259f9e49f1bd8ef7028b4ebc4ca770c32fbbc31 Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Fri, 13 Feb 2026 13:25:53 +0100 Subject: [PATCH 09/17] Log permission errors --- internal/graph/apierror/apierror.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/graph/apierror/apierror.go b/internal/graph/apierror/apierror.go index 181fd663b..20ccca0f6 100644 --- a/internal/graph/apierror/apierror.go +++ b/internal/graph/apierror/apierror.go @@ -64,6 +64,8 @@ func GetErrorPresenter(log logrus.FieldLogger) graphql.ErrorPresenterFunc { case k8serrors.IsNotFound(unwrappedError): unwrappedError = ErrNotFound case k8serrors.IsForbidden(unwrappedError): + log.WithError(unwrappedError).Errorf("kubernetes api permission error") + unwrappedError = ErrForbidden } From ef68285c0ecf5adf06d9528a1f3218d1bf660c84 Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Fri, 13 Feb 2026 13:38:36 +0100 Subject: [PATCH 10/17] Fix delete Co-authored-by: Trong Huu Nguyen --- internal/kubernetes/watcher/cluster_watcher.go | 1 + internal/kubernetes/watcher/watcher.go | 1 + internal/persistence/opensearch/queries.go | 2 +- internal/persistence/valkey/queries.go | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/kubernetes/watcher/cluster_watcher.go b/internal/kubernetes/watcher/cluster_watcher.go index 1815b075a..4eb9e4d53 100644 --- a/internal/kubernetes/watcher/cluster_watcher.go +++ b/internal/kubernetes/watcher/cluster_watcher.go @@ -135,6 +135,7 @@ func (w *clusterWatcher[T]) OnDelete(obj any) { w.watcher.remove(w.cluster, t) } +// Delete will delete the resource using an imperonated client. func (w *clusterWatcher[T]) Delete(ctx context.Context, namespace, name string) error { client, err := w.ImpersonatedClient(ctx) if err != nil { diff --git a/internal/kubernetes/watcher/watcher.go b/internal/kubernetes/watcher/watcher.go index 8d53a6222..4e7f6ef21 100644 --- a/internal/kubernetes/watcher/watcher.go +++ b/internal/kubernetes/watcher/watcher.go @@ -276,6 +276,7 @@ func (w *Watcher[T]) GetByNamespace(namespace string, filter ...Filter) []*Envir return ret } +// Delete will delete the resource using an imperonated client. func (w *Watcher[T]) Delete(ctx context.Context, cluster, namespace string, name string) error { for _, watcher := range w.watchers { if watcher.cluster == cluster { diff --git a/internal/persistence/opensearch/queries.go b/internal/persistence/opensearch/queries.go index 10313228b..41d379b35 100644 --- a/internal/persistence/opensearch/queries.go +++ b/internal/persistence/opensearch/queries.go @@ -500,7 +500,7 @@ func Delete(ctx context.Context, input DeleteOpenSearchInput) (*DeleteOpenSearch } } - if err := fromContext(ctx).naisWatcher.Delete(ctx, input.EnvironmentName, input.TeamSlug.String(), input.Name); err != nil { + if err := client.Delete(ctx, input.Name, metav1.DeleteOptions{}); err != nil { return nil, err } diff --git a/internal/persistence/valkey/queries.go b/internal/persistence/valkey/queries.go index d57977ace..60163dca3 100644 --- a/internal/persistence/valkey/queries.go +++ b/internal/persistence/valkey/queries.go @@ -506,7 +506,7 @@ func Delete(ctx context.Context, input DeleteValkeyInput) (*DeleteValkeyPayload, } } - if err := fromContext(ctx).naisWatcher.Delete(ctx, input.EnvironmentName, input.TeamSlug.String(), input.Name); err != nil { + if err := client.Delete(ctx, input.Name, metav1.DeleteOptions{}); err != nil { return nil, err } From 0e64231cf5a05c9b4a3461047c164145fe0a3371 Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Fri, 13 Feb 2026 13:57:00 +0100 Subject: [PATCH 11/17] Change fallback version Co-authored-by: Trong Huu Nguyen --- internal/persistence/opensearch/queries.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/persistence/opensearch/queries.go b/internal/persistence/opensearch/queries.go index 41d379b35..ccfa0ea44 100644 --- a/internal/persistence/opensearch/queries.go +++ b/internal/persistence/opensearch/queries.go @@ -151,7 +151,7 @@ func GetOpenSearchVersion(ctx context.Context, os *OpenSearch) (*OpenSearchVersi } if major == "" { - major = OpenSearchMajorVersionV2 + major = OpenSearchMajorVersionV3_3 } return &OpenSearchVersion{ From 625b990983df47aa210c6e15f2c5aa0ccce08796 Mon Sep 17 00:00:00 2001 From: Trong Huu Nguyen Date: Wed, 3 Jun 2026 09:07:35 +0200 Subject: [PATCH 12/17] fix and cleanup after rebase --- integration_tests/valkey_crud.lua | 10 +++---- internal/kubernetes/watcher/watcher.go | 2 +- internal/persistence/valkey/models.go | 7 +++++ internal/persistence/valkey/queries.go | 40 ++++++++++++++++++++------ 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/integration_tests/valkey_crud.lua b/integration_tests/valkey_crud.lua index 97d1fc5c5..3db45d7a9 100644 --- a/integration_tests/valkey_crud.lua +++ b/integration_tests/valkey_crud.lua @@ -86,12 +86,10 @@ Test.gql("Create valkey as team member", function(t) teamSlug: "someteamname" tier: SINGLE_NODE memory: GB_14 - databases: 32 } ) { valkey { name - databases } } } @@ -102,7 +100,6 @@ Test.gql("Create valkey as team member", function(t) createValkey = { valkey = { name = "foobar", - databases = 32, }, }, }, @@ -233,8 +230,7 @@ Test.k8s("Validate Valkey resource", function(t) name = "foobar", namespace = "someteamname", }, - spec = { - databases = 32, + spec = { memory = "14GB", tier = "SingleNode", }, @@ -362,6 +358,7 @@ Test.k8s("Validate Valkey resource after update", function(t) namespace = "someteamname", }, spec = { + databases = 64, maxMemoryPolicy = "allkeys-random", memory = "4GB", notifyKeyspaceEvents = "Exd", @@ -539,6 +536,7 @@ Test.k8s("Validate hobbyist Valkey resource after update", function(t) namespace = "someteamname", }, spec = { + databases = 64, maxMemoryPolicy = "allkeys-random", memory = "1GB", notifyKeyspaceEvents = "Exd", @@ -789,7 +787,7 @@ Test.gql("Verify activity log for valkey operations", function(t) }, { field = "databases", - oldValue = "32", + oldValue = Null, newValue = "64", }, }, diff --git a/internal/kubernetes/watcher/watcher.go b/internal/kubernetes/watcher/watcher.go index 4e7f6ef21..5c567fd53 100644 --- a/internal/kubernetes/watcher/watcher.go +++ b/internal/kubernetes/watcher/watcher.go @@ -310,7 +310,7 @@ func (w *Watcher[T]) ImpersonatedClientWithNamespace(ctx context.Context, cluste return c.Namespace(namespace), nil } -func (w *Watcher[T]) SystemAuthenticatedClient(ctx context.Context, cluster string) (dynamic.NamespaceableResourceInterface, error) { +func (w *Watcher[T]) SystemAuthenticatedClient(ctx context.Context, cluster string, opts ...ImpersonatedClientOption) (dynamic.NamespaceableResourceInterface, error) { for _, watcher := range w.watchers { if watcher.cluster == cluster { return watcher.SystemAuthenticatedClient(ctx, opts...) diff --git a/internal/persistence/valkey/models.go b/internal/persistence/valkey/models.go index d569cb6dd..69fee9264 100644 --- a/internal/persistence/valkey/models.go +++ b/internal/persistence/valkey/models.go @@ -221,6 +221,12 @@ func toValkeyFromNais(v *naiscrd.Valkey, envName string) (*Valkey, error) { return nil, err } } + + databases := 16 + if v.Spec.Databases != nil { + databases = *v.Spec.Databases + } + return &Valkey{ Name: v.Name, EnvironmentName: envName, @@ -231,6 +237,7 @@ func toValkeyFromNais(v *naiscrd.Valkey, envName string) (*Valkey, error) { Memory: fromMapperatorMemory(v.Spec.Memory), NotifyKeyspaceEvents: v.Spec.NotifyKeyspaceEvents, MaxMemoryPolicy: mmp, + Databases: databases, }, nil } diff --git a/internal/persistence/valkey/queries.go b/internal/persistence/valkey/queries.go index 60163dca3..5054279c6 100644 --- a/internal/persistence/valkey/queries.go +++ b/internal/persistence/valkey/queries.go @@ -169,12 +169,24 @@ func Create(ctx context.Context, input CreateValkeyInput) (*CreateValkeyPayload, if input.NotifyKeyspaceEvents != nil { res.Spec.NotifyKeyspaceEvents = *input.NotifyKeyspaceEvents } + if input.Databases != nil { + res.Spec.Databases = input.Databases + } obj, err := kubernetes.ToUnstructured(res) if err != nil { return nil, err } + // TODO: fix + if input.Databases != nil { + // Must be stored and read as float64 for the integration tests to work. + err := unstructured.SetNestedField(obj.Object, float64(*input.Databases), "spec", "databases") + if err != nil { + return nil, err + } + } + if _, err = client.Create(ctx, obj, metav1.CreateOptions{}); err != nil { if k8serrors.IsAlreadyExists(err) { return nil, apierror.ErrAlreadyExists @@ -241,6 +253,16 @@ func Update(ctx context.Context, input UpdateValkeyInput) (*UpdateValkeyPayload, changes = append(changes, res...) } + // TODO: fix + databases := concreteValkey.Spec.Databases + if input.Databases != nil { + databases = input.Databases + } + // Must be stored and read as float64 for the integration tests to work. + if err := unstructured.SetNestedField(valkey.Object, float64(*databases), "spec", "databases"); err != nil { + return nil, err + } + if len(changes) == 0 { v, err := kubernetes.ToConcrete[naiscrd.Valkey](valkey) if err != nil { @@ -261,6 +283,12 @@ func Update(ctx context.Context, input UpdateValkeyInput) (*UpdateValkeyPayload, return nil, err } + // TODO: fix + // Must be stored and read as float64 for the integration tests to work. + if err := unstructured.SetNestedField(obj.Object, float64(*databases), "spec", "databases"); err != nil { + return nil, err + } + obj.SetAnnotations(kubernetes.WithCommonAnnotations(obj.GetAnnotations(), authz.ActorFromContext(ctx).User.Identity())) ret, err := client.Update(ctx, obj, metav1.UpdateOptions{}) @@ -401,7 +429,7 @@ func updateMaxMemoryPolicy(valkey *naiscrd.Valkey, input UpdateValkeyInput) ([]* if err != nil { return nil, fmt.Errorf("parsing existing max memory policy: %w", err) } - oldMMP = ptr.To(old.String()) + oldMMP = new(old.String()) } changes := make([]*ValkeyUpdatedActivityLogEntryDataUpdatedField, 0) @@ -430,7 +458,7 @@ func updateNotifyKeyspaceEvents(valkey *naiscrd.Valkey, input UpdateValkeyInput) var oldValue *string if valkey.Spec.NotifyKeyspaceEvents != "" { - oldValue = new(valkey.Spec.NotifyKeyspaceEvents)) + oldValue = new(valkey.Spec.NotifyKeyspaceEvents) } changes = append(changes, &ValkeyUpdatedActivityLogEntryDataUpdatedField{ Field: "notifyKeyspaceEvents", @@ -455,7 +483,7 @@ func updateDatabases(valkey *naiscrd.Valkey, input UpdateValkeyInput) ([]*Valkey } changes = append(changes, &ValkeyUpdatedActivityLogEntryDataUpdatedField{ - Field: "databases", + Field: "databases", OldValue: func() *string { if oldValue != nil { return new(strconv.FormatInt(int64(*oldValue), 10)) @@ -465,11 +493,7 @@ func updateDatabases(valkey *naiscrd.Valkey, input UpdateValkeyInput) ([]*Valkey NewValue: new(strconv.Itoa(*input.Databases)), }) - // Must be stored and read as float64 for the integration tests to work. - if err := unstructured.SetNestedField(valkey.Object, float64(*input.Databases), specNumberOfDatabases...); err != nil { - return nil, err - } - + valkey.Spec.Databases = input.Databases return changes, nil } From 5b5f16584b6100b52bcd1f2870f691d49bcd6552 Mon Sep 17 00:00:00 2001 From: Trong Huu Nguyen Date: Wed, 3 Jun 2026 09:48:23 +0200 Subject: [PATCH 13/17] feat(valkey): support persistence configuration --- go.mod | 28 +-- go.sum | 68 ++++---- .../valkey_crud/dev/someteamname/valkey.yaml | 2 + integration_tests/valkey_crud.lua | 33 ++++ internal/graph/gengql/root_.generated.go | 46 +++++ internal/graph/gengql/valkey.generated.go | 159 +++++++++++++++++- internal/graph/schema/valkey.graphqls | 16 ++ internal/persistence/valkey/models.go | 31 +++- internal/persistence/valkey/queries.go | 36 ++++ 9 files changed, 364 insertions(+), 55 deletions(-) diff --git a/go.mod b/go.mod index 0dd2b21b6..dc4e0b89f 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( github.com/nais/api/pkg/apiclient v0.0.0-20250219111538-2b76a0fd6ed9 github.com/nais/bifrost v0.0.0-20260106105449-911627ac2c61 github.com/nais/liberator v0.0.0-20260216142648-ee49a9372bc4 - github.com/nais/pgrator/pkg/api v0.0.0-20260219115817-cf954d58c04e + github.com/nais/pgrator/pkg/api v0.0.0-20260528100930-7dcc162c09d6 github.com/nais/tester v0.1.1 github.com/nais/unleasherator v0.0.0-20251216221129-efebc54203fe github.com/nais/v13s/pkg/api v0.0.0-20260528080657-d4f49e5737da @@ -82,12 +82,12 @@ require ( google.golang.org/api v0.280.0 google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 google.golang.org/grpc v1.81.1 - google.golang.org/protobuf v1.36.11 - k8s.io/api v0.35.1 - k8s.io/apimachinery v0.35.3 - k8s.io/client-go v0.35.1 + google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af + k8s.io/api v0.36.1 + k8s.io/apimachinery v0.36.1 + k8s.io/client-go v0.36.1 k8s.io/klog/v2 v2.140.0 - k8s.io/utils v0.0.0-20260108192941-914a6e750570 + k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 sigs.k8s.io/yaml v1.6.0 ) @@ -170,7 +170,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/coreos/go-semver v0.3.1 // indirect - github.com/coreos/go-systemd/v22 v22.6.0 // indirect + github.com/coreos/go-systemd/v22 v22.7.0 // indirect github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/cubicdaiya/gonp v1.0.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -411,9 +411,9 @@ require ( github.com/zitadel/logging v0.6.1 // indirect github.com/zitadel/schema v1.3.0 // indirect go.etcd.io/bbolt v1.4.3 // indirect - go.etcd.io/etcd/api/v3 v3.6.7 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.6.7 // indirect - go.etcd.io/etcd/client/v3 v3.6.7 // indirect + go.etcd.io/etcd/api/v3 v3.6.8 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.8 // indirect + go.etcd.io/etcd/client/v3 v3.6.8 // indirect go.mongodb.org/mongo-driver v1.17.9 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect @@ -472,13 +472,13 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect honnef.co/go/tools v0.7.0 // indirect - k8s.io/apiextensions-apiserver v0.35.0 // indirect - k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e // indirect + k8s.io/apiextensions-apiserver v0.36.0 // indirect + k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect mvdan.cc/gofumpt v0.9.2 // indirect - sigs.k8s.io/controller-runtime v0.23.1 // indirect + sigs.k8s.io/controller-runtime v0.24.1 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect ) replace github.com/GoogleCloudPlatform/k8s-config-connector/mockgcp => ./mockgcp diff --git a/go.sum b/go.sum index 08b8ab991..7ec8a6e58 100644 --- a/go.sum +++ b/go.sum @@ -277,8 +277,8 @@ github.com/coreos/go-oidc/v3 v3.12.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDh github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= -github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= +github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= +github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -515,8 +515,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= -github.com/google/pprof v0.0.0-20260202012954-cb029daf43ef h1:xpF9fUHpoIrrjX24DURVKiwHcFpw19ndIs+FwTSMbno= -github.com/google/pprof v0.0.0-20260202012954-cb029daf43ef/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 h1:EwtI+Al+DeppwYX2oXJCETMO23COyaKGP6fHVpkpWpg= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= @@ -807,8 +807,8 @@ github.com/nais/bifrost v0.0.0-20260106105449-911627ac2c61 h1:DMIjq7U47OJ8GlgOR3 github.com/nais/bifrost v0.0.0-20260106105449-911627ac2c61/go.mod h1:sAeomjrnGAI9VAErCaOHbTehVkf6hhKoJpHL8uzOqGg= github.com/nais/liberator v0.0.0-20260216142648-ee49a9372bc4 h1:i7jBukqLtNpQIBhy/YBA8XjLRVdI8B7WxC9nhQyxlWE= github.com/nais/liberator v0.0.0-20260216142648-ee49a9372bc4/go.mod h1:jmMoQtUMhvv7j1C2gz89Gxc4hxc73GXtTR3mEXn7cvU= -github.com/nais/pgrator/pkg/api v0.0.0-20260219115817-cf954d58c04e h1:FWPwIgFlNjA0akEUt2y6Hp5e03rGg1QmWl56XvnctYs= -github.com/nais/pgrator/pkg/api v0.0.0-20260219115817-cf954d58c04e/go.mod h1:iDLbi5Ss8Fs6L5+ot8jBLStR5/bdiPgblt7OsN06n50= +github.com/nais/pgrator/pkg/api v0.0.0-20260528100930-7dcc162c09d6 h1:xu68LxWvXcV9QUs6vCk3p4SrRyH/0veByRP7vRmw9ZI= +github.com/nais/pgrator/pkg/api v0.0.0-20260528100930-7dcc162c09d6/go.mod h1:wFRwI8RMC1RzNBABV1SOojhVhnEG7C071BcOOayExtE= github.com/nais/tester v0.1.1 h1:tpJ5HKpu3mEIWX/mec0Yj0xLHEpt+MwTAsj282n0Py0= github.com/nais/tester v0.1.1/go.mod h1:NCQMcgftHz/EXorob1XwDTOqkQmImDqr51YQ2Uea9Pc= github.com/nais/unleasherator v0.0.0-20251216221129-efebc54203fe h1:CdRVopOihru4tXVwKZjhg6C8SbPLCQYOhJKpjBZYhjg= @@ -830,11 +830,11 @@ github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNs github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo/v2 v2.28.2 h1:DTrMfpqxiNUyQ3Y0zhn1n3cOO2euFgQPYIpkWwxVFps= -github.com/onsi/ginkgo/v2 v2.28.2/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= +github.com/onsi/ginkgo/v2 v2.29.0 h1:rfh+ZFjgJhYWRoIqVf3Uwx/W20yLrcrE2h2GmYVRaag= +github.com/onsi/ginkgo/v2 v2.29.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= -github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= +github.com/onsi/gomega v1.41.0 h1:OwKp4pXNgVxf6sCplzYo794OFNuoL2q2SBMU5NSWOjA= +github.com/onsi/gomega v1.41.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= github.com/open-telemetry/opentelemetry-collector-contrib/internal/exp/metrics v0.145.0 h1:0dYiJ7krIwaHFX6YLNDo/yawTZIu8X16tT/nwW1UTG8= github.com/open-telemetry/opentelemetry-collector-contrib/internal/exp/metrics v0.145.0/go.mod h1:mhoa9lipcEH0heeKf6+xHzGUrCuAgImQv4/Qpmu0+Fk= github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.145.0 h1:sB4yuYx45zig1ceQ+kmrEYy0xMZ+mGagwYIFtJkkU1w= @@ -1122,12 +1122,12 @@ go.einride.tech/aip v0.79.0 h1:19zdPlZzlUvxOA8syAFw4LkdJdXepzyTl6gt9XEeqdU= go.einride.tech/aip v0.79.0/go.mod h1:E8+wdTApA70odnpFzJgsGogHozC2JCIhFJBKPr8bVig= go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= -go.etcd.io/etcd/api/v3 v3.6.7 h1:7BNJ2gQmc3DNM+9cRkv7KkGQDayElg8x3X+tFDYS+E0= -go.etcd.io/etcd/api/v3 v3.6.7/go.mod h1:xJ81TLj9hxrYYEDmXTeKURMeY3qEDN24hqe+q7KhbnI= -go.etcd.io/etcd/client/pkg/v3 v3.6.7 h1:vvzgyozz46q+TyeGBuFzVuI53/yd133CHceNb/AhBVs= -go.etcd.io/etcd/client/pkg/v3 v3.6.7/go.mod h1:2IVulJ3FZ/czIGl9T4lMF1uxzrhRahLqe+hSgy+Kh7Q= -go.etcd.io/etcd/client/v3 v3.6.7 h1:9WqA5RpIBtdMxAy1ukXLAdtg2pAxNqW5NUoO2wQrE6U= -go.etcd.io/etcd/client/v3 v3.6.7/go.mod h1:2XfROY56AXnUqGsvl+6k29wrwsSbEh1lAouQB1vHpeE= +go.etcd.io/etcd/api/v3 v3.6.8 h1:gqb1VN92TAI6G2FiBvWcqKtHiIjr4SU2GdXxTwyexbM= +go.etcd.io/etcd/api/v3 v3.6.8/go.mod h1:qyQj1HZPUV3B5cbAL8scG62+fyz5dSxxu0w8pn28N6Q= +go.etcd.io/etcd/client/pkg/v3 v3.6.8 h1:Qs/5C0LNFiqXxYf2GU8MVjYUEXJ6sZaYOz0zEqQgy50= +go.etcd.io/etcd/client/pkg/v3 v3.6.8/go.mod h1:GsiTRUZE2318PggZkAo6sWb6l8JLVrnckTNfbG8PWtw= +go.etcd.io/etcd/client/v3 v3.6.8 h1:B3G76t1UykqAOrbio7s/EPatixQDkQBevN8/mwiplrY= +go.etcd.io/etcd/client/v3 v3.6.8/go.mod h1:MVG4BpSIuumPi+ELF7wYtySETmoTWBHVcDoHdVupwt8= go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU= go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= @@ -1450,8 +1450,8 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1489,20 +1489,20 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU= honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc= -k8s.io/api v0.35.1 h1:0PO/1FhlK/EQNVK5+txc4FuhQibV25VLSdLMmGpDE/Q= -k8s.io/api v0.35.1/go.mod h1:28uR9xlXWml9eT0uaGo6y71xK86JBELShLy4wR1XtxM= -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.3 h1:MeaUwQCV3tjKP4bcwWGgZ/cp/vpsRnQzqO6J6tJyoF8= -k8s.io/apimachinery v0.35.3/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= -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/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY= +k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo= +k8s.io/apiextensions-apiserver v0.36.0 h1:Wt7E8J+VBCbj4FjiBfDTK/neXDDjyJVJc7xfuOHImZ0= +k8s.io/apiextensions-apiserver v0.36.0/go.mod h1:kGDjH0msuiIB3tgsYRV0kS9GqpMYMUsQ3GHv7TApyug= +k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA= +k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8= +k8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0= +k8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= -k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= -k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20260108192941-914a6e750570 h1:JT4W8lsdrGENg9W+YwwdLJxklIuKWdRm+BC+xt33FOY= -k8s.io/utils v0.0.0-20260108192941-914a6e750570/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= modernc.org/libc v1.68.0 h1:PJ5ikFOV5pwpW+VqCK1hKJuEWsonkIJhhIXyuF/91pQ= modernc.org/libc v1.68.0/go.mod h1:NnKCYeoYgsEqnY3PgvNgAeaJnso968ygU8Z0DxjoEc0= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= @@ -1513,13 +1513,13 @@ modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU= modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA= mvdan.cc/gofumpt v0.9.2 h1:zsEMWL8SVKGHNztrx6uZrXdp7AX8r421Vvp23sz7ik4= mvdan.cc/gofumpt v0.9.2/go.mod h1:iB7Hn+ai8lPvofHd9ZFGVg2GOr8sBUw1QUWjNbmIL/s= -sigs.k8s.io/controller-runtime v0.23.1 h1:TjJSM80Nf43Mg21+RCy3J70aj/W6KyvDtOlpKf+PupE= -sigs.k8s.io/controller-runtime v0.23.1/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0= +sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4= +sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 h1:2WOzJpHUBVrrkDjU4KBT8n5LDcj824eX0I5UKcgeRUs= -sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/integration_tests/k8s_resources/valkey_crud/dev/someteamname/valkey.yaml b/integration_tests/k8s_resources/valkey_crud/dev/someteamname/valkey.yaml index e09c68366..fc196a689 100644 --- a/integration_tests/k8s_resources/valkey_crud/dev/someteamname/valkey.yaml +++ b/integration_tests/k8s_resources/valkey_crud/dev/someteamname/valkey.yaml @@ -13,6 +13,8 @@ spec: plan: startup-4 project: nav-dev projectVpcId: d405e36a-a577-4dce-af0e-6d217fc47a5c + userConfig: + valkey_persistence: "off" tags: environment: dev team: slug-1 diff --git a/integration_tests/valkey_crud.lua b/integration_tests/valkey_crud.lua index 3db45d7a9..131c31f1d 100644 --- a/integration_tests/valkey_crud.lua +++ b/integration_tests/valkey_crud.lua @@ -319,11 +319,15 @@ Test.gql("Update Valkey as team-member", function(t) maxMemoryPolicy: ALLKEYS_RANDOM notifyKeyspaceEvents: "Exd" databases: 64 + persistence: { disabled: true } } ) { valkey { name databases + persistence { + disabled + } } } } @@ -335,6 +339,9 @@ Test.gql("Update Valkey as team-member", function(t) valkey = { name = "foobar", databases = 64, + persistence = { + disabled = true, + }, }, }, }, @@ -362,6 +369,9 @@ Test.k8s("Validate Valkey resource after update", function(t) maxMemoryPolicy = "allkeys-random", memory = "4GB", notifyKeyspaceEvents = "Exd", + persistence = { + disabled = true, + }, tier = "HighAvailability", }, }) @@ -436,6 +446,9 @@ Test.gql("List valkeys for team", function(t) maxMemoryPolicy notifyKeyspaceEvents databases + persistence { + disabled + } } } } @@ -454,6 +467,9 @@ Test.gql("List valkeys for team", function(t) maxMemoryPolicy = "ALLKEYS_RANDOM", notifyKeyspaceEvents = "Exd", databases = 64, + persistence = { + disabled = true, + }, }, { name = "foobar-hobbyist", @@ -462,6 +478,9 @@ Test.gql("List valkeys for team", function(t) maxMemoryPolicy = "", notifyKeyspaceEvents = "", databases = 16, + persistence = { + disabled = false, + }, }, { name = "valkey-someteamname-hobbyist-not-managed", @@ -470,6 +489,9 @@ Test.gql("List valkeys for team", function(t) maxMemoryPolicy = "", notifyKeyspaceEvents = "", databases = 16, + persistence = { + disabled = false, + }, }, { name = "valkey-someteamname-not-managed", @@ -478,6 +500,9 @@ Test.gql("List valkeys for team", function(t) maxMemoryPolicy = "", notifyKeyspaceEvents = "", databases = 16, + persistence = { + disabled = true, + }, }, }, }, @@ -540,6 +565,9 @@ Test.k8s("Validate hobbyist Valkey resource after update", function(t) maxMemoryPolicy = "allkeys-random", memory = "1GB", notifyKeyspaceEvents = "Exd", + persistence = { + disabled = true, + }, tier = "SingleNode", }, }) @@ -790,6 +818,11 @@ Test.gql("Verify activity log for valkey operations", function(t) oldValue = Null, newValue = "64", }, + { + field = "persistence.disabled", + oldValue = "false", + newValue = "true", + }, }, }, }, diff --git a/internal/graph/gengql/root_.generated.go b/internal/graph/gengql/root_.generated.go index a0066b741..b4aaabe47 100644 --- a/internal/graph/gengql/root_.generated.go +++ b/internal/graph/gengql/root_.generated.go @@ -3270,6 +3270,7 @@ type ComplexityRoot struct { Memory func(childComplexity int) int Name func(childComplexity int) int NotifyKeyspaceEvents func(childComplexity int) int + Persistence func(childComplexity int) int State func(childComplexity int) int Team func(childComplexity int) int TeamEnvironment func(childComplexity int) int @@ -3371,6 +3372,10 @@ type ComplexityRoot struct { Node func(childComplexity int) int } + ValkeyPersistence struct { + Disabled func(childComplexity int) int + } + ValkeyUpdatedActivityLogEntry struct { Actor func(childComplexity int) int CreatedAt func(childComplexity int) int @@ -17328,6 +17333,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.Valkey.NotifyKeyspaceEvents(childComplexity), true + case "Valkey.persistence": + if e.ComplexityRoot.Valkey.Persistence == nil { + break + } + + return e.ComplexityRoot.Valkey.Persistence(childComplexity), true + case "Valkey.state": if e.ComplexityRoot.Valkey.State == nil { break @@ -17732,6 +17744,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.ValkeyMaintenanceUpdateEdge.Node(childComplexity), true + case "ValkeyPersistence.disabled": + if e.ComplexityRoot.ValkeyPersistence.Disabled == nil { + break + } + + return e.ComplexityRoot.ValkeyPersistence.Disabled(childComplexity), true + case "ValkeyUpdatedActivityLogEntry.actor": if e.ComplexityRoot.ValkeyUpdatedActivityLogEntry.Actor == nil { break @@ -18545,6 +18564,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputUserTeamOrder, ec.unmarshalInputValkeyAccessOrder, ec.unmarshalInputValkeyOrder, + ec.unmarshalInputValkeyPersistenceInput, ec.unmarshalInputViewSecretValuesInput, ec.unmarshalInputVulnerabilitySummaryOrder, ec.unmarshalInputWorkloadLogSubscriptionFilter, @@ -29654,6 +29674,8 @@ type Valkey implements Persistence & Node { notifyKeyspaceEvents: String "Number of databases the Valkey instance is configured with. Default is 16. Minimum 1, maximum 128. Changing this will cause a restart of the Valkey service." databases: Int! + "Persistence and backup settings for the Valkey instance." + persistence: ValkeyPersistence! "Issues that affects the instance." issues( "Get the first n items in the connection. This can be used in combination with the after parameter." @@ -29684,6 +29706,16 @@ enum ValkeyState { UNKNOWN } +type ValkeyPersistence { + "Whether persistence (RDB dumps and backups) is disabled. If true, all data is lost if the instance is restarted for any reason." + disabled: Boolean! +} + +input ValkeyPersistenceInput { + "Disable persistence (RDB dumps and backups). Defaults to false." + disabled: Boolean! +} + type ValkeyAccess { workload: Workload! access: String! @@ -29797,6 +29829,8 @@ input CreateValkeyInput { notifyKeyspaceEvents: String "Number of databases. Default is 16. Minimum 1, maximum 128. Changing this will cause a restart of the Valkey service." databases: Int + "Persistence and backup settings for the Valkey instance. Defaults to enabled." + persistence: ValkeyPersistenceInput } type CreateValkeyPayload { @@ -29821,6 +29855,8 @@ input UpdateValkeyInput { notifyKeyspaceEvents: String "Number of databases. Default is 16. Minimum 1, maximum 128. Changing this will cause a restart of the Valkey service." databases: Int + "Persistence and backup settings for the Valkey instance. Defaults to enabled." + persistence: ValkeyPersistenceInput } type UpdateValkeyPayload { @@ -35516,6 +35552,8 @@ func (ec *executionContext) childFields_Valkey(ctx context.Context, field graphq return ec.fieldContext_Valkey_notifyKeyspaceEvents(ctx, field) case "databases": return ec.fieldContext_Valkey_databases(ctx, field) + case "persistence": + return ec.fieldContext_Valkey_persistence(ctx, field) case "issues": return ec.fieldContext_Valkey_issues(ctx, field) case "activityLog": @@ -35652,6 +35690,14 @@ func (ec *executionContext) childFields_ValkeyMaintenanceUpdateEdge(ctx context. return nil, fmt.Errorf("no field named %q was found under type ValkeyMaintenanceUpdateEdge", field.Name) } +func (ec *executionContext) childFields_ValkeyPersistence(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "disabled": + return ec.fieldContext_ValkeyPersistence_disabled(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type ValkeyPersistence", field.Name) +} + func (ec *executionContext) childFields_ValkeyUpdatedActivityLogEntryData(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "updatedFields": diff --git a/internal/graph/gengql/valkey.generated.go b/internal/graph/gengql/valkey.generated.go index 3f01fd3e0..ad16e21dd 100644 --- a/internal/graph/gengql/valkey.generated.go +++ b/internal/graph/gengql/valkey.generated.go @@ -722,6 +722,38 @@ func (ec *executionContext) fieldContext_Valkey_databases(_ context.Context, fie return graphql.NewScalarFieldContext("Valkey", field, false, false, errors.New("field of type Int does not have child fields")) } +func (ec *executionContext) _Valkey_persistence(ctx context.Context, field graphql.CollectedField, obj *valkey.Valkey) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_Valkey_persistence(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Persistence, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v valkey.ValkeyPersistence) graphql.Marshaler { + return ec.marshalNValkeyPersistence2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋvalkeyᚐValkeyPersistence(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_Valkey_persistence(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Valkey", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_ValkeyPersistence(ctx, field) + }, + } + return fc, nil +} + func (ec *executionContext) _Valkey_issues(ctx context.Context, field graphql.CollectedField, obj *valkey.Valkey) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -1714,6 +1746,29 @@ func (ec *executionContext) fieldContext_ValkeyEdge_node(_ context.Context, fiel return fc, nil } +func (ec *executionContext) _ValkeyPersistence_disabled(ctx context.Context, field graphql.CollectedField, obj *valkey.ValkeyPersistence) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ValkeyPersistence_disabled(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Disabled, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v bool) graphql.Marshaler { + return ec.marshalNBoolean2bool(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_ValkeyPersistence_disabled(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ValkeyPersistence", field, false, false, errors.New("field of type Boolean does not have child fields")) +} + func (ec *executionContext) _ValkeyUpdatedActivityLogEntry_id(ctx context.Context, field graphql.CollectedField, obj *valkey.ValkeyUpdatedActivityLogEntry) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -2104,7 +2159,7 @@ func (ec *executionContext) unmarshalInputCreateValkeyInput(ctx context.Context, asMap[k] = v } - fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "maxMemoryPolicy", "notifyKeyspaceEvents", "databases"} + fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "maxMemoryPolicy", "notifyKeyspaceEvents", "databases", "persistence"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -2167,6 +2222,13 @@ func (ec *executionContext) unmarshalInputCreateValkeyInput(ctx context.Context, return it, err } it.Databases = data + case "persistence": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("persistence")) + data, err := ec.unmarshalOValkeyPersistenceInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋvalkeyᚐValkeyPersistenceInput(ctx, v) + if err != nil { + return it, err + } + it.Persistence = data } } return it, nil @@ -2227,7 +2289,7 @@ func (ec *executionContext) unmarshalInputUpdateValkeyInput(ctx context.Context, asMap[k] = v } - fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "maxMemoryPolicy", "notifyKeyspaceEvents", "databases"} + fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "maxMemoryPolicy", "notifyKeyspaceEvents", "databases", "persistence"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -2290,6 +2352,13 @@ func (ec *executionContext) unmarshalInputUpdateValkeyInput(ctx context.Context, return it, err } it.Databases = data + case "persistence": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("persistence")) + data, err := ec.unmarshalOValkeyPersistenceInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋvalkeyᚐValkeyPersistenceInput(ctx, v) + if err != nil { + return it, err + } + it.Persistence = data } } return it, nil @@ -2369,6 +2438,36 @@ func (ec *executionContext) unmarshalInputValkeyOrder(ctx context.Context, obj a return it, nil } +func (ec *executionContext) unmarshalInputValkeyPersistenceInput(ctx context.Context, obj any) (valkey.ValkeyPersistenceInput, error) { + var it valkey.ValkeyPersistenceInput + if obj == nil { + return it, nil + } + + asMap := map[string]any{} + for k, v := range obj.(map[string]any) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"disabled"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "disabled": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("disabled")) + data, err := ec.unmarshalNBoolean2bool(ctx, v) + if err != nil { + return it, err + } + it.Disabled = data + } + } + return it, nil +} + // endregion **************************** input.gotpl ***************************** // region ************************** interface.gotpl *************************** @@ -2827,6 +2926,11 @@ func (ec *executionContext) _Valkey(ctx context.Context, sel ast.SelectionSet, o if out.Values[i] == graphql.Null { atomic.AddUint32(&out.Invalids, 1) } + case "persistence": + out.Values[i] = ec._Valkey_persistence(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } case "issues": field := field @@ -3456,6 +3560,45 @@ func (ec *executionContext) _ValkeyEdge(ctx context.Context, sel ast.SelectionSe return out } +var valkeyPersistenceImplementors = []string{"ValkeyPersistence"} + +func (ec *executionContext) _ValkeyPersistence(ctx context.Context, sel ast.SelectionSet, obj *valkey.ValkeyPersistence) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, valkeyPersistenceImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("ValkeyPersistence") + case "disabled": + out.Values[i] = ec._ValkeyPersistence_disabled(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.Deferred, int32(min(len(deferred), math.MaxInt32))) + + for label, dfs := range deferred { + ec.ProcessDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var valkeyUpdatedActivityLogEntryImplementors = []string{"ValkeyUpdatedActivityLogEntry", "ActivityLogEntry", "Node"} func (ec *executionContext) _ValkeyUpdatedActivityLogEntry(ctx context.Context, sel ast.SelectionSet, obj *valkey.ValkeyUpdatedActivityLogEntry) graphql.Marshaler { @@ -3872,6 +4015,10 @@ func (ec *executionContext) marshalNValkeyOrderField2githubᚗcomᚋnaisᚋapi return v } +func (ec *executionContext) marshalNValkeyPersistence2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋvalkeyᚐValkeyPersistence(ctx context.Context, sel ast.SelectionSet, v valkey.ValkeyPersistence) graphql.Marshaler { + return ec._ValkeyPersistence(ctx, sel, &v) +} + func (ec *executionContext) unmarshalNValkeyState2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋvalkeyᚐValkeyState(ctx context.Context, v any) (valkey.ValkeyState, error) { var res valkey.ValkeyState err := res.UnmarshalGQL(v) @@ -3970,4 +4117,12 @@ func (ec *executionContext) unmarshalOValkeyOrder2ᚖgithubᚗcomᚋnaisᚋapi return &res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) unmarshalOValkeyPersistenceInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋvalkeyᚐValkeyPersistenceInput(ctx context.Context, v any) (*valkey.ValkeyPersistenceInput, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputValkeyPersistenceInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + // endregion ***************************** type.gotpl ***************************** diff --git a/internal/graph/schema/valkey.graphqls b/internal/graph/schema/valkey.graphqls index 5ff110867..0e064e24f 100644 --- a/internal/graph/schema/valkey.graphqls +++ b/internal/graph/schema/valkey.graphqls @@ -83,6 +83,8 @@ type Valkey implements Persistence & Node { notifyKeyspaceEvents: String "Number of databases the Valkey instance is configured with. Default is 16. Minimum 1, maximum 128. Changing this will cause a restart of the Valkey service." databases: Int! + "Persistence and backup settings for the Valkey instance." + persistence: ValkeyPersistence! "Issues that affects the instance." issues( "Get the first n items in the connection. This can be used in combination with the after parameter." @@ -113,6 +115,16 @@ enum ValkeyState { UNKNOWN } +type ValkeyPersistence { + "Whether persistence (RDB dumps and backups) is disabled. If true, all data is lost if the instance is restarted for any reason." + disabled: Boolean! +} + +input ValkeyPersistenceInput { + "Disable persistence (RDB dumps and backups). Defaults to false." + disabled: Boolean! +} + type ValkeyAccess { workload: Workload! access: String! @@ -226,6 +238,8 @@ input CreateValkeyInput { notifyKeyspaceEvents: String "Number of databases. Default is 16. Minimum 1, maximum 128. Changing this will cause a restart of the Valkey service." databases: Int + "Persistence and backup settings for the Valkey instance. Defaults to enabled." + persistence: ValkeyPersistenceInput } type CreateValkeyPayload { @@ -250,6 +264,8 @@ input UpdateValkeyInput { notifyKeyspaceEvents: String "Number of databases. Default is 16. Minimum 1, maximum 128. Changing this will cause a restart of the Valkey service." databases: Int + "Persistence and backup settings for the Valkey instance. Defaults to enabled." + persistence: ValkeyPersistenceInput } type UpdateValkeyPayload { diff --git a/internal/persistence/valkey/models.go b/internal/persistence/valkey/models.go index 69fee9264..bc5261f38 100644 --- a/internal/persistence/valkey/models.go +++ b/internal/persistence/valkey/models.go @@ -41,6 +41,7 @@ type Valkey struct { MaxMemoryPolicy ValkeyMaxMemoryPolicy `json:"maxMemoryPolicy,omitempty"` NotifyKeyspaceEvents string `json:"notifyKeyspaceEvents,omitempty"` Databases int `json:"databases"` + Persistence ValkeyPersistence `json:"persistence"` TeamSlug slug.Slug `json:"-"` EnvironmentName string `json:"-"` WorkloadReference *workload.Reference `json:"-"` @@ -83,6 +84,14 @@ type ValkeyAccess struct { WorkloadReference *workload.Reference `json:"-"` } +type ValkeyPersistence struct { + Disabled bool `json:"disabled"` +} + +type ValkeyPersistenceInput struct { + Disabled bool `json:"disabled"` +} + type ValkeyStatus struct { State string `json:"state"` Conditions []metav1.Condition `json:"conditions"` @@ -183,6 +192,10 @@ func toValkey(u *unstructured.Unstructured, envName string) (*Valkey, error) { numberOfDatabases = 16 } + // In the Aiven CRD, persistence is configured via userConfig.valkey_persistence, where "off" disables it. + valkeyPersistence, _, _ := unstructured.NestedString(u.Object, specValkeyPersistence...) + persistenceDisabled := valkeyPersistence == "off" + machine, err := machineTypeFromPlan(obj.Spec.Plan) if err != nil { return nil, fmt.Errorf("converting from plan: %w", err) @@ -209,6 +222,7 @@ func toValkey(u *unstructured.Unstructured, envName string) (*Valkey, error) { MaxMemoryPolicy: maxMemoryPolicy, NotifyKeyspaceEvents: notifyKeyspaceEvents, Databases: int(numberOfDatabases), + Persistence: ValkeyPersistence{Disabled: persistenceDisabled}, }, nil } @@ -227,6 +241,11 @@ func toValkeyFromNais(v *naiscrd.Valkey, envName string) (*Valkey, error) { databases = *v.Spec.Databases } + persistence := ValkeyPersistence{} + if v.Spec.Persistence != nil { + persistence.Disabled = v.Spec.Persistence.Disabled + } + return &Valkey{ Name: v.Name, EnvironmentName: envName, @@ -238,6 +257,7 @@ func toValkeyFromNais(v *naiscrd.Valkey, envName string) (*Valkey, error) { NotifyKeyspaceEvents: v.Spec.NotifyKeyspaceEvents, MaxMemoryPolicy: mmp, Databases: databases, + Persistence: persistence, }, nil } @@ -278,11 +298,12 @@ func (v *ValkeyMetadataInput) ValidationErrors(ctx context.Context) *validate.Va type ValkeyInput struct { ValkeyMetadataInput - Tier ValkeyTier `json:"tier"` - Memory ValkeyMemory `json:"memory"` - MaxMemoryPolicy *ValkeyMaxMemoryPolicy `json:"maxMemoryPolicy,omitempty"` - NotifyKeyspaceEvents *string `json:"notifyKeyspaceEvents,omitempty"` - Databases *int `json:"databases,omitempty"` + Tier ValkeyTier `json:"tier"` + Memory ValkeyMemory `json:"memory"` + MaxMemoryPolicy *ValkeyMaxMemoryPolicy `json:"maxMemoryPolicy,omitempty"` + NotifyKeyspaceEvents *string `json:"notifyKeyspaceEvents,omitempty"` + Databases *int `json:"databases,omitempty"` + Persistence *ValkeyPersistenceInput `json:"persistence,omitempty"` } func (v *ValkeyInput) Validate(ctx context.Context) error { diff --git a/internal/persistence/valkey/queries.go b/internal/persistence/valkey/queries.go index 5054279c6..5aa77101d 100644 --- a/internal/persistence/valkey/queries.go +++ b/internal/persistence/valkey/queries.go @@ -33,6 +33,7 @@ var ( specMaxMemoryPolicy = []string{"spec", "userConfig", "valkey_maxmemory_policy"} specNotifyKeyspaceEvents = []string{"spec", "userConfig", "valkey_notify_keyspace_events"} specNumberOfDatabases = []string{"spec", "userConfig", "valkey_number_of_databases"} + specValkeyPersistence = []string{"spec", "userConfig", "valkey_persistence"} ) func GetByIdent(ctx context.Context, id ident.Ident) (*Valkey, error) { @@ -173,6 +174,12 @@ func Create(ctx context.Context, input CreateValkeyInput) (*CreateValkeyPayload, res.Spec.Databases = input.Databases } + if input.Persistence != nil { + res.Spec.Persistence = &naiscrd.ValkeyPersistence{ + Disabled: input.Persistence.Disabled, + } + } + obj, err := kubernetes.ToUnstructured(res) if err != nil { return nil, err @@ -243,6 +250,7 @@ func Update(ctx context.Context, input UpdateValkeyInput) (*UpdateValkeyPayload, updateMaxMemoryPolicy, updateNotifyKeyspaceEvents, updateDatabases, + updatePersistence, } for _, f := range updateFuncs { @@ -497,6 +505,34 @@ func updateDatabases(valkey *naiscrd.Valkey, input UpdateValkeyInput) ([]*Valkey return changes, nil } +func updatePersistence(valkey *naiscrd.Valkey, input UpdateValkeyInput) ([]*ValkeyUpdatedActivityLogEntryDataUpdatedField, error) { + if input.Persistence == nil { + return nil, nil + } + + oldDisabled := false + if valkey.Spec.Persistence != nil { + oldDisabled = valkey.Spec.Persistence.Disabled + } + + if oldDisabled == input.Persistence.Disabled { + return nil, nil + } + + changes := []*ValkeyUpdatedActivityLogEntryDataUpdatedField{ + { + Field: "persistence.disabled", + OldValue: new(strconv.FormatBool(oldDisabled)), + NewValue: new(strconv.FormatBool(input.Persistence.Disabled)), + }, + } + + valkey.Spec.Persistence = &naiscrd.ValkeyPersistence{ + Disabled: input.Persistence.Disabled, + } + return changes, nil +} + func Delete(ctx context.Context, input DeleteValkeyInput) (*DeleteValkeyPayload, error) { if err := input.Validate(ctx); err != nil { return nil, err From 4946409518e3d52fd2017241ac460faa1b84cc93 Mon Sep 17 00:00:00 2001 From: Trong Huu Nguyen Date: Wed, 3 Jun 2026 10:03:47 +0200 Subject: [PATCH 14/17] feat(opensearch): support shardIndexingPressure --- .../dev/someteamname/opensearch.yaml | 4 + integration_tests/opensearch_crud.lua | 51 +++++ internal/graph/gengql/opensearch.generated.go | 194 +++++++++++++++++- internal/graph/gengql/root_.generated.go | 60 ++++++ internal/graph/schema/opensearch.graphqls | 20 ++ internal/persistence/opensearch/models.go | 72 ++++--- internal/persistence/opensearch/queries.go | 51 +++++ 7 files changed, 427 insertions(+), 25 deletions(-) diff --git a/integration_tests/k8s_resources/opensearch_crud/dev/someteamname/opensearch.yaml b/integration_tests/k8s_resources/opensearch_crud/dev/someteamname/opensearch.yaml index a71e37b56..b9f041651 100644 --- a/integration_tests/k8s_resources/opensearch_crud/dev/someteamname/opensearch.yaml +++ b/integration_tests/k8s_resources/opensearch_crud/dev/someteamname/opensearch.yaml @@ -36,6 +36,10 @@ spec: terminationProtection: true userConfig: opensearch_version: "2" + opensearch: + shard_indexing_pressure: + enabled: true + enforced: true status: conditions: - lastTransitionTime: "2023-11-08T10:36:06Z" diff --git a/integration_tests/opensearch_crud.lua b/integration_tests/opensearch_crud.lua index 10a6c19f0..10f17ebc9 100644 --- a/integration_tests/opensearch_crud.lua +++ b/integration_tests/opensearch_crud.lua @@ -429,10 +429,15 @@ Test.gql("Update OpenSearch as team-member", function(t) memory: GB_4 version: V2 storageGB: 1020 + shardIndexingPressure: { enabled: true, enforced: true } } ) { openSearch { name + shardIndexingPressure { + enabled + enforced + } } } } @@ -443,6 +448,10 @@ Test.gql("Update OpenSearch as team-member", function(t) updateOpenSearch = { openSearch = { name = "foobar", + shardIndexingPressure = { + enabled = true, + enforced = true, + }, }, }, }, @@ -470,6 +479,10 @@ Test.k8s("Validate OpenSearch resource after update", function(t) tier = "HighAvailability", version = "2", storageGB = NotNull(), + shardIndexingPressure = { + enabled = true, + enforced = true, + }, }, }) end) @@ -485,6 +498,10 @@ Test.gql("List opensearches for team", function(t) name tier memory + shardIndexingPressure { + enabled + enforced + } } } } @@ -500,26 +517,46 @@ Test.gql("List opensearches for team", function(t) name = "foobar", tier = "HIGH_AVAILABILITY", memory = "GB_4", + shardIndexingPressure = { + enabled = true, + enforced = true, + }, }, { name = "foobar-hobbyist", tier = "SINGLE_NODE", memory = "GB_2", + shardIndexingPressure = { + enabled = false, + enforced = false, + }, }, { name = "noversion", tier = "SINGLE_NODE", memory = "GB_2", + shardIndexingPressure = { + enabled = false, + enforced = false, + }, }, { name = "opensearch-someteamname-hobbyist-not-managed", tier = "SINGLE_NODE", memory = "GB_2", + shardIndexingPressure = { + enabled = false, + enforced = false, + }, }, { name = "opensearch-someteamname-not-managed", tier = "HIGH_AVAILABILITY", memory = "GB_8", + shardIndexingPressure = { + enabled = true, + enforced = true, + }, }, }, }, @@ -654,6 +691,10 @@ Test.k8s("Validate hobbyist OpenSearch resource after update", function(t) tier = "SingleNode", version = "2", storageGB = NotNull(), + shardIndexingPressure = { + enabled = true, + enforced = true, + }, }, }) end) @@ -923,6 +964,16 @@ Test.gql("Verify activity log for opensearch operations", function(t) oldValue = "350", newValue = "1020", }, + { + field = "shardIndexingPressure.enabled", + oldValue = "false", + newValue = "true", + }, + { + field = "shardIndexingPressure.enforced", + oldValue = "false", + newValue = "true", + }, }, }, }, diff --git a/internal/graph/gengql/opensearch.generated.go b/internal/graph/gengql/opensearch.generated.go index a41496eb7..191156b39 100644 --- a/internal/graph/gengql/opensearch.generated.go +++ b/internal/graph/gengql/opensearch.generated.go @@ -655,6 +655,38 @@ func (ec *executionContext) fieldContext_OpenSearch_storageGB(_ context.Context, return graphql.NewScalarFieldContext("OpenSearch", field, false, false, errors.New("field of type Int does not have child fields")) } +func (ec *executionContext) _OpenSearch_shardIndexingPressure(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearch) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_OpenSearch_shardIndexingPressure(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.ShardIndexingPressure, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v opensearch.OpenSearchShardIndexingPressure) graphql.Marshaler { + return ec.marshalNOpenSearchShardIndexingPressure2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchShardIndexingPressure(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_OpenSearch_shardIndexingPressure(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "OpenSearch", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_OpenSearchShardIndexingPressure(ctx, field) + }, + } + return fc, nil +} + func (ec *executionContext) _OpenSearch_issues(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearch) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -1647,6 +1679,52 @@ func (ec *executionContext) fieldContext_OpenSearchEdge_node(_ context.Context, return fc, nil } +func (ec *executionContext) _OpenSearchShardIndexingPressure_enabled(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearchShardIndexingPressure) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_OpenSearchShardIndexingPressure_enabled(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Enabled, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v bool) graphql.Marshaler { + return ec.marshalNBoolean2bool(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_OpenSearchShardIndexingPressure_enabled(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("OpenSearchShardIndexingPressure", field, false, false, errors.New("field of type Boolean does not have child fields")) +} + +func (ec *executionContext) _OpenSearchShardIndexingPressure_enforced(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearchShardIndexingPressure) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_OpenSearchShardIndexingPressure_enforced(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Enforced, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v bool) graphql.Marshaler { + return ec.marshalNBoolean2bool(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_OpenSearchShardIndexingPressure_enforced(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("OpenSearchShardIndexingPressure", field, false, false, errors.New("field of type Boolean does not have child fields")) +} + func (ec *executionContext) _OpenSearchUpdatedActivityLogEntry_id(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearchUpdatedActivityLogEntry) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -2138,7 +2216,7 @@ func (ec *executionContext) unmarshalInputCreateOpenSearchInput(ctx context.Cont asMap[k] = v } - fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "version", "storageGB"} + fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "version", "storageGB", "shardIndexingPressure"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -2194,6 +2272,13 @@ func (ec *executionContext) unmarshalInputCreateOpenSearchInput(ctx context.Cont return it, err } it.StorageGB = data + case "shardIndexingPressure": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("shardIndexingPressure")) + data, err := ec.unmarshalOOpenSearchShardIndexingPressureInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchShardIndexingPressureInput(ctx, v) + if err != nil { + return it, err + } + it.ShardIndexingPressure = data } } return it, nil @@ -2317,6 +2402,43 @@ func (ec *executionContext) unmarshalInputOpenSearchOrder(ctx context.Context, o return it, nil } +func (ec *executionContext) unmarshalInputOpenSearchShardIndexingPressureInput(ctx context.Context, obj any) (opensearch.OpenSearchShardIndexingPressureInput, error) { + var it opensearch.OpenSearchShardIndexingPressureInput + if obj == nil { + return it, nil + } + + asMap := map[string]any{} + for k, v := range obj.(map[string]any) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"enabled", "enforced"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "enabled": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("enabled")) + data, err := ec.unmarshalNBoolean2bool(ctx, v) + if err != nil { + return it, err + } + it.Enabled = data + case "enforced": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("enforced")) + data, err := ec.unmarshalNBoolean2bool(ctx, v) + if err != nil { + return it, err + } + it.Enforced = data + } + } + return it, nil +} + func (ec *executionContext) unmarshalInputUpdateOpenSearchInput(ctx context.Context, obj any) (opensearch.UpdateOpenSearchInput, error) { var it opensearch.UpdateOpenSearchInput if obj == nil { @@ -2328,7 +2450,7 @@ func (ec *executionContext) unmarshalInputUpdateOpenSearchInput(ctx context.Cont asMap[k] = v } - fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "version", "storageGB"} + fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "version", "storageGB", "shardIndexingPressure"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -2384,6 +2506,13 @@ func (ec *executionContext) unmarshalInputUpdateOpenSearchInput(ctx context.Cont return it, err } it.StorageGB = data + case "shardIndexingPressure": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("shardIndexingPressure")) + data, err := ec.unmarshalOOpenSearchShardIndexingPressureInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchShardIndexingPressureInput(ctx, v) + if err != nil { + return it, err + } + it.ShardIndexingPressure = data } } return it, nil @@ -2801,6 +2930,11 @@ func (ec *executionContext) _OpenSearch(ctx context.Context, sel ast.SelectionSe if out.Values[i] == graphql.Null { atomic.AddUint32(&out.Invalids, 1) } + case "shardIndexingPressure": + out.Values[i] = ec._OpenSearch_shardIndexingPressure(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } case "issues": field := field @@ -3430,6 +3564,50 @@ func (ec *executionContext) _OpenSearchEdge(ctx context.Context, sel ast.Selecti return out } +var openSearchShardIndexingPressureImplementors = []string{"OpenSearchShardIndexingPressure"} + +func (ec *executionContext) _OpenSearchShardIndexingPressure(ctx context.Context, sel ast.SelectionSet, obj *opensearch.OpenSearchShardIndexingPressure) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, openSearchShardIndexingPressureImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("OpenSearchShardIndexingPressure") + case "enabled": + out.Values[i] = ec._OpenSearchShardIndexingPressure_enabled(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "enforced": + out.Values[i] = ec._OpenSearchShardIndexingPressure_enforced(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.Deferred, int32(min(len(deferred), math.MaxInt32))) + + for label, dfs := range deferred { + ec.ProcessDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var openSearchUpdatedActivityLogEntryImplementors = []string{"OpenSearchUpdatedActivityLogEntry", "ActivityLogEntry", "Node"} func (ec *executionContext) _OpenSearchUpdatedActivityLogEntry(ctx context.Context, sel ast.SelectionSet, obj *opensearch.OpenSearchUpdatedActivityLogEntry) graphql.Marshaler { @@ -3942,6 +4120,10 @@ func (ec *executionContext) marshalNOpenSearchOrderField2githubᚗcomᚋnaisᚋa return v } +func (ec *executionContext) marshalNOpenSearchShardIndexingPressure2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchShardIndexingPressure(ctx context.Context, sel ast.SelectionSet, v opensearch.OpenSearchShardIndexingPressure) graphql.Marshaler { + return ec._OpenSearchShardIndexingPressure(ctx, sel, &v) +} + func (ec *executionContext) unmarshalNOpenSearchState2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchState(ctx context.Context, v any) (opensearch.OpenSearchState, error) { var res opensearch.OpenSearchState err := res.UnmarshalGQL(v) @@ -4068,4 +4250,12 @@ func (ec *executionContext) unmarshalOOpenSearchOrder2ᚖgithubᚗcomᚋnaisᚋa return &res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) unmarshalOOpenSearchShardIndexingPressureInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchShardIndexingPressureInput(ctx context.Context, v any) (*opensearch.OpenSearchShardIndexingPressureInput, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputOpenSearchShardIndexingPressureInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + // endregion ***************************** type.gotpl ***************************** diff --git a/internal/graph/gengql/root_.generated.go b/internal/graph/gengql/root_.generated.go index b4aaabe47..a7b500056 100644 --- a/internal/graph/gengql/root_.generated.go +++ b/internal/graph/gengql/root_.generated.go @@ -1551,6 +1551,7 @@ type ComplexityRoot struct { Maintenance func(childComplexity int) int Memory func(childComplexity int) int Name func(childComplexity int) int + ShardIndexingPressure func(childComplexity int) int State func(childComplexity int) int StorageGB func(childComplexity int) int Team func(childComplexity int) int @@ -1654,6 +1655,11 @@ type ComplexityRoot struct { Node func(childComplexity int) int } + OpenSearchShardIndexingPressure struct { + Enabled func(childComplexity int) int + Enforced func(childComplexity int) int + } + OpenSearchUpdatedActivityLogEntry struct { Actor func(childComplexity int) int CreatedAt func(childComplexity int) int @@ -9710,6 +9716,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.OpenSearch.Name(childComplexity), true + case "OpenSearch.shardIndexingPressure": + if e.ComplexityRoot.OpenSearch.ShardIndexingPressure == nil { + break + } + + return e.ComplexityRoot.OpenSearch.ShardIndexingPressure(childComplexity), true + case "OpenSearch.state": if e.ComplexityRoot.OpenSearch.State == nil { break @@ -10128,6 +10141,20 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.OpenSearchMaintenanceUpdateEdge.Node(childComplexity), true + case "OpenSearchShardIndexingPressure.enabled": + if e.ComplexityRoot.OpenSearchShardIndexingPressure.Enabled == nil { + break + } + + return e.ComplexityRoot.OpenSearchShardIndexingPressure.Enabled(childComplexity), true + + case "OpenSearchShardIndexingPressure.enforced": + if e.ComplexityRoot.OpenSearchShardIndexingPressure.Enforced == nil { + break + } + + return e.ComplexityRoot.OpenSearchShardIndexingPressure.Enforced(childComplexity), true + case "OpenSearchUpdatedActivityLogEntry.actor": if e.ComplexityRoot.OpenSearchUpdatedActivityLogEntry.Actor == nil { break @@ -18512,6 +18539,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputMetricsRangeInput, ec.unmarshalInputOpenSearchAccessOrder, ec.unmarshalInputOpenSearchOrder, + ec.unmarshalInputOpenSearchShardIndexingPressureInput, ec.unmarshalInputPostgresInstanceOrder, ec.unmarshalInputReconcilerConfigInput, ec.unmarshalInputRemoveConfigValueInput, @@ -23868,6 +23896,8 @@ type OpenSearch implements Persistence & Node { memory: OpenSearchMemory! "Available storage in GB." storageGB: Int! + "Shard indexing back pressure settings for the OpenSearch instance." + shardIndexingPressure: OpenSearchShardIndexingPressure! "Issues that affects the instance." issues( "Get the first n items in the connection. This can be used in combination with the after parameter." @@ -23898,6 +23928,20 @@ enum OpenSearchState { UNKNOWN } +type OpenSearchShardIndexingPressure { + "Whether shard indexing back pressure is enabled." + enabled: Boolean! + "Whether back pressure runs in enforced mode. In enforced mode requests that may degrade cluster performance are rejected; in shadow mode (enforced false) metrics are tracked but no requests are rejected." + enforced: Boolean! +} + +input OpenSearchShardIndexingPressureInput { + "Enable or disable shard indexing back pressure. Defaults to false." + enabled: Boolean! + "Run back pressure in enforced mode. Defaults to false (shadow mode)." + enforced: Boolean! +} + type OpenSearchAccess { workload: Workload! access: String! @@ -23992,6 +24036,8 @@ input CreateOpenSearchInput { version: OpenSearchMajorVersion! "Available storage in GB." storageGB: Int! + "Shard indexing back pressure settings. Defaults to disabled." + shardIndexingPressure: OpenSearchShardIndexingPressureInput } type CreateOpenSearchPayload { @@ -24014,6 +24060,8 @@ input UpdateOpenSearchInput { version: OpenSearchMajorVersion! "Available storage in GB." storageGB: Int! + "Shard indexing back pressure settings. Defaults to disabled." + shardIndexingPressure: OpenSearchShardIndexingPressureInput } type UpdateOpenSearchPayload { @@ -33336,6 +33384,8 @@ func (ec *executionContext) childFields_OpenSearch(ctx context.Context, field gr return ec.fieldContext_OpenSearch_memory(ctx, field) case "storageGB": return ec.fieldContext_OpenSearch_storageGB(ctx, field) + case "shardIndexingPressure": + return ec.fieldContext_OpenSearch_shardIndexingPressure(ctx, field) case "issues": return ec.fieldContext_OpenSearch_issues(ctx, field) case "activityLog": @@ -33472,6 +33522,16 @@ func (ec *executionContext) childFields_OpenSearchMaintenanceUpdateEdge(ctx cont return nil, fmt.Errorf("no field named %q was found under type OpenSearchMaintenanceUpdateEdge", field.Name) } +func (ec *executionContext) childFields_OpenSearchShardIndexingPressure(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "enabled": + return ec.fieldContext_OpenSearchShardIndexingPressure_enabled(ctx, field) + case "enforced": + return ec.fieldContext_OpenSearchShardIndexingPressure_enforced(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type OpenSearchShardIndexingPressure", field.Name) +} + func (ec *executionContext) childFields_OpenSearchUpdatedActivityLogEntryData(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "updatedFields": diff --git a/internal/graph/schema/opensearch.graphqls b/internal/graph/schema/opensearch.graphqls index ec66d052c..b08c89985 100644 --- a/internal/graph/schema/opensearch.graphqls +++ b/internal/graph/schema/opensearch.graphqls @@ -79,6 +79,8 @@ type OpenSearch implements Persistence & Node { memory: OpenSearchMemory! "Available storage in GB." storageGB: Int! + "Shard indexing back pressure settings for the OpenSearch instance." + shardIndexingPressure: OpenSearchShardIndexingPressure! "Issues that affects the instance." issues( "Get the first n items in the connection. This can be used in combination with the after parameter." @@ -109,6 +111,20 @@ enum OpenSearchState { UNKNOWN } +type OpenSearchShardIndexingPressure { + "Whether shard indexing back pressure is enabled." + enabled: Boolean! + "Whether back pressure runs in enforced mode. In enforced mode requests that may degrade cluster performance are rejected; in shadow mode (enforced false) metrics are tracked but no requests are rejected." + enforced: Boolean! +} + +input OpenSearchShardIndexingPressureInput { + "Enable or disable shard indexing back pressure. Defaults to false." + enabled: Boolean! + "Run back pressure in enforced mode. Defaults to false (shadow mode)." + enforced: Boolean! +} + type OpenSearchAccess { workload: Workload! access: String! @@ -203,6 +219,8 @@ input CreateOpenSearchInput { version: OpenSearchMajorVersion! "Available storage in GB." storageGB: Int! + "Shard indexing back pressure settings. Defaults to disabled." + shardIndexingPressure: OpenSearchShardIndexingPressureInput } type CreateOpenSearchPayload { @@ -225,6 +243,8 @@ input UpdateOpenSearchInput { version: OpenSearchMajorVersion! "Available storage in GB." storageGB: Int! + "Shard indexing back pressure settings. Defaults to disabled." + shardIndexingPressure: OpenSearchShardIndexingPressureInput } type UpdateOpenSearchPayload { diff --git a/internal/persistence/opensearch/models.go b/internal/persistence/opensearch/models.go index ea9093792..e991619e6 100644 --- a/internal/persistence/opensearch/models.go +++ b/internal/persistence/opensearch/models.go @@ -36,16 +36,17 @@ type ( ) type OpenSearch struct { - Name string `json:"name"` - Status *naiscrd.OpenSearchStatus `json:"status"` - TerminationProtection bool `json:"terminationProtection"` - Tier OpenSearchTier `json:"tier"` - Memory OpenSearchMemory `json:"memory"` - StorageGB StorageGB `json:"storageGB"` - TeamSlug slug.Slug `json:"-"` - EnvironmentName string `json:"-"` - WorkloadReference *workload.Reference `json:"-"` - MajorVersion OpenSearchMajorVersion `json:"-"` + Name string `json:"name"` + Status *naiscrd.OpenSearchStatus `json:"status"` + TerminationProtection bool `json:"terminationProtection"` + Tier OpenSearchTier `json:"tier"` + Memory OpenSearchMemory `json:"memory"` + StorageGB StorageGB `json:"storageGB"` + ShardIndexingPressure OpenSearchShardIndexingPressure `json:"shardIndexingPressure"` + TeamSlug slug.Slug `json:"-"` + EnvironmentName string `json:"-"` + WorkloadReference *workload.Reference `json:"-"` + MajorVersion OpenSearchMajorVersion `json:"-"` } func (OpenSearch) IsPersistence() {} @@ -85,6 +86,16 @@ type OpenSearchAccess struct { WorkloadReference *workload.Reference `json:"-"` } +type OpenSearchShardIndexingPressure struct { + Enabled bool `json:"enabled"` + Enforced bool `json:"enforced"` +} + +type OpenSearchShardIndexingPressureInput struct { + Enabled bool `json:"enabled"` + Enforced bool `json:"enforced"` +} + type OpenSearchStatus struct { State string `json:"state"` Conditions []metav1.Condition `json:"conditions"` @@ -199,6 +210,9 @@ func toOpenSearch(u *unstructured.Unstructured, envName string) (*OpenSearch, er } } + shardIndexingPressureEnabled, _, _ := unstructured.NestedBool(u.Object, specShardIndexingPressureEnabled...) + shardIndexingPressureEnforced, _, _ := unstructured.NestedBool(u.Object, specShardIndexingPressureEnforced...) + return &OpenSearch{ Name: name, EnvironmentName: envName, @@ -214,22 +228,33 @@ func toOpenSearch(u *unstructured.Unstructured, envName string) (*OpenSearch, er Memory: machine.Memory, MajorVersion: majorVersion, StorageGB: storageGB, + ShardIndexingPressure: OpenSearchShardIndexingPressure{ + Enabled: shardIndexingPressureEnabled, + Enforced: shardIndexingPressureEnforced, + }, }, nil } func toOpenSearchFromNais(o *naiscrd.OpenSearch, envName string) (*OpenSearch, error) { majorVersion := fromMapperatorVersion(o.Spec.Version) + shardIndexingPressure := OpenSearchShardIndexingPressure{} + if o.Spec.ShardIndexingPressure != nil { + shardIndexingPressure.Enabled = o.Spec.ShardIndexingPressure.Enabled + shardIndexingPressure.Enforced = o.Spec.ShardIndexingPressure.Enforced + } + return &OpenSearch{ - Name: o.Name, - EnvironmentName: envName, - Status: o.Status, - TeamSlug: slug.Slug(o.Namespace), - WorkloadReference: workload.ReferenceFromOwnerReferences(o.OwnerReferences), - Tier: fromMapperatorTier(o.Spec.Tier), - Memory: fromMapperatorMemory(o.Spec.Memory), - MajorVersion: majorVersion, - StorageGB: StorageGB(o.Spec.StorageGB), + Name: o.Name, + EnvironmentName: envName, + Status: o.Status, + TeamSlug: slug.Slug(o.Namespace), + WorkloadReference: workload.ReferenceFromOwnerReferences(o.OwnerReferences), + Tier: fromMapperatorTier(o.Spec.Tier), + Memory: fromMapperatorMemory(o.Spec.Memory), + MajorVersion: majorVersion, + StorageGB: StorageGB(o.Spec.StorageGB), + ShardIndexingPressure: shardIndexingPressure, }, nil } @@ -270,10 +295,11 @@ func (o *OpenSearchMetadataInput) ValidationErrors(ctx context.Context) *validat type OpenSearchInput struct { OpenSearchMetadataInput - Tier OpenSearchTier `json:"tier"` - Memory OpenSearchMemory `json:"memory"` - Version OpenSearchMajorVersion `json:"version"` - StorageGB StorageGB `json:"storageGB"` + Tier OpenSearchTier `json:"tier"` + Memory OpenSearchMemory `json:"memory"` + Version OpenSearchMajorVersion `json:"version"` + StorageGB StorageGB `json:"storageGB"` + ShardIndexingPressure *OpenSearchShardIndexingPressureInput `json:"shardIndexingPressure,omitempty"` } func (o *OpenSearchInput) Validate(ctx context.Context) error { diff --git a/internal/persistence/opensearch/queries.go b/internal/persistence/opensearch/queries.go index ccfa0ea44..7a1cdd3fb 100644 --- a/internal/persistence/opensearch/queries.go +++ b/internal/persistence/opensearch/queries.go @@ -30,6 +30,9 @@ var ( specDiskSpace = []string{"spec", "disk_space"} specTerminationProtection = []string{"spec", "terminationProtection"} specOpenSearchVersion = []string{"spec", "userConfig", "opensearch_version"} + + specShardIndexingPressureEnabled = []string{"spec", "userConfig", "opensearch", "shard_indexing_pressure", "enabled"} + specShardIndexingPressureEnforced = []string{"spec", "userConfig", "opensearch", "shard_indexing_pressure", "enforced"} ) func GetByIdent(ctx context.Context, id ident.Ident) (*OpenSearch, error) { @@ -215,6 +218,13 @@ func Create(ctx context.Context, input CreateOpenSearchInput) (*CreateOpenSearch res.SetAnnotations(kubernetes.WithCommonAnnotations(nil, authz.ActorFromContext(ctx).User.Identity())) kubernetes.SetManagedByConsoleLabel(res) + if input.ShardIndexingPressure != nil { + res.Spec.ShardIndexingPressure = &naiscrd.OpenSearchShardIndexingPressure{ + Enabled: input.ShardIndexingPressure.Enabled, + Enforced: input.ShardIndexingPressure.Enforced, + } + } + obj, err := kubernetes.ToUnstructured(res) if err != nil { return nil, err @@ -275,6 +285,7 @@ func Update(ctx context.Context, input UpdateOpenSearchInput) (*UpdateOpenSearch updateMemory, updateVersion, updateStorage, + updateShardIndexingPressure, } for _, f := range updateFuncs { @@ -467,6 +478,46 @@ func updateStorage(openSearch *naiscrd.OpenSearch, input UpdateOpenSearchInput) return changes, nil } +func updateShardIndexingPressure(openSearch *naiscrd.OpenSearch, input UpdateOpenSearchInput) ([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, error) { + if input.ShardIndexingPressure == nil { + return nil, nil + } + + var oldEnabled, oldEnforced bool + if openSearch.Spec.ShardIndexingPressure != nil { + oldEnabled = openSearch.Spec.ShardIndexingPressure.Enabled + oldEnforced = openSearch.Spec.ShardIndexingPressure.Enforced + } + + changes := make([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, 0) + + if oldEnabled != input.ShardIndexingPressure.Enabled { + changes = append(changes, &OpenSearchUpdatedActivityLogEntryDataUpdatedField{ + Field: "shardIndexingPressure.enabled", + OldValue: new(strconv.FormatBool(oldEnabled)), + NewValue: new(strconv.FormatBool(input.ShardIndexingPressure.Enabled)), + }) + } + if oldEnforced != input.ShardIndexingPressure.Enforced { + changes = append(changes, &OpenSearchUpdatedActivityLogEntryDataUpdatedField{ + Field: "shardIndexingPressure.enforced", + OldValue: new(strconv.FormatBool(oldEnforced)), + NewValue: new(strconv.FormatBool(input.ShardIndexingPressure.Enforced)), + }) + } + + if len(changes) == 0 { + return nil, nil + } + + openSearch.Spec.ShardIndexingPressure = &naiscrd.OpenSearchShardIndexingPressure{ + Enabled: input.ShardIndexingPressure.Enabled, + Enforced: input.ShardIndexingPressure.Enforced, + } + + return changes, nil +} + func Delete(ctx context.Context, input DeleteOpenSearchInput) (*DeleteOpenSearchPayload, error) { if err := input.Validate(ctx); err != nil { return nil, err From 9ee2299f116fc9576e14d4f6ad5ce1f5f021b5b8 Mon Sep 17 00:00:00 2001 From: Trong Huu Nguyen Date: Wed, 3 Jun 2026 10:18:03 +0200 Subject: [PATCH 15/17] feat(opensearch): support indices.queryBoolMaxClauseCount --- .../dev/someteamname/opensearch.yaml | 1 + integration_tests/opensearch_crud.lua | 77 +++++++++ internal/graph/gengql/opensearch.generated.go | 156 +++++++++++++++++- internal/graph/gengql/root_.generated.go | 46 ++++++ internal/graph/schema/opensearch.graphqls | 16 ++ internal/persistence/opensearch/models.go | 29 ++++ internal/persistence/opensearch/queries.go | 51 ++++++ 7 files changed, 374 insertions(+), 2 deletions(-) diff --git a/integration_tests/k8s_resources/opensearch_crud/dev/someteamname/opensearch.yaml b/integration_tests/k8s_resources/opensearch_crud/dev/someteamname/opensearch.yaml index b9f041651..d5aac0ffc 100644 --- a/integration_tests/k8s_resources/opensearch_crud/dev/someteamname/opensearch.yaml +++ b/integration_tests/k8s_resources/opensearch_crud/dev/someteamname/opensearch.yaml @@ -37,6 +37,7 @@ spec: userConfig: opensearch_version: "2" opensearch: + indices_query_bool_max_clause_count: 512 shard_indexing_pressure: enabled: true enforced: true diff --git a/integration_tests/opensearch_crud.lua b/integration_tests/opensearch_crud.lua index 10f17ebc9..1059264c5 100644 --- a/integration_tests/opensearch_crud.lua +++ b/integration_tests/opensearch_crud.lua @@ -261,6 +261,45 @@ Test.gql("Create opensearch with invalid storage capacity increment", function(t } end) +Test.gql("Create opensearch with out-of-range query bool max clause count", function(t) + t.addHeader("x-user-email", user:email()) + t.query [[ + mutation CreateOpenSearch { + createOpenSearch( + input: { + name: "foobar" + environmentName: "dev" + teamSlug: "someteamname" + tier: HIGH_AVAILABILITY + memory: GB_4 + version: V2 + storageGB: 240 + indices: { queryBoolMaxClauseCount: 8192 } + } + ) { + openSearch { + name + } + } + } + ]] + + t.check { + errors = { + { + extensions = { + field = "queryBoolMaxClauseCount", + }, + message = "Query bool max clause count must be between 64 and 4096.", + path = { + "createOpenSearch", + }, + }, + }, + data = Null, + } +end) + Test.k8s("Validate OpenSearch resource", function(t) t.check("nais.io/v1", "opensearches", "dev", mainTeam:slug(), "foobar", { apiVersion = "nais.io/v1", @@ -430,6 +469,7 @@ Test.gql("Update OpenSearch as team-member", function(t) version: V2 storageGB: 1020 shardIndexingPressure: { enabled: true, enforced: true } + indices: { queryBoolMaxClauseCount: 2048 } } ) { openSearch { @@ -438,6 +478,9 @@ Test.gql("Update OpenSearch as team-member", function(t) enabled enforced } + indices { + queryBoolMaxClauseCount + } } } } @@ -452,6 +495,9 @@ Test.gql("Update OpenSearch as team-member", function(t) enabled = true, enforced = true, }, + indices = { + queryBoolMaxClauseCount = 2048, + }, }, }, }, @@ -483,6 +529,10 @@ Test.k8s("Validate OpenSearch resource after update", function(t) enabled = true, enforced = true, }, + indices = { + -- TODO: more precise assertion? + queryBoolMaxClauseCount = NotNull(), + }, }, }) end) @@ -502,6 +552,9 @@ Test.gql("List opensearches for team", function(t) enabled enforced } + indices { + queryBoolMaxClauseCount + } } } } @@ -521,6 +574,9 @@ Test.gql("List opensearches for team", function(t) enabled = true, enforced = true, }, + indices = { + queryBoolMaxClauseCount = 2048, + }, }, { name = "foobar-hobbyist", @@ -530,6 +586,9 @@ Test.gql("List opensearches for team", function(t) enabled = false, enforced = false, }, + indices = { + queryBoolMaxClauseCount = Null, + }, }, { name = "noversion", @@ -539,6 +598,9 @@ Test.gql("List opensearches for team", function(t) enabled = false, enforced = false, }, + indices = { + queryBoolMaxClauseCount = Null, + }, }, { name = "opensearch-someteamname-hobbyist-not-managed", @@ -548,6 +610,9 @@ Test.gql("List opensearches for team", function(t) enabled = false, enforced = false, }, + indices = { + queryBoolMaxClauseCount = Null, + }, }, { name = "opensearch-someteamname-not-managed", @@ -557,6 +622,9 @@ Test.gql("List opensearches for team", function(t) enabled = true, enforced = true, }, + indices = { + queryBoolMaxClauseCount = 512, + }, }, }, }, @@ -695,6 +763,10 @@ Test.k8s("Validate hobbyist OpenSearch resource after update", function(t) enabled = true, enforced = true, }, + indices = { + -- TODO: more precise assertion? + queryBoolMaxClauseCount = NotNull(), + }, }, }) end) @@ -974,6 +1046,11 @@ Test.gql("Verify activity log for opensearch operations", function(t) oldValue = "false", newValue = "true", }, + { + field = "indices.queryBoolMaxClauseCount", + oldValue = Null, + newValue = "2048", + }, }, }, }, diff --git a/internal/graph/gengql/opensearch.generated.go b/internal/graph/gengql/opensearch.generated.go index 191156b39..7a1266ef5 100644 --- a/internal/graph/gengql/opensearch.generated.go +++ b/internal/graph/gengql/opensearch.generated.go @@ -687,6 +687,38 @@ func (ec *executionContext) fieldContext_OpenSearch_shardIndexingPressure(_ cont return fc, nil } +func (ec *executionContext) _OpenSearch_indices(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearch) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_OpenSearch_indices(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Indices, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v opensearch.OpenSearchIndices) graphql.Marshaler { + return ec.marshalNOpenSearchIndices2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchIndices(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_OpenSearch_indices(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "OpenSearch", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_OpenSearchIndices(ctx, field) + }, + } + return fc, nil +} + func (ec *executionContext) _OpenSearch_issues(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearch) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -1679,6 +1711,29 @@ func (ec *executionContext) fieldContext_OpenSearchEdge_node(_ context.Context, return fc, nil } +func (ec *executionContext) _OpenSearchIndices_queryBoolMaxClauseCount(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearchIndices) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_OpenSearchIndices_queryBoolMaxClauseCount(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.QueryBoolMaxClauseCount, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *int) graphql.Marshaler { + return ec.marshalOInt2ᚖint(ctx, selections, v) + }, + true, + false, + ) +} +func (ec *executionContext) fieldContext_OpenSearchIndices_queryBoolMaxClauseCount(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("OpenSearchIndices", field, false, false, errors.New("field of type Int does not have child fields")) +} + func (ec *executionContext) _OpenSearchShardIndexingPressure_enabled(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearchShardIndexingPressure) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -2216,7 +2271,7 @@ func (ec *executionContext) unmarshalInputCreateOpenSearchInput(ctx context.Cont asMap[k] = v } - fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "version", "storageGB", "shardIndexingPressure"} + fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "version", "storageGB", "shardIndexingPressure", "indices"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -2279,6 +2334,13 @@ func (ec *executionContext) unmarshalInputCreateOpenSearchInput(ctx context.Cont return it, err } it.ShardIndexingPressure = data + case "indices": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("indices")) + data, err := ec.unmarshalOOpenSearchIndicesInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchIndicesInput(ctx, v) + if err != nil { + return it, err + } + it.Indices = data } } return it, nil @@ -2365,6 +2427,36 @@ func (ec *executionContext) unmarshalInputOpenSearchAccessOrder(ctx context.Cont return it, nil } +func (ec *executionContext) unmarshalInputOpenSearchIndicesInput(ctx context.Context, obj any) (opensearch.OpenSearchIndicesInput, error) { + var it opensearch.OpenSearchIndicesInput + if obj == nil { + return it, nil + } + + asMap := map[string]any{} + for k, v := range obj.(map[string]any) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"queryBoolMaxClauseCount"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "queryBoolMaxClauseCount": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("queryBoolMaxClauseCount")) + data, err := ec.unmarshalOInt2ᚖint(ctx, v) + if err != nil { + return it, err + } + it.QueryBoolMaxClauseCount = data + } + } + return it, nil +} + func (ec *executionContext) unmarshalInputOpenSearchOrder(ctx context.Context, obj any) (opensearch.OpenSearchOrder, error) { var it opensearch.OpenSearchOrder if obj == nil { @@ -2450,7 +2542,7 @@ func (ec *executionContext) unmarshalInputUpdateOpenSearchInput(ctx context.Cont asMap[k] = v } - fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "version", "storageGB", "shardIndexingPressure"} + fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "version", "storageGB", "shardIndexingPressure", "indices"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -2513,6 +2605,13 @@ func (ec *executionContext) unmarshalInputUpdateOpenSearchInput(ctx context.Cont return it, err } it.ShardIndexingPressure = data + case "indices": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("indices")) + data, err := ec.unmarshalOOpenSearchIndicesInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchIndicesInput(ctx, v) + if err != nil { + return it, err + } + it.Indices = data } } return it, nil @@ -2935,6 +3034,11 @@ func (ec *executionContext) _OpenSearch(ctx context.Context, sel ast.SelectionSe if out.Values[i] == graphql.Null { atomic.AddUint32(&out.Invalids, 1) } + case "indices": + out.Values[i] = ec._OpenSearch_indices(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } case "issues": field := field @@ -3564,6 +3668,42 @@ func (ec *executionContext) _OpenSearchEdge(ctx context.Context, sel ast.Selecti return out } +var openSearchIndicesImplementors = []string{"OpenSearchIndices"} + +func (ec *executionContext) _OpenSearchIndices(ctx context.Context, sel ast.SelectionSet, obj *opensearch.OpenSearchIndices) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, openSearchIndicesImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("OpenSearchIndices") + case "queryBoolMaxClauseCount": + out.Values[i] = ec._OpenSearchIndices_queryBoolMaxClauseCount(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.Deferred, int32(min(len(deferred), math.MaxInt32))) + + for label, dfs := range deferred { + ec.ProcessDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var openSearchShardIndexingPressureImplementors = []string{"OpenSearchShardIndexingPressure"} func (ec *executionContext) _OpenSearchShardIndexingPressure(ctx context.Context, sel ast.SelectionSet, obj *opensearch.OpenSearchShardIndexingPressure) graphql.Marshaler { @@ -4090,6 +4230,10 @@ func (ec *executionContext) marshalNOpenSearchEdge2ᚕgithubᚗcomᚋnaisᚋapi return ret } +func (ec *executionContext) marshalNOpenSearchIndices2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchIndices(ctx context.Context, sel ast.SelectionSet, v opensearch.OpenSearchIndices) graphql.Marshaler { + return ec._OpenSearchIndices(ctx, sel, &v) +} + func (ec *executionContext) unmarshalNOpenSearchMajorVersion2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchMajorVersion(ctx context.Context, v any) (opensearch.OpenSearchMajorVersion, error) { var res opensearch.OpenSearchMajorVersion err := res.UnmarshalGQL(v) @@ -4242,6 +4386,14 @@ func (ec *executionContext) unmarshalOOpenSearchAccessOrder2ᚖgithubᚗcomᚋna return &res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) unmarshalOOpenSearchIndicesInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchIndicesInput(ctx context.Context, v any) (*opensearch.OpenSearchIndicesInput, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputOpenSearchIndicesInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) unmarshalOOpenSearchOrder2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchOrder(ctx context.Context, v any) (*opensearch.OpenSearchOrder, error) { if v == nil { return nil, nil diff --git a/internal/graph/gengql/root_.generated.go b/internal/graph/gengql/root_.generated.go index a7b500056..78da13740 100644 --- a/internal/graph/gengql/root_.generated.go +++ b/internal/graph/gengql/root_.generated.go @@ -1547,6 +1547,7 @@ type ComplexityRoot struct { Cost func(childComplexity int) int Environment func(childComplexity int) int ID func(childComplexity int) int + Indices func(childComplexity int) int Issues func(childComplexity int, first *int, after *pagination.Cursor, last *int, before *pagination.Cursor, orderBy *issue.IssueOrder, filter *issue.ResourceIssueFilter) int Maintenance func(childComplexity int) int Memory func(childComplexity int) int @@ -1623,6 +1624,10 @@ type ComplexityRoot struct { Node func(childComplexity int) int } + OpenSearchIndices struct { + QueryBoolMaxClauseCount func(childComplexity int) int + } + OpenSearchIssue struct { Event func(childComplexity int) int ID func(childComplexity int) int @@ -9683,6 +9688,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.OpenSearch.ID(childComplexity), true + case "OpenSearch.indices": + if e.ComplexityRoot.OpenSearch.Indices == nil { + break + } + + return e.ComplexityRoot.OpenSearch.Indices(childComplexity), true + case "OpenSearch.issues": if e.ComplexityRoot.OpenSearch.Issues == nil { break @@ -10017,6 +10029,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.OpenSearchEdge.Node(childComplexity), true + case "OpenSearchIndices.queryBoolMaxClauseCount": + if e.ComplexityRoot.OpenSearchIndices.QueryBoolMaxClauseCount == nil { + break + } + + return e.ComplexityRoot.OpenSearchIndices.QueryBoolMaxClauseCount(childComplexity), true + case "OpenSearchIssue.event": if e.ComplexityRoot.OpenSearchIssue.Event == nil { break @@ -18538,6 +18557,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputMetricsQueryInput, ec.unmarshalInputMetricsRangeInput, ec.unmarshalInputOpenSearchAccessOrder, + ec.unmarshalInputOpenSearchIndicesInput, ec.unmarshalInputOpenSearchOrder, ec.unmarshalInputOpenSearchShardIndexingPressureInput, ec.unmarshalInputPostgresInstanceOrder, @@ -23898,6 +23918,8 @@ type OpenSearch implements Persistence & Node { storageGB: Int! "Shard indexing back pressure settings for the OpenSearch instance." shardIndexingPressure: OpenSearchShardIndexingPressure! + "Index settings for the OpenSearch instance." + indices: OpenSearchIndices! "Issues that affects the instance." issues( "Get the first n items in the connection. This can be used in combination with the after parameter." @@ -23942,6 +23964,16 @@ input OpenSearchShardIndexingPressureInput { enforced: Boolean! } +type OpenSearchIndices { + "Maximum number of clauses a Lucene BooleanQuery can contain. When not set, the instance uses the default of 1024. Increasing this value may cause performance issues." + queryBoolMaxClauseCount: Int +} + +input OpenSearchIndicesInput { + "Maximum number of clauses a Lucene BooleanQuery can contain. Must be between 64 and 4096. When not set, the instance uses the default of 1024." + queryBoolMaxClauseCount: Int +} + type OpenSearchAccess { workload: Workload! access: String! @@ -24038,6 +24070,8 @@ input CreateOpenSearchInput { storageGB: Int! "Shard indexing back pressure settings. Defaults to disabled." shardIndexingPressure: OpenSearchShardIndexingPressureInput + "Index settings for the OpenSearch instance." + indices: OpenSearchIndicesInput } type CreateOpenSearchPayload { @@ -24062,6 +24096,8 @@ input UpdateOpenSearchInput { storageGB: Int! "Shard indexing back pressure settings. Defaults to disabled." shardIndexingPressure: OpenSearchShardIndexingPressureInput + "Index settings for the OpenSearch instance." + indices: OpenSearchIndicesInput } type UpdateOpenSearchPayload { @@ -33386,6 +33422,8 @@ func (ec *executionContext) childFields_OpenSearch(ctx context.Context, field gr return ec.fieldContext_OpenSearch_storageGB(ctx, field) case "shardIndexingPressure": return ec.fieldContext_OpenSearch_shardIndexingPressure(ctx, field) + case "indices": + return ec.fieldContext_OpenSearch_indices(ctx, field) case "issues": return ec.fieldContext_OpenSearch_issues(ctx, field) case "activityLog": @@ -33476,6 +33514,14 @@ func (ec *executionContext) childFields_OpenSearchEdge(ctx context.Context, fiel return nil, fmt.Errorf("no field named %q was found under type OpenSearchEdge", field.Name) } +func (ec *executionContext) childFields_OpenSearchIndices(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "queryBoolMaxClauseCount": + return ec.fieldContext_OpenSearchIndices_queryBoolMaxClauseCount(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type OpenSearchIndices", field.Name) +} + func (ec *executionContext) childFields_OpenSearchMaintenance(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "window": diff --git a/internal/graph/schema/opensearch.graphqls b/internal/graph/schema/opensearch.graphqls index b08c89985..3c72d8828 100644 --- a/internal/graph/schema/opensearch.graphqls +++ b/internal/graph/schema/opensearch.graphqls @@ -81,6 +81,8 @@ type OpenSearch implements Persistence & Node { storageGB: Int! "Shard indexing back pressure settings for the OpenSearch instance." shardIndexingPressure: OpenSearchShardIndexingPressure! + "Index settings for the OpenSearch instance." + indices: OpenSearchIndices! "Issues that affects the instance." issues( "Get the first n items in the connection. This can be used in combination with the after parameter." @@ -125,6 +127,16 @@ input OpenSearchShardIndexingPressureInput { enforced: Boolean! } +type OpenSearchIndices { + "Maximum number of clauses a Lucene BooleanQuery can contain. When not set, the instance uses the default of 1024. Increasing this value may cause performance issues." + queryBoolMaxClauseCount: Int +} + +input OpenSearchIndicesInput { + "Maximum number of clauses a Lucene BooleanQuery can contain. Must be between 64 and 4096. When not set, the instance uses the default of 1024." + queryBoolMaxClauseCount: Int +} + type OpenSearchAccess { workload: Workload! access: String! @@ -221,6 +233,8 @@ input CreateOpenSearchInput { storageGB: Int! "Shard indexing back pressure settings. Defaults to disabled." shardIndexingPressure: OpenSearchShardIndexingPressureInput + "Index settings for the OpenSearch instance." + indices: OpenSearchIndicesInput } type CreateOpenSearchPayload { @@ -245,6 +259,8 @@ input UpdateOpenSearchInput { storageGB: Int! "Shard indexing back pressure settings. Defaults to disabled." shardIndexingPressure: OpenSearchShardIndexingPressureInput + "Index settings for the OpenSearch instance." + indices: OpenSearchIndicesInput } type UpdateOpenSearchPayload { diff --git a/internal/persistence/opensearch/models.go b/internal/persistence/opensearch/models.go index e991619e6..53d04f3ba 100644 --- a/internal/persistence/opensearch/models.go +++ b/internal/persistence/opensearch/models.go @@ -43,6 +43,7 @@ type OpenSearch struct { Memory OpenSearchMemory `json:"memory"` StorageGB StorageGB `json:"storageGB"` ShardIndexingPressure OpenSearchShardIndexingPressure `json:"shardIndexingPressure"` + Indices OpenSearchIndices `json:"indices"` TeamSlug slug.Slug `json:"-"` EnvironmentName string `json:"-"` WorkloadReference *workload.Reference `json:"-"` @@ -96,6 +97,14 @@ type OpenSearchShardIndexingPressureInput struct { Enforced bool `json:"enforced"` } +type OpenSearchIndices struct { + QueryBoolMaxClauseCount *int `json:"queryBoolMaxClauseCount,omitempty"` +} + +type OpenSearchIndicesInput struct { + QueryBoolMaxClauseCount *int `json:"queryBoolMaxClauseCount,omitempty"` +} + type OpenSearchStatus struct { State string `json:"state"` Conditions []metav1.Condition `json:"conditions"` @@ -213,6 +222,12 @@ func toOpenSearch(u *unstructured.Unstructured, envName string) (*OpenSearch, er shardIndexingPressureEnabled, _, _ := unstructured.NestedBool(u.Object, specShardIndexingPressureEnabled...) shardIndexingPressureEnforced, _, _ := unstructured.NestedBool(u.Object, specShardIndexingPressureEnforced...) + indices := OpenSearchIndices{} + if v, found, _ := unstructured.NestedNumberAsFloat64(u.Object, specIndicesQueryBoolMaxClauseCount...); found { + n := int(v) + indices.QueryBoolMaxClauseCount = &n + } + return &OpenSearch{ Name: name, EnvironmentName: envName, @@ -232,6 +247,7 @@ func toOpenSearch(u *unstructured.Unstructured, envName string) (*OpenSearch, er Enabled: shardIndexingPressureEnabled, Enforced: shardIndexingPressureEnforced, }, + Indices: indices, }, nil } @@ -244,6 +260,11 @@ func toOpenSearchFromNais(o *naiscrd.OpenSearch, envName string) (*OpenSearch, e shardIndexingPressure.Enforced = o.Spec.ShardIndexingPressure.Enforced } + indices := OpenSearchIndices{} + if o.Spec.Indices != nil { + indices.QueryBoolMaxClauseCount = o.Spec.Indices.QueryBoolMaxClauseCount + } + return &OpenSearch{ Name: o.Name, EnvironmentName: envName, @@ -255,6 +276,7 @@ func toOpenSearchFromNais(o *naiscrd.OpenSearch, envName string) (*OpenSearch, e MajorVersion: majorVersion, StorageGB: StorageGB(o.Spec.StorageGB), ShardIndexingPressure: shardIndexingPressure, + Indices: indices, }, nil } @@ -300,6 +322,7 @@ type OpenSearchInput struct { Version OpenSearchMajorVersion `json:"version"` StorageGB StorageGB `json:"storageGB"` ShardIndexingPressure *OpenSearchShardIndexingPressureInput `json:"shardIndexingPressure,omitempty"` + Indices *OpenSearchIndicesInput `json:"indices,omitempty"` } func (o *OpenSearchInput) Validate(ctx context.Context) error { @@ -315,6 +338,12 @@ func (o *OpenSearchInput) Validate(ctx context.Context) error { verr.Add("version", "Invalid OpenSearch version: %s.", o.Version.String()) } + if o.Indices != nil && o.Indices.QueryBoolMaxClauseCount != nil { + if c := *o.Indices.QueryBoolMaxClauseCount; c < 64 || c > 4096 { + verr.Add("queryBoolMaxClauseCount", "Query bool max clause count must be between 64 and 4096.") + } + } + machine, err := machineTypeFromTierAndMemory(o.Tier, o.Memory) if err != nil { verr.Add("memory", "%s", err) diff --git a/internal/persistence/opensearch/queries.go b/internal/persistence/opensearch/queries.go index 7a1cdd3fb..c6a2de79a 100644 --- a/internal/persistence/opensearch/queries.go +++ b/internal/persistence/opensearch/queries.go @@ -33,6 +33,8 @@ var ( specShardIndexingPressureEnabled = []string{"spec", "userConfig", "opensearch", "shard_indexing_pressure", "enabled"} specShardIndexingPressureEnforced = []string{"spec", "userConfig", "opensearch", "shard_indexing_pressure", "enforced"} + + specIndicesQueryBoolMaxClauseCount = []string{"spec", "userConfig", "opensearch", "indices_query_bool_max_clause_count"} ) func GetByIdent(ctx context.Context, id ident.Ident) (*OpenSearch, error) { @@ -225,6 +227,12 @@ func Create(ctx context.Context, input CreateOpenSearchInput) (*CreateOpenSearch } } + if input.Indices != nil && input.Indices.QueryBoolMaxClauseCount != nil { + res.Spec.Indices = &naiscrd.OpenSearchIndices{ + QueryBoolMaxClauseCount: input.Indices.QueryBoolMaxClauseCount, + } + } + obj, err := kubernetes.ToUnstructured(res) if err != nil { return nil, err @@ -286,6 +294,7 @@ func Update(ctx context.Context, input UpdateOpenSearchInput) (*UpdateOpenSearch updateVersion, updateStorage, updateShardIndexingPressure, + updateIndices, } for _, f := range updateFuncs { @@ -518,6 +527,48 @@ func updateShardIndexingPressure(openSearch *naiscrd.OpenSearch, input UpdateOpe return changes, nil } +func updateIndices(openSearch *naiscrd.OpenSearch, input UpdateOpenSearchInput) ([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, error) { + if input.Indices == nil { + return nil, nil + } + + var oldCount *int + if openSearch.Spec.Indices != nil { + oldCount = openSearch.Spec.Indices.QueryBoolMaxClauseCount + } + newCount := input.Indices.QueryBoolMaxClauseCount + + if equalIntPtr(oldCount, newCount) { + return nil, nil + } + + openSearch.Spec.Indices = &naiscrd.OpenSearchIndices{ + QueryBoolMaxClauseCount: newCount, + } + + return []*OpenSearchUpdatedActivityLogEntryDataUpdatedField{ + { + Field: "indices.queryBoolMaxClauseCount", + OldValue: intPtrToStringPtr(oldCount), + NewValue: intPtrToStringPtr(newCount), + }, + }, nil +} + +func equalIntPtr(a, b *int) bool { + if a == nil || b == nil { + return a == b + } + return *a == *b +} + +func intPtrToStringPtr(v *int) *string { + if v == nil { + return nil + } + return new(strconv.Itoa(*v)) +} + func Delete(ctx context.Context, input DeleteOpenSearchInput) (*DeleteOpenSearchPayload, error) { if err := input.Validate(ctx); err != nil { return nil, err From b9d1adbd343aa92c5e416ba0265246290250e336 Mon Sep 17 00:00:00 2001 From: Trong Huu Nguyen Date: Wed, 3 Jun 2026 10:29:35 +0200 Subject: [PATCH 16/17] feat(opensearch): support http.maxContentLength --- .../dev/someteamname/opensearch.yaml | 1 + integration_tests/opensearch_crud.lua | 114 +++++++++++++ internal/graph/gengql/opensearch.generated.go | 156 +++++++++++++++++- internal/graph/gengql/root_.generated.go | 46 ++++++ internal/graph/schema/opensearch.graphqls | 16 ++ internal/persistence/opensearch/models.go | 34 ++++ internal/persistence/opensearch/queries.go | 47 ++++++ 7 files changed, 412 insertions(+), 2 deletions(-) diff --git a/integration_tests/k8s_resources/opensearch_crud/dev/someteamname/opensearch.yaml b/integration_tests/k8s_resources/opensearch_crud/dev/someteamname/opensearch.yaml index d5aac0ffc..49bd42262 100644 --- a/integration_tests/k8s_resources/opensearch_crud/dev/someteamname/opensearch.yaml +++ b/integration_tests/k8s_resources/opensearch_crud/dev/someteamname/opensearch.yaml @@ -37,6 +37,7 @@ spec: userConfig: opensearch_version: "2" opensearch: + http_max_content_length: 209715200 indices_query_bool_max_clause_count: 512 shard_indexing_pressure: enabled: true diff --git a/integration_tests/opensearch_crud.lua b/integration_tests/opensearch_crud.lua index 1059264c5..23ed4b138 100644 --- a/integration_tests/opensearch_crud.lua +++ b/integration_tests/opensearch_crud.lua @@ -300,6 +300,84 @@ Test.gql("Create opensearch with out-of-range query bool max clause count", func } end) +Test.gql("Create opensearch with invalid max content length", function(t) + t.addHeader("x-user-email", user:email()) + t.query [[ + mutation CreateOpenSearch { + createOpenSearch( + input: { + name: "foobar" + environmentName: "dev" + teamSlug: "someteamname" + tier: HIGH_AVAILABILITY + memory: GB_4 + version: V2 + storageGB: 240 + http: { maxContentLength: "not-a-quantity" } + } + ) { + openSearch { + name + } + } + } + ]] + + t.check { + errors = { + { + extensions = { + field = "maxContentLength", + }, + message = "Max content length must be a valid quantity (e.g. \"100Mi\", \"1Gi\").", + path = { + "createOpenSearch", + }, + }, + }, + data = Null, + } +end) + +Test.gql("Create opensearch with out-of-range max content length", function(t) + t.addHeader("x-user-email", user:email()) + t.query [[ + mutation CreateOpenSearch { + createOpenSearch( + input: { + name: "foobar" + environmentName: "dev" + teamSlug: "someteamname" + tier: HIGH_AVAILABILITY + memory: GB_4 + version: V2 + storageGB: 240 + http: { maxContentLength: "4Gi" } + } + ) { + openSearch { + name + } + } + } + ]] + + t.check { + errors = { + { + extensions = { + field = "maxContentLength", + }, + message = "Max content length must be between 1 byte and 2147483647 bytes (around 2047Mi).", + path = { + "createOpenSearch", + }, + }, + }, + data = Null, + } +end) + Test.k8s("Validate OpenSearch resource", function(t) t.check("nais.io/v1", "opensearches", "dev", mainTeam:slug(), "foobar", { apiVersion = "nais.io/v1", @@ -470,6 +548,7 @@ Test.gql("Update OpenSearch as team-member", function(t) storageGB: 1020 shardIndexingPressure: { enabled: true, enforced: true } indices: { queryBoolMaxClauseCount: 2048 } + http: { maxContentLength: "100Mi" } } ) { openSearch { @@ -481,6 +560,9 @@ Test.gql("Update OpenSearch as team-member", function(t) indices { queryBoolMaxClauseCount } + http { + maxContentLength + } } } } @@ -498,6 +580,9 @@ Test.gql("Update OpenSearch as team-member", function(t) indices = { queryBoolMaxClauseCount = 2048, }, + http = { + maxContentLength = "100Mi", + }, }, }, }, @@ -533,6 +618,9 @@ Test.k8s("Validate OpenSearch resource after update", function(t) -- TODO: more precise assertion? queryBoolMaxClauseCount = NotNull(), }, + http = { + maxContentLength = "100Mi", + }, }, }) end) @@ -555,6 +643,9 @@ Test.gql("List opensearches for team", function(t) indices { queryBoolMaxClauseCount } + http { + maxContentLength + } } } } @@ -577,6 +668,9 @@ Test.gql("List opensearches for team", function(t) indices = { queryBoolMaxClauseCount = 2048, }, + http = { + maxContentLength = "100Mi", + }, }, { name = "foobar-hobbyist", @@ -589,6 +683,9 @@ Test.gql("List opensearches for team", function(t) indices = { queryBoolMaxClauseCount = Null, }, + http = { + maxContentLength = Null, + }, }, { name = "noversion", @@ -601,6 +698,9 @@ Test.gql("List opensearches for team", function(t) indices = { queryBoolMaxClauseCount = Null, }, + http = { + maxContentLength = Null, + }, }, { name = "opensearch-someteamname-hobbyist-not-managed", @@ -613,6 +713,9 @@ Test.gql("List opensearches for team", function(t) indices = { queryBoolMaxClauseCount = Null, }, + http = { + maxContentLength = Null, + }, }, { name = "opensearch-someteamname-not-managed", @@ -625,6 +728,9 @@ Test.gql("List opensearches for team", function(t) indices = { queryBoolMaxClauseCount = 512, }, + http = { + maxContentLength = "200Mi", + }, }, }, }, @@ -767,6 +873,9 @@ Test.k8s("Validate hobbyist OpenSearch resource after update", function(t) -- TODO: more precise assertion? queryBoolMaxClauseCount = NotNull(), }, + http = { + maxContentLength = "100Mi", + }, }, }) end) @@ -1051,6 +1160,11 @@ Test.gql("Verify activity log for opensearch operations", function(t) oldValue = Null, newValue = "2048", }, + { + field = "http.maxContentLength", + oldValue = Null, + newValue = "100Mi", + }, }, }, }, diff --git a/internal/graph/gengql/opensearch.generated.go b/internal/graph/gengql/opensearch.generated.go index 7a1266ef5..8ac916b8e 100644 --- a/internal/graph/gengql/opensearch.generated.go +++ b/internal/graph/gengql/opensearch.generated.go @@ -719,6 +719,38 @@ func (ec *executionContext) fieldContext_OpenSearch_indices(_ context.Context, f return fc, nil } +func (ec *executionContext) _OpenSearch_http(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearch) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_OpenSearch_http(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.HTTP, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v opensearch.OpenSearchHTTP) graphql.Marshaler { + return ec.marshalNOpenSearchHTTP2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchHTTP(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_OpenSearch_http(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "OpenSearch", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_OpenSearchHTTP(ctx, field) + }, + } + return fc, nil +} + func (ec *executionContext) _OpenSearch_issues(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearch) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -1711,6 +1743,29 @@ func (ec *executionContext) fieldContext_OpenSearchEdge_node(_ context.Context, return fc, nil } +func (ec *executionContext) _OpenSearchHTTP_maxContentLength(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearchHTTP) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_OpenSearchHTTP_maxContentLength(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.MaxContentLength, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *string) graphql.Marshaler { + return ec.marshalOString2ᚖstring(ctx, selections, v) + }, + true, + false, + ) +} +func (ec *executionContext) fieldContext_OpenSearchHTTP_maxContentLength(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("OpenSearchHTTP", field, false, false, errors.New("field of type String does not have child fields")) +} + func (ec *executionContext) _OpenSearchIndices_queryBoolMaxClauseCount(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearchIndices) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -2271,7 +2326,7 @@ func (ec *executionContext) unmarshalInputCreateOpenSearchInput(ctx context.Cont asMap[k] = v } - fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "version", "storageGB", "shardIndexingPressure", "indices"} + fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "version", "storageGB", "shardIndexingPressure", "indices", "http"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -2341,6 +2396,13 @@ func (ec *executionContext) unmarshalInputCreateOpenSearchInput(ctx context.Cont return it, err } it.Indices = data + case "http": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("http")) + data, err := ec.unmarshalOOpenSearchHTTPInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchHTTPInput(ctx, v) + if err != nil { + return it, err + } + it.HTTP = data } } return it, nil @@ -2427,6 +2489,36 @@ func (ec *executionContext) unmarshalInputOpenSearchAccessOrder(ctx context.Cont return it, nil } +func (ec *executionContext) unmarshalInputOpenSearchHTTPInput(ctx context.Context, obj any) (opensearch.OpenSearchHTTPInput, error) { + var it opensearch.OpenSearchHTTPInput + if obj == nil { + return it, nil + } + + asMap := map[string]any{} + for k, v := range obj.(map[string]any) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"maxContentLength"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "maxContentLength": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("maxContentLength")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.MaxContentLength = data + } + } + return it, nil +} + func (ec *executionContext) unmarshalInputOpenSearchIndicesInput(ctx context.Context, obj any) (opensearch.OpenSearchIndicesInput, error) { var it opensearch.OpenSearchIndicesInput if obj == nil { @@ -2542,7 +2634,7 @@ func (ec *executionContext) unmarshalInputUpdateOpenSearchInput(ctx context.Cont asMap[k] = v } - fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "version", "storageGB", "shardIndexingPressure", "indices"} + fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "version", "storageGB", "shardIndexingPressure", "indices", "http"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -2612,6 +2704,13 @@ func (ec *executionContext) unmarshalInputUpdateOpenSearchInput(ctx context.Cont return it, err } it.Indices = data + case "http": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("http")) + data, err := ec.unmarshalOOpenSearchHTTPInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchHTTPInput(ctx, v) + if err != nil { + return it, err + } + it.HTTP = data } } return it, nil @@ -3039,6 +3138,11 @@ func (ec *executionContext) _OpenSearch(ctx context.Context, sel ast.SelectionSe if out.Values[i] == graphql.Null { atomic.AddUint32(&out.Invalids, 1) } + case "http": + out.Values[i] = ec._OpenSearch_http(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } case "issues": field := field @@ -3668,6 +3772,42 @@ func (ec *executionContext) _OpenSearchEdge(ctx context.Context, sel ast.Selecti return out } +var openSearchHTTPImplementors = []string{"OpenSearchHTTP"} + +func (ec *executionContext) _OpenSearchHTTP(ctx context.Context, sel ast.SelectionSet, obj *opensearch.OpenSearchHTTP) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, openSearchHTTPImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("OpenSearchHTTP") + case "maxContentLength": + out.Values[i] = ec._OpenSearchHTTP_maxContentLength(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.Deferred, int32(min(len(deferred), math.MaxInt32))) + + for label, dfs := range deferred { + ec.ProcessDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var openSearchIndicesImplementors = []string{"OpenSearchIndices"} func (ec *executionContext) _OpenSearchIndices(ctx context.Context, sel ast.SelectionSet, obj *opensearch.OpenSearchIndices) graphql.Marshaler { @@ -4230,6 +4370,10 @@ func (ec *executionContext) marshalNOpenSearchEdge2ᚕgithubᚗcomᚋnaisᚋapi return ret } +func (ec *executionContext) marshalNOpenSearchHTTP2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchHTTP(ctx context.Context, sel ast.SelectionSet, v opensearch.OpenSearchHTTP) graphql.Marshaler { + return ec._OpenSearchHTTP(ctx, sel, &v) +} + func (ec *executionContext) marshalNOpenSearchIndices2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchIndices(ctx context.Context, sel ast.SelectionSet, v opensearch.OpenSearchIndices) graphql.Marshaler { return ec._OpenSearchIndices(ctx, sel, &v) } @@ -4386,6 +4530,14 @@ func (ec *executionContext) unmarshalOOpenSearchAccessOrder2ᚖgithubᚗcomᚋna return &res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) unmarshalOOpenSearchHTTPInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchHTTPInput(ctx context.Context, v any) (*opensearch.OpenSearchHTTPInput, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputOpenSearchHTTPInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) unmarshalOOpenSearchIndicesInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchIndicesInput(ctx context.Context, v any) (*opensearch.OpenSearchIndicesInput, error) { if v == nil { return nil, nil diff --git a/internal/graph/gengql/root_.generated.go b/internal/graph/gengql/root_.generated.go index 78da13740..595c23753 100644 --- a/internal/graph/gengql/root_.generated.go +++ b/internal/graph/gengql/root_.generated.go @@ -1546,6 +1546,7 @@ type ComplexityRoot struct { ActivityLog func(childComplexity int, first *int, after *pagination.Cursor, last *int, before *pagination.Cursor, filter *activitylog.ActivityLogFilter) int Cost func(childComplexity int) int Environment func(childComplexity int) int + HTTP func(childComplexity int) int ID func(childComplexity int) int Indices func(childComplexity int) int Issues func(childComplexity int, first *int, after *pagination.Cursor, last *int, before *pagination.Cursor, orderBy *issue.IssueOrder, filter *issue.ResourceIssueFilter) int @@ -1624,6 +1625,10 @@ type ComplexityRoot struct { Node func(childComplexity int) int } + OpenSearchHTTP struct { + MaxContentLength func(childComplexity int) int + } + OpenSearchIndices struct { QueryBoolMaxClauseCount func(childComplexity int) int } @@ -9681,6 +9686,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.OpenSearch.Environment(childComplexity), true + case "OpenSearch.http": + if e.ComplexityRoot.OpenSearch.HTTP == nil { + break + } + + return e.ComplexityRoot.OpenSearch.HTTP(childComplexity), true + case "OpenSearch.id": if e.ComplexityRoot.OpenSearch.ID == nil { break @@ -10029,6 +10041,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.OpenSearchEdge.Node(childComplexity), true + case "OpenSearchHTTP.maxContentLength": + if e.ComplexityRoot.OpenSearchHTTP.MaxContentLength == nil { + break + } + + return e.ComplexityRoot.OpenSearchHTTP.MaxContentLength(childComplexity), true + case "OpenSearchIndices.queryBoolMaxClauseCount": if e.ComplexityRoot.OpenSearchIndices.QueryBoolMaxClauseCount == nil { break @@ -18557,6 +18576,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputMetricsQueryInput, ec.unmarshalInputMetricsRangeInput, ec.unmarshalInputOpenSearchAccessOrder, + ec.unmarshalInputOpenSearchHTTPInput, ec.unmarshalInputOpenSearchIndicesInput, ec.unmarshalInputOpenSearchOrder, ec.unmarshalInputOpenSearchShardIndexingPressureInput, @@ -23920,6 +23940,8 @@ type OpenSearch implements Persistence & Node { shardIndexingPressure: OpenSearchShardIndexingPressure! "Index settings for the OpenSearch instance." indices: OpenSearchIndices! + "HTTP settings for the OpenSearch instance." + http: OpenSearchHTTP! "Issues that affects the instance." issues( "Get the first n items in the connection. This can be used in combination with the after parameter." @@ -23974,6 +23996,16 @@ input OpenSearchIndicesInput { queryBoolMaxClauseCount: Int } +type OpenSearchHTTP { + "Maximum content length, in a human-readable quantity (e.g. \"100Mi\", \"1Gi\"), for requests to the OpenSearch HTTP API. When not set, the instance uses the default of 100Mi." + maxContentLength: String +} + +input OpenSearchHTTPInput { + "Maximum content length for requests to the OpenSearch HTTP API. Specified as a human-readable quantity (e.g. \"100Mi\", \"1Gi\"); unitless values are interpreted as bytes. Must be between 1 byte and 2147483647 bytes (around 2047Mi). When not set, the instance uses the default of 100Mi." + maxContentLength: String +} + type OpenSearchAccess { workload: Workload! access: String! @@ -24072,6 +24104,8 @@ input CreateOpenSearchInput { shardIndexingPressure: OpenSearchShardIndexingPressureInput "Index settings for the OpenSearch instance." indices: OpenSearchIndicesInput + "HTTP settings for the OpenSearch instance." + http: OpenSearchHTTPInput } type CreateOpenSearchPayload { @@ -24098,6 +24132,8 @@ input UpdateOpenSearchInput { shardIndexingPressure: OpenSearchShardIndexingPressureInput "Index settings for the OpenSearch instance." indices: OpenSearchIndicesInput + "HTTP settings for the OpenSearch instance." + http: OpenSearchHTTPInput } type UpdateOpenSearchPayload { @@ -33424,6 +33460,8 @@ func (ec *executionContext) childFields_OpenSearch(ctx context.Context, field gr return ec.fieldContext_OpenSearch_shardIndexingPressure(ctx, field) case "indices": return ec.fieldContext_OpenSearch_indices(ctx, field) + case "http": + return ec.fieldContext_OpenSearch_http(ctx, field) case "issues": return ec.fieldContext_OpenSearch_issues(ctx, field) case "activityLog": @@ -33514,6 +33552,14 @@ func (ec *executionContext) childFields_OpenSearchEdge(ctx context.Context, fiel return nil, fmt.Errorf("no field named %q was found under type OpenSearchEdge", field.Name) } +func (ec *executionContext) childFields_OpenSearchHTTP(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "maxContentLength": + return ec.fieldContext_OpenSearchHTTP_maxContentLength(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type OpenSearchHTTP", field.Name) +} + func (ec *executionContext) childFields_OpenSearchIndices(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "queryBoolMaxClauseCount": diff --git a/internal/graph/schema/opensearch.graphqls b/internal/graph/schema/opensearch.graphqls index 3c72d8828..2930b6733 100644 --- a/internal/graph/schema/opensearch.graphqls +++ b/internal/graph/schema/opensearch.graphqls @@ -83,6 +83,8 @@ type OpenSearch implements Persistence & Node { shardIndexingPressure: OpenSearchShardIndexingPressure! "Index settings for the OpenSearch instance." indices: OpenSearchIndices! + "HTTP settings for the OpenSearch instance." + http: OpenSearchHTTP! "Issues that affects the instance." issues( "Get the first n items in the connection. This can be used in combination with the after parameter." @@ -137,6 +139,16 @@ input OpenSearchIndicesInput { queryBoolMaxClauseCount: Int } +type OpenSearchHTTP { + "Maximum content length, in a human-readable quantity (e.g. \"100Mi\", \"1Gi\"), for requests to the OpenSearch HTTP API. When not set, the instance uses the default of 100Mi." + maxContentLength: String +} + +input OpenSearchHTTPInput { + "Maximum content length for requests to the OpenSearch HTTP API. Specified as a human-readable quantity (e.g. \"100Mi\", \"1Gi\"); unitless values are interpreted as bytes. Must be between 1 byte and 2147483647 bytes (around 2047Mi). When not set, the instance uses the default of 100Mi." + maxContentLength: String +} + type OpenSearchAccess { workload: Workload! access: String! @@ -235,6 +247,8 @@ input CreateOpenSearchInput { shardIndexingPressure: OpenSearchShardIndexingPressureInput "Index settings for the OpenSearch instance." indices: OpenSearchIndicesInput + "HTTP settings for the OpenSearch instance." + http: OpenSearchHTTPInput } type CreateOpenSearchPayload { @@ -261,6 +275,8 @@ input UpdateOpenSearchInput { shardIndexingPressure: OpenSearchShardIndexingPressureInput "Index settings for the OpenSearch instance." indices: OpenSearchIndicesInput + "HTTP settings for the OpenSearch instance." + http: OpenSearchHTTPInput } type UpdateOpenSearchPayload { diff --git a/internal/persistence/opensearch/models.go b/internal/persistence/opensearch/models.go index 53d04f3ba..75ff23f1b 100644 --- a/internal/persistence/opensearch/models.go +++ b/internal/persistence/opensearch/models.go @@ -21,6 +21,7 @@ import ( aiven_io_v1alpha1 "github.com/nais/liberator/pkg/apis/aiven.io/v1alpha1" "github.com/nais/pgrator/pkg/api" naiscrd "github.com/nais/pgrator/pkg/api/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -44,6 +45,7 @@ type OpenSearch struct { StorageGB StorageGB `json:"storageGB"` ShardIndexingPressure OpenSearchShardIndexingPressure `json:"shardIndexingPressure"` Indices OpenSearchIndices `json:"indices"` + HTTP OpenSearchHTTP `json:"http"` TeamSlug slug.Slug `json:"-"` EnvironmentName string `json:"-"` WorkloadReference *workload.Reference `json:"-"` @@ -105,6 +107,14 @@ type OpenSearchIndicesInput struct { QueryBoolMaxClauseCount *int `json:"queryBoolMaxClauseCount,omitempty"` } +type OpenSearchHTTP struct { + MaxContentLength *string `json:"maxContentLength,omitempty"` +} + +type OpenSearchHTTPInput struct { + MaxContentLength *string `json:"maxContentLength,omitempty"` +} + type OpenSearchStatus struct { State string `json:"state"` Conditions []metav1.Condition `json:"conditions"` @@ -228,6 +238,12 @@ func toOpenSearch(u *unstructured.Unstructured, envName string) (*OpenSearch, er indices.QueryBoolMaxClauseCount = &n } + http := OpenSearchHTTP{} + if v, found, _ := unstructured.NestedNumberAsFloat64(u.Object, specHTTPMaxContentLength...); found { + s := resource.NewQuantity(int64(v), resource.BinarySI).String() + http.MaxContentLength = &s + } + return &OpenSearch{ Name: name, EnvironmentName: envName, @@ -248,6 +264,7 @@ func toOpenSearch(u *unstructured.Unstructured, envName string) (*OpenSearch, er Enforced: shardIndexingPressureEnforced, }, Indices: indices, + HTTP: http, }, nil } @@ -265,6 +282,12 @@ func toOpenSearchFromNais(o *naiscrd.OpenSearch, envName string) (*OpenSearch, e indices.QueryBoolMaxClauseCount = o.Spec.Indices.QueryBoolMaxClauseCount } + http := OpenSearchHTTP{} + if o.Spec.Http != nil && o.Spec.Http.MaxContentLength != nil { + s := o.Spec.Http.MaxContentLength.String() + http.MaxContentLength = &s + } + return &OpenSearch{ Name: o.Name, EnvironmentName: envName, @@ -277,6 +300,7 @@ func toOpenSearchFromNais(o *naiscrd.OpenSearch, envName string) (*OpenSearch, e StorageGB: StorageGB(o.Spec.StorageGB), ShardIndexingPressure: shardIndexingPressure, Indices: indices, + HTTP: http, }, nil } @@ -323,6 +347,7 @@ type OpenSearchInput struct { StorageGB StorageGB `json:"storageGB"` ShardIndexingPressure *OpenSearchShardIndexingPressureInput `json:"shardIndexingPressure,omitempty"` Indices *OpenSearchIndicesInput `json:"indices,omitempty"` + HTTP *OpenSearchHTTPInput `json:"http,omitempty"` } func (o *OpenSearchInput) Validate(ctx context.Context) error { @@ -344,6 +369,15 @@ func (o *OpenSearchInput) Validate(ctx context.Context) error { } } + if o.HTTP != nil && o.HTTP.MaxContentLength != nil { + q, err := resource.ParseQuantity(*o.HTTP.MaxContentLength) + if err != nil { + verr.Add("maxContentLength", "Max content length must be a valid quantity (e.g. \"100Mi\", \"1Gi\").") + } else if b := q.Value(); b < 1 || b > 2147483647 { + verr.Add("maxContentLength", "Max content length must be between 1 byte and 2147483647 bytes (around 2047Mi).") + } + } + machine, err := machineTypeFromTierAndMemory(o.Tier, o.Memory) if err != nil { verr.Add("memory", "%s", err) diff --git a/internal/persistence/opensearch/queries.go b/internal/persistence/opensearch/queries.go index c6a2de79a..19226bca8 100644 --- a/internal/persistence/opensearch/queries.go +++ b/internal/persistence/opensearch/queries.go @@ -23,6 +23,7 @@ import ( "github.com/nais/pgrator/pkg/api" naiscrd "github.com/nais/pgrator/pkg/api/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -35,6 +36,8 @@ var ( specShardIndexingPressureEnforced = []string{"spec", "userConfig", "opensearch", "shard_indexing_pressure", "enforced"} specIndicesQueryBoolMaxClauseCount = []string{"spec", "userConfig", "opensearch", "indices_query_bool_max_clause_count"} + + specHTTPMaxContentLength = []string{"spec", "userConfig", "opensearch", "http_max_content_length"} ) func GetByIdent(ctx context.Context, id ident.Ident) (*OpenSearch, error) { @@ -233,6 +236,16 @@ func Create(ctx context.Context, input CreateOpenSearchInput) (*CreateOpenSearch } } + if input.HTTP != nil && input.HTTP.MaxContentLength != nil { + q, err := resource.ParseQuantity(*input.HTTP.MaxContentLength) + if err != nil { + return nil, err + } + res.Spec.Http = &naiscrd.OpenSearchHttp{ + MaxContentLength: &q, + } + } + obj, err := kubernetes.ToUnstructured(res) if err != nil { return nil, err @@ -295,6 +308,7 @@ func Update(ctx context.Context, input UpdateOpenSearchInput) (*UpdateOpenSearch updateStorage, updateShardIndexingPressure, updateIndices, + updateHTTP, } for _, f := range updateFuncs { @@ -555,6 +569,39 @@ func updateIndices(openSearch *naiscrd.OpenSearch, input UpdateOpenSearchInput) }, nil } +func updateHTTP(openSearch *naiscrd.OpenSearch, input UpdateOpenSearchInput) ([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, error) { + if input.HTTP == nil || input.HTTP.MaxContentLength == nil { + return nil, nil + } + + newQ, err := resource.ParseQuantity(*input.HTTP.MaxContentLength) + if err != nil { + return nil, err + } + + var oldValue *string + if openSearch.Spec.Http != nil && openSearch.Spec.Http.MaxContentLength != nil { + old := openSearch.Spec.Http.MaxContentLength + if old.Cmp(newQ) == 0 { + return nil, nil + } + s := old.String() + oldValue = &s + } + + openSearch.Spec.Http = &naiscrd.OpenSearchHttp{ + MaxContentLength: &newQ, + } + + return []*OpenSearchUpdatedActivityLogEntryDataUpdatedField{ + { + Field: "http.maxContentLength", + OldValue: oldValue, + NewValue: new(newQ.String()), + }, + }, nil +} + func equalIntPtr(a, b *int) bool { if a == nil || b == nil { return a == b From f85ebda829ec02cefc9f0b7f5389cf1db0ed733d Mon Sep 17 00:00:00 2001 From: Trong Huu Nguyen Date: Wed, 3 Jun 2026 12:10:26 +0200 Subject: [PATCH 17/17] refactor: flatten fields for valkey and opensearch --- integration_tests/opensearch_crud.lua | 139 ++--- integration_tests/valkey_crud.lua | 32 +- internal/graph/gengql/opensearch.generated.go | 516 ++++-------------- internal/graph/gengql/root_.generated.go | 280 +++------- internal/graph/gengql/valkey.generated.go | 151 +---- internal/graph/schema/opensearch.graphqls | 76 +-- internal/graph/schema/valkey.graphqls | 22 +- internal/persistence/opensearch/models.go | 153 +++--- internal/persistence/opensearch/queries.go | 48 +- internal/persistence/valkey/models.go | 30 +- internal/persistence/valkey/queries.go | 14 +- 11 files changed, 386 insertions(+), 1075 deletions(-) diff --git a/integration_tests/opensearch_crud.lua b/integration_tests/opensearch_crud.lua index 23ed4b138..680fa53cb 100644 --- a/integration_tests/opensearch_crud.lua +++ b/integration_tests/opensearch_crud.lua @@ -274,7 +274,7 @@ Test.gql("Create opensearch with out-of-range query bool max clause count", func memory: GB_4 version: V2 storageGB: 240 - indices: { queryBoolMaxClauseCount: 8192 } + indicesQueryBoolMaxClauseCount: 8192 } ) { openSearch { @@ -288,7 +288,7 @@ Test.gql("Create opensearch with out-of-range query bool max clause count", func errors = { { extensions = { - field = "queryBoolMaxClauseCount", + field = "indicesQueryBoolMaxClauseCount", }, message = "Query bool max clause count must be between 64 and 4096.", path = { @@ -313,7 +313,7 @@ Test.gql("Create opensearch with invalid max content length", function(t) memory: GB_4 version: V2 storageGB: 240 - http: { maxContentLength: "not-a-quantity" } + httpMaxContentLength: "not-a-quantity" } ) { openSearch { @@ -327,7 +327,7 @@ Test.gql("Create opensearch with invalid max content length", function(t) errors = { { extensions = { - field = "maxContentLength", + field = "httpMaxContentLength", }, message = "Max content length must be a valid quantity (e.g. \"100Mi\", \"1Gi\").", path = { @@ -352,7 +352,7 @@ Test.gql("Create opensearch with out-of-range max content length", function(t) memory: GB_4 version: V2 storageGB: 240 - http: { maxContentLength: "4Gi" } + httpMaxContentLength: "4Gi" } ) { openSearch { @@ -366,7 +366,7 @@ Test.gql("Create opensearch with out-of-range max content length", function(t) errors = { { extensions = { - field = "maxContentLength", + field = "httpMaxContentLength", }, message = "Max content length must be between 1 byte and 2147483647 bytes (around 2047Mi).", path = { @@ -546,23 +546,18 @@ Test.gql("Update OpenSearch as team-member", function(t) memory: GB_4 version: V2 storageGB: 1020 - shardIndexingPressure: { enabled: true, enforced: true } - indices: { queryBoolMaxClauseCount: 2048 } - http: { maxContentLength: "100Mi" } + shardIndexingPressureEnabled: true + shardIndexingPressureEnforced: true + indicesQueryBoolMaxClauseCount: 2048 + httpMaxContentLength: "100Mi" } ) { openSearch { name - shardIndexingPressure { - enabled - enforced - } - indices { - queryBoolMaxClauseCount - } - http { - maxContentLength - } + shardIndexingPressureEnabled + shardIndexingPressureEnforced + indicesQueryBoolMaxClauseCount + httpMaxContentLength } } } @@ -573,16 +568,10 @@ Test.gql("Update OpenSearch as team-member", function(t) updateOpenSearch = { openSearch = { name = "foobar", - shardIndexingPressure = { - enabled = true, - enforced = true, - }, - indices = { - queryBoolMaxClauseCount = 2048, - }, - http = { - maxContentLength = "100Mi", - }, + shardIndexingPressureEnabled = true, + shardIndexingPressureEnforced = true, + indicesQueryBoolMaxClauseCount = 2048, + httpMaxContentLength = "100Mi", }, }, }, @@ -636,16 +625,10 @@ Test.gql("List opensearches for team", function(t) name tier memory - shardIndexingPressure { - enabled - enforced - } - indices { - queryBoolMaxClauseCount - } - http { - maxContentLength - } + shardIndexingPressureEnabled + shardIndexingPressureEnforced + indicesQueryBoolMaxClauseCount + httpMaxContentLength } } } @@ -661,76 +644,46 @@ Test.gql("List opensearches for team", function(t) name = "foobar", tier = "HIGH_AVAILABILITY", memory = "GB_4", - shardIndexingPressure = { - enabled = true, - enforced = true, - }, - indices = { - queryBoolMaxClauseCount = 2048, - }, - http = { - maxContentLength = "100Mi", - }, + shardIndexingPressureEnabled = true, + shardIndexingPressureEnforced = true, + indicesQueryBoolMaxClauseCount = 2048, + httpMaxContentLength = "100Mi", }, { name = "foobar-hobbyist", tier = "SINGLE_NODE", memory = "GB_2", - shardIndexingPressure = { - enabled = false, - enforced = false, - }, - indices = { - queryBoolMaxClauseCount = Null, - }, - http = { - maxContentLength = Null, - }, + shardIndexingPressureEnabled = false, + shardIndexingPressureEnforced = false, + indicesQueryBoolMaxClauseCount = Null, + httpMaxContentLength = Null, }, { name = "noversion", tier = "SINGLE_NODE", memory = "GB_2", - shardIndexingPressure = { - enabled = false, - enforced = false, - }, - indices = { - queryBoolMaxClauseCount = Null, - }, - http = { - maxContentLength = Null, - }, + shardIndexingPressureEnabled = false, + shardIndexingPressureEnforced = false, + indicesQueryBoolMaxClauseCount = Null, + httpMaxContentLength = Null, }, { name = "opensearch-someteamname-hobbyist-not-managed", tier = "SINGLE_NODE", memory = "GB_2", - shardIndexingPressure = { - enabled = false, - enforced = false, - }, - indices = { - queryBoolMaxClauseCount = Null, - }, - http = { - maxContentLength = Null, - }, + shardIndexingPressureEnabled = false, + shardIndexingPressureEnforced = false, + indicesQueryBoolMaxClauseCount = Null, + httpMaxContentLength = Null, }, { name = "opensearch-someteamname-not-managed", tier = "HIGH_AVAILABILITY", memory = "GB_8", - shardIndexingPressure = { - enabled = true, - enforced = true, - }, - indices = { - queryBoolMaxClauseCount = 512, - }, - http = { - maxContentLength = "200Mi", - }, + shardIndexingPressureEnabled = true, + shardIndexingPressureEnforced = true, + indicesQueryBoolMaxClauseCount = 512, + httpMaxContentLength = "200Mi", }, }, }, @@ -1146,22 +1099,22 @@ Test.gql("Verify activity log for opensearch operations", function(t) newValue = "1020", }, { - field = "shardIndexingPressure.enabled", + field = "shardIndexingPressureEnabled", oldValue = "false", newValue = "true", }, { - field = "shardIndexingPressure.enforced", + field = "shardIndexingPressureEnforced", oldValue = "false", newValue = "true", }, { - field = "indices.queryBoolMaxClauseCount", + field = "indicesQueryBoolMaxClauseCount", oldValue = Null, newValue = "2048", }, { - field = "http.maxContentLength", + field = "httpMaxContentLength", oldValue = Null, newValue = "100Mi", }, diff --git a/integration_tests/valkey_crud.lua b/integration_tests/valkey_crud.lua index 131c31f1d..658fbcd69 100644 --- a/integration_tests/valkey_crud.lua +++ b/integration_tests/valkey_crud.lua @@ -319,15 +319,13 @@ Test.gql("Update Valkey as team-member", function(t) maxMemoryPolicy: ALLKEYS_RANDOM notifyKeyspaceEvents: "Exd" databases: 64 - persistence: { disabled: true } + persistenceDisabled: true } ) { valkey { name databases - persistence { - disabled - } + persistenceDisabled } } } @@ -339,9 +337,7 @@ Test.gql("Update Valkey as team-member", function(t) valkey = { name = "foobar", databases = 64, - persistence = { - disabled = true, - }, + persistenceDisabled = true, }, }, }, @@ -446,9 +442,7 @@ Test.gql("List valkeys for team", function(t) maxMemoryPolicy notifyKeyspaceEvents databases - persistence { - disabled - } + persistenceDisabled } } } @@ -467,9 +461,7 @@ Test.gql("List valkeys for team", function(t) maxMemoryPolicy = "ALLKEYS_RANDOM", notifyKeyspaceEvents = "Exd", databases = 64, - persistence = { - disabled = true, - }, + persistenceDisabled = true, }, { name = "foobar-hobbyist", @@ -478,9 +470,7 @@ Test.gql("List valkeys for team", function(t) maxMemoryPolicy = "", notifyKeyspaceEvents = "", databases = 16, - persistence = { - disabled = false, - }, + persistenceDisabled = false, }, { name = "valkey-someteamname-hobbyist-not-managed", @@ -489,9 +479,7 @@ Test.gql("List valkeys for team", function(t) maxMemoryPolicy = "", notifyKeyspaceEvents = "", databases = 16, - persistence = { - disabled = false, - }, + persistenceDisabled = false, }, { name = "valkey-someteamname-not-managed", @@ -500,9 +488,7 @@ Test.gql("List valkeys for team", function(t) maxMemoryPolicy = "", notifyKeyspaceEvents = "", databases = 16, - persistence = { - disabled = true, - }, + persistenceDisabled = true, }, }, }, @@ -819,7 +805,7 @@ Test.gql("Verify activity log for valkey operations", function(t) newValue = "64", }, { - field = "persistence.disabled", + field = "persistenceDisabled", oldValue = "false", newValue = "true", }, diff --git a/internal/graph/gengql/opensearch.generated.go b/internal/graph/gengql/opensearch.generated.go index ea2db6067..f1f34008b 100644 --- a/internal/graph/gengql/opensearch.generated.go +++ b/internal/graph/gengql/opensearch.generated.go @@ -659,100 +659,96 @@ func (ec *executionContext) fieldContext_OpenSearch_storageGB(_ context.Context, return graphql.NewScalarFieldContext("OpenSearch", field, false, false, errors.New("field of type Int does not have child fields")) } -func (ec *executionContext) _OpenSearch_shardIndexingPressure(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearch) (ret graphql.Marshaler) { +func (ec *executionContext) _OpenSearch_shardIndexingPressureEnabled(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearch) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_OpenSearch_shardIndexingPressure(ctx, field) + return ec.fieldContext_OpenSearch_shardIndexingPressureEnabled(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.ShardIndexingPressure, nil + return obj.ShardIndexingPressureEnabled, nil }, nil, - func(ctx context.Context, selections ast.SelectionSet, v opensearch.OpenSearchShardIndexingPressure) graphql.Marshaler { - return ec.marshalNOpenSearchShardIndexingPressure2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchShardIndexingPressure(ctx, selections, v) + func(ctx context.Context, selections ast.SelectionSet, v bool) graphql.Marshaler { + return ec.marshalNBoolean2bool(ctx, selections, v) }, true, true, ) } -func (ec *executionContext) fieldContext_OpenSearch_shardIndexingPressure(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "OpenSearch", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.childFields_OpenSearchShardIndexingPressure(ctx, field) - }, - } - return fc, nil +func (ec *executionContext) fieldContext_OpenSearch_shardIndexingPressureEnabled(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("OpenSearch", field, false, false, errors.New("field of type Boolean does not have child fields")) } -func (ec *executionContext) _OpenSearch_indices(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearch) (ret graphql.Marshaler) { +func (ec *executionContext) _OpenSearch_shardIndexingPressureEnforced(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearch) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_OpenSearch_indices(ctx, field) + return ec.fieldContext_OpenSearch_shardIndexingPressureEnforced(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.Indices, nil + return obj.ShardIndexingPressureEnforced, nil }, nil, - func(ctx context.Context, selections ast.SelectionSet, v opensearch.OpenSearchIndices) graphql.Marshaler { - return ec.marshalNOpenSearchIndices2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchIndices(ctx, selections, v) + func(ctx context.Context, selections ast.SelectionSet, v bool) graphql.Marshaler { + return ec.marshalNBoolean2bool(ctx, selections, v) }, true, true, ) } -func (ec *executionContext) fieldContext_OpenSearch_indices(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "OpenSearch", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.childFields_OpenSearchIndices(ctx, field) - }, - } - return fc, nil +func (ec *executionContext) fieldContext_OpenSearch_shardIndexingPressureEnforced(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("OpenSearch", field, false, false, errors.New("field of type Boolean does not have child fields")) } -func (ec *executionContext) _OpenSearch_http(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearch) (ret graphql.Marshaler) { +func (ec *executionContext) _OpenSearch_indicesQueryBoolMaxClauseCount(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearch) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_OpenSearch_http(ctx, field) + return ec.fieldContext_OpenSearch_indicesQueryBoolMaxClauseCount(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.HTTP, nil + return obj.IndicesQueryBoolMaxClauseCount, nil }, nil, - func(ctx context.Context, selections ast.SelectionSet, v opensearch.OpenSearchHTTP) graphql.Marshaler { - return ec.marshalNOpenSearchHTTP2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchHTTP(ctx, selections, v) + func(ctx context.Context, selections ast.SelectionSet, v *int) graphql.Marshaler { + return ec.marshalOInt2ᚖint(ctx, selections, v) }, true, - true, + false, ) } -func (ec *executionContext) fieldContext_OpenSearch_http(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "OpenSearch", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.childFields_OpenSearchHTTP(ctx, field) +func (ec *executionContext) fieldContext_OpenSearch_indicesQueryBoolMaxClauseCount(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("OpenSearch", field, false, false, errors.New("field of type Int does not have child fields")) +} + +func (ec *executionContext) _OpenSearch_httpMaxContentLength(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearch) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_OpenSearch_httpMaxContentLength(ctx, field) }, - } - return fc, nil + func(ctx context.Context) (any, error) { + return obj.HTTPMaxContentLength, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *string) graphql.Marshaler { + return ec.marshalOString2ᚖstring(ctx, selections, v) + }, + true, + false, + ) +} +func (ec *executionContext) fieldContext_OpenSearch_httpMaxContentLength(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("OpenSearch", field, false, false, errors.New("field of type String does not have child fields")) } func (ec *executionContext) _OpenSearch_issues(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearch) (ret graphql.Marshaler) { @@ -1843,98 +1839,6 @@ func (ec *executionContext) fieldContext_OpenSearchFacets_tiers(_ context.Contex return fc, nil } -func (ec *executionContext) _OpenSearchHTTP_maxContentLength(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearchHTTP) (ret graphql.Marshaler) { - return graphql.ResolveField( - ctx, - ec.OperationContext, - field, - func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_OpenSearchHTTP_maxContentLength(ctx, field) - }, - func(ctx context.Context) (any, error) { - return obj.MaxContentLength, nil - }, - nil, - func(ctx context.Context, selections ast.SelectionSet, v *string) graphql.Marshaler { - return ec.marshalOString2ᚖstring(ctx, selections, v) - }, - true, - false, - ) -} -func (ec *executionContext) fieldContext_OpenSearchHTTP_maxContentLength(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("OpenSearchHTTP", field, false, false, errors.New("field of type String does not have child fields")) -} - -func (ec *executionContext) _OpenSearchIndices_queryBoolMaxClauseCount(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearchIndices) (ret graphql.Marshaler) { - return graphql.ResolveField( - ctx, - ec.OperationContext, - field, - func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_OpenSearchIndices_queryBoolMaxClauseCount(ctx, field) - }, - func(ctx context.Context) (any, error) { - return obj.QueryBoolMaxClauseCount, nil - }, - nil, - func(ctx context.Context, selections ast.SelectionSet, v *int) graphql.Marshaler { - return ec.marshalOInt2ᚖint(ctx, selections, v) - }, - true, - false, - ) -} -func (ec *executionContext) fieldContext_OpenSearchIndices_queryBoolMaxClauseCount(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("OpenSearchIndices", field, false, false, errors.New("field of type Int does not have child fields")) -} - -func (ec *executionContext) _OpenSearchShardIndexingPressure_enabled(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearchShardIndexingPressure) (ret graphql.Marshaler) { - return graphql.ResolveField( - ctx, - ec.OperationContext, - field, - func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_OpenSearchShardIndexingPressure_enabled(ctx, field) - }, - func(ctx context.Context) (any, error) { - return obj.Enabled, nil - }, - nil, - func(ctx context.Context, selections ast.SelectionSet, v bool) graphql.Marshaler { - return ec.marshalNBoolean2bool(ctx, selections, v) - }, - true, - true, - ) -} -func (ec *executionContext) fieldContext_OpenSearchShardIndexingPressure_enabled(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("OpenSearchShardIndexingPressure", field, false, false, errors.New("field of type Boolean does not have child fields")) -} - -func (ec *executionContext) _OpenSearchShardIndexingPressure_enforced(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearchShardIndexingPressure) (ret graphql.Marshaler) { - return graphql.ResolveField( - ctx, - ec.OperationContext, - field, - func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_OpenSearchShardIndexingPressure_enforced(ctx, field) - }, - func(ctx context.Context) (any, error) { - return obj.Enforced, nil - }, - nil, - func(ctx context.Context, selections ast.SelectionSet, v bool) graphql.Marshaler { - return ec.marshalNBoolean2bool(ctx, selections, v) - }, - true, - true, - ) -} -func (ec *executionContext) fieldContext_OpenSearchShardIndexingPressure_enforced(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("OpenSearchShardIndexingPressure", field, false, false, errors.New("field of type Boolean does not have child fields")) -} - func (ec *executionContext) _OpenSearchTierFacetItem_tier(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearchTierFacetItem) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -2472,7 +2376,7 @@ func (ec *executionContext) unmarshalInputCreateOpenSearchInput(ctx context.Cont asMap[k] = v } - fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "version", "storageGB", "shardIndexingPressure", "indices", "http"} + fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "version", "storageGB", "shardIndexingPressureEnabled", "shardIndexingPressureEnforced", "indicesQueryBoolMaxClauseCount", "httpMaxContentLength"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -2528,27 +2432,34 @@ func (ec *executionContext) unmarshalInputCreateOpenSearchInput(ctx context.Cont return it, err } it.StorageGB = data - case "shardIndexingPressure": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("shardIndexingPressure")) - data, err := ec.unmarshalOOpenSearchShardIndexingPressureInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchShardIndexingPressureInput(ctx, v) + case "shardIndexingPressureEnabled": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("shardIndexingPressureEnabled")) + data, err := ec.unmarshalOBoolean2ᚖbool(ctx, v) + if err != nil { + return it, err + } + it.ShardIndexingPressureEnabled = data + case "shardIndexingPressureEnforced": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("shardIndexingPressureEnforced")) + data, err := ec.unmarshalOBoolean2ᚖbool(ctx, v) if err != nil { return it, err } - it.ShardIndexingPressure = data - case "indices": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("indices")) - data, err := ec.unmarshalOOpenSearchIndicesInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchIndicesInput(ctx, v) + it.ShardIndexingPressureEnforced = data + case "indicesQueryBoolMaxClauseCount": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("indicesQueryBoolMaxClauseCount")) + data, err := ec.unmarshalOInt2ᚖint(ctx, v) if err != nil { return it, err } - it.Indices = data - case "http": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("http")) - data, err := ec.unmarshalOOpenSearchHTTPInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchHTTPInput(ctx, v) + it.IndicesQueryBoolMaxClauseCount = data + case "httpMaxContentLength": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("httpMaxContentLength")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) if err != nil { return it, err } - it.HTTP = data + it.HTTPMaxContentLength = data } } return it, nil @@ -2679,66 +2590,6 @@ func (ec *executionContext) unmarshalInputOpenSearchFilter(ctx context.Context, return it, nil } -func (ec *executionContext) unmarshalInputOpenSearchHTTPInput(ctx context.Context, obj any) (opensearch.OpenSearchHTTPInput, error) { - var it opensearch.OpenSearchHTTPInput - if obj == nil { - return it, nil - } - - asMap := map[string]any{} - for k, v := range obj.(map[string]any) { - asMap[k] = v - } - - fieldsInOrder := [...]string{"maxContentLength"} - for _, k := range fieldsInOrder { - v, ok := asMap[k] - if !ok { - continue - } - switch k { - case "maxContentLength": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("maxContentLength")) - data, err := ec.unmarshalOString2ᚖstring(ctx, v) - if err != nil { - return it, err - } - it.MaxContentLength = data - } - } - return it, nil -} - -func (ec *executionContext) unmarshalInputOpenSearchIndicesInput(ctx context.Context, obj any) (opensearch.OpenSearchIndicesInput, error) { - var it opensearch.OpenSearchIndicesInput - if obj == nil { - return it, nil - } - - asMap := map[string]any{} - for k, v := range obj.(map[string]any) { - asMap[k] = v - } - - fieldsInOrder := [...]string{"queryBoolMaxClauseCount"} - for _, k := range fieldsInOrder { - v, ok := asMap[k] - if !ok { - continue - } - switch k { - case "queryBoolMaxClauseCount": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("queryBoolMaxClauseCount")) - data, err := ec.unmarshalOInt2ᚖint(ctx, v) - if err != nil { - return it, err - } - it.QueryBoolMaxClauseCount = data - } - } - return it, nil -} - func (ec *executionContext) unmarshalInputOpenSearchOrder(ctx context.Context, obj any) (opensearch.OpenSearchOrder, error) { var it opensearch.OpenSearchOrder if obj == nil { @@ -2776,43 +2627,6 @@ func (ec *executionContext) unmarshalInputOpenSearchOrder(ctx context.Context, o return it, nil } -func (ec *executionContext) unmarshalInputOpenSearchShardIndexingPressureInput(ctx context.Context, obj any) (opensearch.OpenSearchShardIndexingPressureInput, error) { - var it opensearch.OpenSearchShardIndexingPressureInput - if obj == nil { - return it, nil - } - - asMap := map[string]any{} - for k, v := range obj.(map[string]any) { - asMap[k] = v - } - - fieldsInOrder := [...]string{"enabled", "enforced"} - for _, k := range fieldsInOrder { - v, ok := asMap[k] - if !ok { - continue - } - switch k { - case "enabled": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("enabled")) - data, err := ec.unmarshalNBoolean2bool(ctx, v) - if err != nil { - return it, err - } - it.Enabled = data - case "enforced": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("enforced")) - data, err := ec.unmarshalNBoolean2bool(ctx, v) - if err != nil { - return it, err - } - it.Enforced = data - } - } - return it, nil -} - func (ec *executionContext) unmarshalInputUpdateOpenSearchInput(ctx context.Context, obj any) (opensearch.UpdateOpenSearchInput, error) { var it opensearch.UpdateOpenSearchInput if obj == nil { @@ -2824,7 +2638,7 @@ func (ec *executionContext) unmarshalInputUpdateOpenSearchInput(ctx context.Cont asMap[k] = v } - fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "version", "storageGB", "shardIndexingPressure", "indices", "http"} + fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "version", "storageGB", "shardIndexingPressureEnabled", "shardIndexingPressureEnforced", "indicesQueryBoolMaxClauseCount", "httpMaxContentLength"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -2880,27 +2694,34 @@ func (ec *executionContext) unmarshalInputUpdateOpenSearchInput(ctx context.Cont return it, err } it.StorageGB = data - case "shardIndexingPressure": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("shardIndexingPressure")) - data, err := ec.unmarshalOOpenSearchShardIndexingPressureInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchShardIndexingPressureInput(ctx, v) + case "shardIndexingPressureEnabled": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("shardIndexingPressureEnabled")) + data, err := ec.unmarshalOBoolean2ᚖbool(ctx, v) + if err != nil { + return it, err + } + it.ShardIndexingPressureEnabled = data + case "shardIndexingPressureEnforced": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("shardIndexingPressureEnforced")) + data, err := ec.unmarshalOBoolean2ᚖbool(ctx, v) if err != nil { return it, err } - it.ShardIndexingPressure = data - case "indices": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("indices")) - data, err := ec.unmarshalOOpenSearchIndicesInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchIndicesInput(ctx, v) + it.ShardIndexingPressureEnforced = data + case "indicesQueryBoolMaxClauseCount": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("indicesQueryBoolMaxClauseCount")) + data, err := ec.unmarshalOInt2ᚖint(ctx, v) if err != nil { return it, err } - it.Indices = data - case "http": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("http")) - data, err := ec.unmarshalOOpenSearchHTTPInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchHTTPInput(ctx, v) + it.IndicesQueryBoolMaxClauseCount = data + case "httpMaxContentLength": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("httpMaxContentLength")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) if err != nil { return it, err } - it.HTTP = data + it.HTTPMaxContentLength = data } } return it, nil @@ -3318,21 +3139,20 @@ func (ec *executionContext) _OpenSearch(ctx context.Context, sel ast.SelectionSe if out.Values[i] == graphql.Null { atomic.AddUint32(&out.Invalids, 1) } - case "shardIndexingPressure": - out.Values[i] = ec._OpenSearch_shardIndexingPressure(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) - } - case "indices": - out.Values[i] = ec._OpenSearch_indices(ctx, field, obj) + case "shardIndexingPressureEnabled": + out.Values[i] = ec._OpenSearch_shardIndexingPressureEnabled(ctx, field, obj) if out.Values[i] == graphql.Null { atomic.AddUint32(&out.Invalids, 1) } - case "http": - out.Values[i] = ec._OpenSearch_http(ctx, field, obj) + case "shardIndexingPressureEnforced": + out.Values[i] = ec._OpenSearch_shardIndexingPressureEnforced(ctx, field, obj) if out.Values[i] == graphql.Null { atomic.AddUint32(&out.Invalids, 1) } + case "indicesQueryBoolMaxClauseCount": + out.Values[i] = ec._OpenSearch_indicesQueryBoolMaxClauseCount(ctx, field, obj) + case "httpMaxContentLength": + out.Values[i] = ec._OpenSearch_httpMaxContentLength(ctx, field, obj) case "issues": field := field @@ -4039,122 +3859,6 @@ func (ec *executionContext) _OpenSearchFacets(ctx context.Context, sel ast.Selec return out } -var openSearchHTTPImplementors = []string{"OpenSearchHTTP"} - -func (ec *executionContext) _OpenSearchHTTP(ctx context.Context, sel ast.SelectionSet, obj *opensearch.OpenSearchHTTP) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, openSearchHTTPImplementors) - - out := graphql.NewFieldSet(fields) - deferred := make(map[string]*graphql.FieldSet) - for i, field := range fields { - switch field.Name { - case "__typename": - out.Values[i] = graphql.MarshalString("OpenSearchHTTP") - case "maxContentLength": - out.Values[i] = ec._OpenSearchHTTP_maxContentLength(ctx, field, obj) - default: - panic("unknown field " + strconv.Quote(field.Name)) - } - } - out.Dispatch(ctx) - if out.Invalids > 0 { - return graphql.Null - } - - atomic.AddInt32(&ec.Deferred, int32(min(len(deferred), math.MaxInt32))) - - for label, dfs := range deferred { - ec.ProcessDeferredGroup(graphql.DeferredGroup{ - Label: label, - Path: graphql.GetPath(ctx), - FieldSet: dfs, - Context: ctx, - }) - } - - return out -} - -var openSearchIndicesImplementors = []string{"OpenSearchIndices"} - -func (ec *executionContext) _OpenSearchIndices(ctx context.Context, sel ast.SelectionSet, obj *opensearch.OpenSearchIndices) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, openSearchIndicesImplementors) - - out := graphql.NewFieldSet(fields) - deferred := make(map[string]*graphql.FieldSet) - for i, field := range fields { - switch field.Name { - case "__typename": - out.Values[i] = graphql.MarshalString("OpenSearchIndices") - case "queryBoolMaxClauseCount": - out.Values[i] = ec._OpenSearchIndices_queryBoolMaxClauseCount(ctx, field, obj) - default: - panic("unknown field " + strconv.Quote(field.Name)) - } - } - out.Dispatch(ctx) - if out.Invalids > 0 { - return graphql.Null - } - - atomic.AddInt32(&ec.Deferred, int32(min(len(deferred), math.MaxInt32))) - - for label, dfs := range deferred { - ec.ProcessDeferredGroup(graphql.DeferredGroup{ - Label: label, - Path: graphql.GetPath(ctx), - FieldSet: dfs, - Context: ctx, - }) - } - - return out -} - -var openSearchShardIndexingPressureImplementors = []string{"OpenSearchShardIndexingPressure"} - -func (ec *executionContext) _OpenSearchShardIndexingPressure(ctx context.Context, sel ast.SelectionSet, obj *opensearch.OpenSearchShardIndexingPressure) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, openSearchShardIndexingPressureImplementors) - - out := graphql.NewFieldSet(fields) - deferred := make(map[string]*graphql.FieldSet) - for i, field := range fields { - switch field.Name { - case "__typename": - out.Values[i] = graphql.MarshalString("OpenSearchShardIndexingPressure") - case "enabled": - out.Values[i] = ec._OpenSearchShardIndexingPressure_enabled(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ - } - case "enforced": - out.Values[i] = ec._OpenSearchShardIndexingPressure_enforced(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ - } - default: - panic("unknown field " + strconv.Quote(field.Name)) - } - } - out.Dispatch(ctx) - if out.Invalids > 0 { - return graphql.Null - } - - atomic.AddInt32(&ec.Deferred, int32(min(len(deferred), math.MaxInt32))) - - for label, dfs := range deferred { - ec.ProcessDeferredGroup(graphql.DeferredGroup{ - Label: label, - Path: graphql.GetPath(ctx), - FieldSet: dfs, - Context: ctx, - }) - } - - return out -} - var openSearchTierFacetItemImplementors = []string{"OpenSearchTierFacetItem"} func (ec *executionContext) _OpenSearchTierFacetItem(ctx context.Context, sel ast.SelectionSet, obj *opensearch.OpenSearchTierFacetItem) graphql.Marshaler { @@ -4681,14 +4385,6 @@ func (ec *executionContext) marshalNOpenSearchEdge2ᚕgithubᚗcomᚋnaisᚋapi return ret } -func (ec *executionContext) marshalNOpenSearchHTTP2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchHTTP(ctx context.Context, sel ast.SelectionSet, v opensearch.OpenSearchHTTP) graphql.Marshaler { - return ec._OpenSearchHTTP(ctx, sel, &v) -} - -func (ec *executionContext) marshalNOpenSearchIndices2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchIndices(ctx context.Context, sel ast.SelectionSet, v opensearch.OpenSearchIndices) graphql.Marshaler { - return ec._OpenSearchIndices(ctx, sel, &v) -} - func (ec *executionContext) unmarshalNOpenSearchMajorVersion2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchMajorVersion(ctx context.Context, v any) (opensearch.OpenSearchMajorVersion, error) { var res opensearch.OpenSearchMajorVersion err := res.UnmarshalGQL(v) @@ -4719,10 +4415,6 @@ func (ec *executionContext) marshalNOpenSearchOrderField2githubᚗcomᚋnaisᚋa return v } -func (ec *executionContext) marshalNOpenSearchShardIndexingPressure2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchShardIndexingPressure(ctx context.Context, sel ast.SelectionSet, v opensearch.OpenSearchShardIndexingPressure) graphql.Marshaler { - return ec._OpenSearchShardIndexingPressure(ctx, sel, &v) -} - func (ec *executionContext) unmarshalNOpenSearchState2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchState(ctx context.Context, v any) (opensearch.OpenSearchState, error) { var res opensearch.OpenSearchState err := res.UnmarshalGQL(v) @@ -4876,22 +4568,6 @@ func (ec *executionContext) unmarshalOOpenSearchFilter2ᚖgithubᚗcomᚋnaisᚋ return &res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) unmarshalOOpenSearchHTTPInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchHTTPInput(ctx context.Context, v any) (*opensearch.OpenSearchHTTPInput, error) { - if v == nil { - return nil, nil - } - res, err := ec.unmarshalInputOpenSearchHTTPInput(ctx, v) - return &res, graphql.ErrorOnPath(ctx, err) -} - -func (ec *executionContext) unmarshalOOpenSearchIndicesInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchIndicesInput(ctx context.Context, v any) (*opensearch.OpenSearchIndicesInput, error) { - if v == nil { - return nil, nil - } - res, err := ec.unmarshalInputOpenSearchIndicesInput(ctx, v) - return &res, graphql.ErrorOnPath(ctx, err) -} - func (ec *executionContext) unmarshalOOpenSearchOrder2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchOrder(ctx context.Context, v any) (*opensearch.OpenSearchOrder, error) { if v == nil { return nil, nil @@ -4900,14 +4576,6 @@ func (ec *executionContext) unmarshalOOpenSearchOrder2ᚖgithubᚗcomᚋnaisᚋa return &res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) unmarshalOOpenSearchShardIndexingPressureInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchShardIndexingPressureInput(ctx context.Context, v any) (*opensearch.OpenSearchShardIndexingPressureInput, error) { - if v == nil { - return nil, nil - } - res, err := ec.unmarshalInputOpenSearchShardIndexingPressureInput(ctx, v) - return &res, graphql.ErrorOnPath(ctx, err) -} - func (ec *executionContext) unmarshalOOpenSearchTier2ᚕgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋopensearchᚐOpenSearchTierᚄ(ctx context.Context, v any) ([]opensearch.OpenSearchTier, error) { if v == nil { return nil, nil diff --git a/internal/graph/gengql/root_.generated.go b/internal/graph/gengql/root_.generated.go index 815539da6..857d669ee 100644 --- a/internal/graph/gengql/root_.generated.go +++ b/internal/graph/gengql/root_.generated.go @@ -1562,26 +1562,27 @@ type ComplexityRoot struct { } OpenSearch struct { - Access func(childComplexity int, first *int, after *pagination.Cursor, last *int, before *pagination.Cursor, orderBy *opensearch.OpenSearchAccessOrder) int - ActivityLog func(childComplexity int, first *int, after *pagination.Cursor, last *int, before *pagination.Cursor, filter *activitylog.ActivityLogFilter) int - Cost func(childComplexity int) int - Environment func(childComplexity int) int - HTTP func(childComplexity int) int - ID func(childComplexity int) int - Indices func(childComplexity int) int - Issues func(childComplexity int, first *int, after *pagination.Cursor, last *int, before *pagination.Cursor, orderBy *issue.IssueOrder, filter *issue.ResourceIssueFilter) int - Maintenance func(childComplexity int) int - Memory func(childComplexity int) int - Name func(childComplexity int) int - ShardIndexingPressure func(childComplexity int) int - State func(childComplexity int) int - StorageGB func(childComplexity int) int - Team func(childComplexity int) int - TeamEnvironment func(childComplexity int) int - TerminationProtection func(childComplexity int) int - Tier func(childComplexity int) int - Version func(childComplexity int) int - Workload func(childComplexity int) int + Access func(childComplexity int, first *int, after *pagination.Cursor, last *int, before *pagination.Cursor, orderBy *opensearch.OpenSearchAccessOrder) int + ActivityLog func(childComplexity int, first *int, after *pagination.Cursor, last *int, before *pagination.Cursor, filter *activitylog.ActivityLogFilter) int + Cost func(childComplexity int) int + Environment func(childComplexity int) int + HTTPMaxContentLength func(childComplexity int) int + ID func(childComplexity int) int + IndicesQueryBoolMaxClauseCount func(childComplexity int) int + Issues func(childComplexity int, first *int, after *pagination.Cursor, last *int, before *pagination.Cursor, orderBy *issue.IssueOrder, filter *issue.ResourceIssueFilter) int + Maintenance func(childComplexity int) int + Memory func(childComplexity int) int + Name func(childComplexity int) int + ShardIndexingPressureEnabled func(childComplexity int) int + ShardIndexingPressureEnforced func(childComplexity int) int + State func(childComplexity int) int + StorageGB func(childComplexity int) int + Team func(childComplexity int) int + TeamEnvironment func(childComplexity int) int + TerminationProtection func(childComplexity int) int + Tier func(childComplexity int) int + Version func(childComplexity int) int + Workload func(childComplexity int) int } OpenSearchAccess struct { @@ -1651,14 +1652,6 @@ type ComplexityRoot struct { Tiers func(childComplexity int) int } - OpenSearchHTTP struct { - MaxContentLength func(childComplexity int) int - } - - OpenSearchIndices struct { - QueryBoolMaxClauseCount func(childComplexity int) int - } - OpenSearchIssue struct { Event func(childComplexity int) int ID func(childComplexity int) int @@ -1691,11 +1684,6 @@ type ComplexityRoot struct { Node func(childComplexity int) int } - OpenSearchShardIndexingPressure struct { - Enabled func(childComplexity int) int - Enforced func(childComplexity int) int - } - OpenSearchTierFacetItem struct { Count func(childComplexity int) int Tier func(childComplexity int) int @@ -3341,7 +3329,7 @@ type ComplexityRoot struct { Memory func(childComplexity int) int Name func(childComplexity int) int NotifyKeyspaceEvents func(childComplexity int) int - Persistence func(childComplexity int) int + PersistenceDisabled func(childComplexity int) int State func(childComplexity int) int Team func(childComplexity int) int TeamEnvironment func(childComplexity int) int @@ -3449,10 +3437,6 @@ type ComplexityRoot struct { Node func(childComplexity int) int } - ValkeyPersistence struct { - Disabled func(childComplexity int) int - } - ValkeyTierFacetItem struct { Count func(childComplexity int) int Tier func(childComplexity int) int @@ -9794,12 +9778,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.OpenSearch.Environment(childComplexity), true - case "OpenSearch.http": - if e.ComplexityRoot.OpenSearch.HTTP == nil { + case "OpenSearch.httpMaxContentLength": + if e.ComplexityRoot.OpenSearch.HTTPMaxContentLength == nil { break } - return e.ComplexityRoot.OpenSearch.HTTP(childComplexity), true + return e.ComplexityRoot.OpenSearch.HTTPMaxContentLength(childComplexity), true case "OpenSearch.id": if e.ComplexityRoot.OpenSearch.ID == nil { @@ -9808,12 +9792,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.OpenSearch.ID(childComplexity), true - case "OpenSearch.indices": - if e.ComplexityRoot.OpenSearch.Indices == nil { + case "OpenSearch.indicesQueryBoolMaxClauseCount": + if e.ComplexityRoot.OpenSearch.IndicesQueryBoolMaxClauseCount == nil { break } - return e.ComplexityRoot.OpenSearch.Indices(childComplexity), true + return e.ComplexityRoot.OpenSearch.IndicesQueryBoolMaxClauseCount(childComplexity), true case "OpenSearch.issues": if e.ComplexityRoot.OpenSearch.Issues == nil { @@ -9848,12 +9832,19 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.OpenSearch.Name(childComplexity), true - case "OpenSearch.shardIndexingPressure": - if e.ComplexityRoot.OpenSearch.ShardIndexingPressure == nil { + case "OpenSearch.shardIndexingPressureEnabled": + if e.ComplexityRoot.OpenSearch.ShardIndexingPressureEnabled == nil { break } - return e.ComplexityRoot.OpenSearch.ShardIndexingPressure(childComplexity), true + return e.ComplexityRoot.OpenSearch.ShardIndexingPressureEnabled(childComplexity), true + + case "OpenSearch.shardIndexingPressureEnforced": + if e.ComplexityRoot.OpenSearch.ShardIndexingPressureEnforced == nil { + break + } + + return e.ComplexityRoot.OpenSearch.ShardIndexingPressureEnforced(childComplexity), true case "OpenSearch.state": if e.ComplexityRoot.OpenSearch.State == nil { @@ -10170,20 +10161,6 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.OpenSearchFacets.Tiers(childComplexity), true - case "OpenSearchHTTP.maxContentLength": - if e.ComplexityRoot.OpenSearchHTTP.MaxContentLength == nil { - break - } - - return e.ComplexityRoot.OpenSearchHTTP.MaxContentLength(childComplexity), true - - case "OpenSearchIndices.queryBoolMaxClauseCount": - if e.ComplexityRoot.OpenSearchIndices.QueryBoolMaxClauseCount == nil { - break - } - - return e.ComplexityRoot.OpenSearchIndices.QueryBoolMaxClauseCount(childComplexity), true - case "OpenSearchIssue.event": if e.ComplexityRoot.OpenSearchIssue.Event == nil { break @@ -10308,20 +10285,6 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.OpenSearchMaintenanceUpdateEdge.Node(childComplexity), true - case "OpenSearchShardIndexingPressure.enabled": - if e.ComplexityRoot.OpenSearchShardIndexingPressure.Enabled == nil { - break - } - - return e.ComplexityRoot.OpenSearchShardIndexingPressure.Enabled(childComplexity), true - - case "OpenSearchShardIndexingPressure.enforced": - if e.ComplexityRoot.OpenSearchShardIndexingPressure.Enforced == nil { - break - } - - return e.ComplexityRoot.OpenSearchShardIndexingPressure.Enforced(childComplexity), true - case "OpenSearchTierFacetItem.count": if e.ComplexityRoot.OpenSearchTierFacetItem.Count == nil { break @@ -17625,12 +17588,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.Valkey.NotifyKeyspaceEvents(childComplexity), true - case "Valkey.persistence": - if e.ComplexityRoot.Valkey.Persistence == nil { + case "Valkey.persistenceDisabled": + if e.ComplexityRoot.Valkey.PersistenceDisabled == nil { break } - return e.ComplexityRoot.Valkey.Persistence(childComplexity), true + return e.ComplexityRoot.Valkey.PersistenceDisabled(childComplexity), true case "Valkey.state": if e.ComplexityRoot.Valkey.State == nil { @@ -18057,13 +18020,6 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.ValkeyMaintenanceUpdateEdge.Node(childComplexity), true - case "ValkeyPersistence.disabled": - if e.ComplexityRoot.ValkeyPersistence.Disabled == nil { - break - } - - return e.ComplexityRoot.ValkeyPersistence.Disabled(childComplexity), true - case "ValkeyTierFacetItem.count": if e.ComplexityRoot.ValkeyTierFacetItem.Count == nil { break @@ -18842,10 +18798,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputMetricsRangeInput, ec.unmarshalInputOpenSearchAccessOrder, ec.unmarshalInputOpenSearchFilter, - ec.unmarshalInputOpenSearchHTTPInput, - ec.unmarshalInputOpenSearchIndicesInput, ec.unmarshalInputOpenSearchOrder, - ec.unmarshalInputOpenSearchShardIndexingPressureInput, ec.unmarshalInputPostgresInstanceFilter, ec.unmarshalInputPostgresInstanceOrder, ec.unmarshalInputReconcilerConfigInput, @@ -18900,7 +18853,6 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputValkeyAccessOrder, ec.unmarshalInputValkeyFilter, ec.unmarshalInputValkeyOrder, - ec.unmarshalInputValkeyPersistenceInput, ec.unmarshalInputViewSecretValuesInput, ec.unmarshalInputVulnerabilitySummaryOrder, ec.unmarshalInputWorkloadLogSubscriptionFilter, @@ -24296,12 +24248,14 @@ type OpenSearch implements Persistence & Node { memory: OpenSearchMemory! "Available storage in GB." storageGB: Int! - "Shard indexing back pressure settings for the OpenSearch instance." - shardIndexingPressure: OpenSearchShardIndexingPressure! - "Index settings for the OpenSearch instance." - indices: OpenSearchIndices! - "HTTP settings for the OpenSearch instance." - http: OpenSearchHTTP! + "Whether shard indexing back pressure is enabled." + shardIndexingPressureEnabled: Boolean! + "Whether shard indexing back pressure runs in enforced mode. In enforced mode requests that may degrade cluster performance are rejected; in shadow mode (enforced false) metrics are tracked but no requests are rejected." + shardIndexingPressureEnforced: Boolean! + "Maximum number of clauses a Lucene BooleanQuery can contain. When not set, the instance uses the default of 1024. Increasing this value may cause performance issues." + indicesQueryBoolMaxClauseCount: Int + "Maximum content length, in a human-readable quantity (e.g. \"100Mi\", \"1Gi\"), for requests to the OpenSearch HTTP API. When not set, the instance uses the default of 100Mi." + httpMaxContentLength: String "Issues that affects the instance." issues( "Get the first n items in the connection. This can be used in combination with the after parameter." @@ -24332,40 +24286,6 @@ enum OpenSearchState { UNKNOWN } -type OpenSearchShardIndexingPressure { - "Whether shard indexing back pressure is enabled." - enabled: Boolean! - "Whether back pressure runs in enforced mode. In enforced mode requests that may degrade cluster performance are rejected; in shadow mode (enforced false) metrics are tracked but no requests are rejected." - enforced: Boolean! -} - -input OpenSearchShardIndexingPressureInput { - "Enable or disable shard indexing back pressure. Defaults to false." - enabled: Boolean! - "Run back pressure in enforced mode. Defaults to false (shadow mode)." - enforced: Boolean! -} - -type OpenSearchIndices { - "Maximum number of clauses a Lucene BooleanQuery can contain. When not set, the instance uses the default of 1024. Increasing this value may cause performance issues." - queryBoolMaxClauseCount: Int -} - -input OpenSearchIndicesInput { - "Maximum number of clauses a Lucene BooleanQuery can contain. Must be between 64 and 4096. When not set, the instance uses the default of 1024." - queryBoolMaxClauseCount: Int -} - -type OpenSearchHTTP { - "Maximum content length, in a human-readable quantity (e.g. \"100Mi\", \"1Gi\"), for requests to the OpenSearch HTTP API. When not set, the instance uses the default of 100Mi." - maxContentLength: String -} - -input OpenSearchHTTPInput { - "Maximum content length for requests to the OpenSearch HTTP API. Specified as a human-readable quantity (e.g. \"100Mi\", \"1Gi\"); unitless values are interpreted as bytes. Must be between 1 byte and 2147483647 bytes (around 2047Mi). When not set, the instance uses the default of 100Mi." - maxContentLength: String -} - type OpenSearchAccess { workload: Workload! access: String! @@ -24502,12 +24422,14 @@ input CreateOpenSearchInput { version: OpenSearchMajorVersion! "Available storage in GB." storageGB: Int! - "Shard indexing back pressure settings. Defaults to disabled." - shardIndexingPressure: OpenSearchShardIndexingPressureInput - "Index settings for the OpenSearch instance." - indices: OpenSearchIndicesInput - "HTTP settings for the OpenSearch instance." - http: OpenSearchHTTPInput + "Enable shard indexing back pressure. Defaults to false." + shardIndexingPressureEnabled: Boolean + "Run shard indexing back pressure in enforced mode. Defaults to false (shadow mode)." + shardIndexingPressureEnforced: Boolean + "Maximum number of clauses a Lucene BooleanQuery can contain. Must be between 64 and 4096. When not set, the instance uses the default of 1024." + indicesQueryBoolMaxClauseCount: Int + "Maximum content length for requests to the OpenSearch HTTP API. Specified as a human-readable quantity (e.g. \"100Mi\", \"1Gi\"); unitless values are interpreted as bytes. Must be between 1 byte and 2147483647 bytes (around 2047Mi). When not set, the instance uses the default of 100Mi." + httpMaxContentLength: String } type CreateOpenSearchPayload { @@ -24530,12 +24452,14 @@ input UpdateOpenSearchInput { version: OpenSearchMajorVersion! "Available storage in GB." storageGB: Int! - "Shard indexing back pressure settings. Defaults to disabled." - shardIndexingPressure: OpenSearchShardIndexingPressureInput - "Index settings for the OpenSearch instance." - indices: OpenSearchIndicesInput - "HTTP settings for the OpenSearch instance." - http: OpenSearchHTTPInput + "Enable shard indexing back pressure. Defaults to false." + shardIndexingPressureEnabled: Boolean + "Run shard indexing back pressure in enforced mode. Defaults to false (shadow mode)." + shardIndexingPressureEnforced: Boolean + "Maximum number of clauses a Lucene BooleanQuery can contain. Must be between 64 and 4096. When not set, the instance uses the default of 1024." + indicesQueryBoolMaxClauseCount: Int + "Maximum content length for requests to the OpenSearch HTTP API. Specified as a human-readable quantity (e.g. \"100Mi\", \"1Gi\"); unitless values are interpreted as bytes. Must be between 1 byte and 2147483647 bytes (around 2047Mi). When not set, the instance uses the default of 100Mi." + httpMaxContentLength: String } type UpdateOpenSearchPayload { @@ -30278,8 +30202,8 @@ type Valkey implements Persistence & Node { notifyKeyspaceEvents: String "Number of databases the Valkey instance is configured with. Default is 16. Minimum 1, maximum 128. Changing this will cause a restart of the Valkey service." databases: Int! - "Persistence and backup settings for the Valkey instance." - persistence: ValkeyPersistence! + "Whether persistence (RDB dumps and backups) is disabled. If true, all data is lost if the instance is restarted for any reason." + persistenceDisabled: Boolean! "Issues that affects the instance." issues( "Get the first n items in the connection. This can be used in combination with the after parameter." @@ -30310,16 +30234,6 @@ enum ValkeyState { UNKNOWN } -type ValkeyPersistence { - "Whether persistence (RDB dumps and backups) is disabled. If true, all data is lost if the instance is restarted for any reason." - disabled: Boolean! -} - -input ValkeyPersistenceInput { - "Disable persistence (RDB dumps and backups). Defaults to false." - disabled: Boolean! -} - type ValkeyAccess { workload: Workload! access: String! @@ -30475,8 +30389,8 @@ input CreateValkeyInput { notifyKeyspaceEvents: String "Number of databases. Default is 16. Minimum 1, maximum 128. Changing this will cause a restart of the Valkey service." databases: Int - "Persistence and backup settings for the Valkey instance. Defaults to enabled." - persistence: ValkeyPersistenceInput + "Disable persistence (RDB dumps and backups). Defaults to false." + persistenceDisabled: Boolean } type CreateValkeyPayload { @@ -30501,8 +30415,8 @@ input UpdateValkeyInput { notifyKeyspaceEvents: String "Number of databases. Default is 16. Minimum 1, maximum 128. Changing this will cause a restart of the Valkey service." databases: Int - "Persistence and backup settings for the Valkey instance. Defaults to enabled." - persistence: ValkeyPersistenceInput + "Disable persistence (RDB dumps and backups). Defaults to false." + persistenceDisabled: Boolean } type UpdateValkeyPayload { @@ -34006,12 +33920,14 @@ func (ec *executionContext) childFields_OpenSearch(ctx context.Context, field gr return ec.fieldContext_OpenSearch_memory(ctx, field) case "storageGB": return ec.fieldContext_OpenSearch_storageGB(ctx, field) - case "shardIndexingPressure": - return ec.fieldContext_OpenSearch_shardIndexingPressure(ctx, field) - case "indices": - return ec.fieldContext_OpenSearch_indices(ctx, field) - case "http": - return ec.fieldContext_OpenSearch_http(ctx, field) + case "shardIndexingPressureEnabled": + return ec.fieldContext_OpenSearch_shardIndexingPressureEnabled(ctx, field) + case "shardIndexingPressureEnforced": + return ec.fieldContext_OpenSearch_shardIndexingPressureEnforced(ctx, field) + case "indicesQueryBoolMaxClauseCount": + return ec.fieldContext_OpenSearch_indicesQueryBoolMaxClauseCount(ctx, field) + case "httpMaxContentLength": + return ec.fieldContext_OpenSearch_httpMaxContentLength(ctx, field) case "issues": return ec.fieldContext_OpenSearch_issues(ctx, field) case "activityLog": @@ -34114,22 +34030,6 @@ func (ec *executionContext) childFields_OpenSearchFacets(ctx context.Context, fi return nil, fmt.Errorf("no field named %q was found under type OpenSearchFacets", field.Name) } -func (ec *executionContext) childFields_OpenSearchHTTP(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - switch field.Name { - case "maxContentLength": - return ec.fieldContext_OpenSearchHTTP_maxContentLength(ctx, field) - } - return nil, fmt.Errorf("no field named %q was found under type OpenSearchHTTP", field.Name) -} - -func (ec *executionContext) childFields_OpenSearchIndices(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - switch field.Name { - case "queryBoolMaxClauseCount": - return ec.fieldContext_OpenSearchIndices_queryBoolMaxClauseCount(ctx, field) - } - return nil, fmt.Errorf("no field named %q was found under type OpenSearchIndices", field.Name) -} - func (ec *executionContext) childFields_OpenSearchMaintenance(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "window": @@ -34176,16 +34076,6 @@ func (ec *executionContext) childFields_OpenSearchMaintenanceUpdateEdge(ctx cont return nil, fmt.Errorf("no field named %q was found under type OpenSearchMaintenanceUpdateEdge", field.Name) } -func (ec *executionContext) childFields_OpenSearchShardIndexingPressure(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - switch field.Name { - case "enabled": - return ec.fieldContext_OpenSearchShardIndexingPressure_enabled(ctx, field) - case "enforced": - return ec.fieldContext_OpenSearchShardIndexingPressure_enforced(ctx, field) - } - return nil, fmt.Errorf("no field named %q was found under type OpenSearchShardIndexingPressure", field.Name) -} - func (ec *executionContext) childFields_OpenSearchTierFacetItem(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "tier": @@ -36324,8 +36214,8 @@ func (ec *executionContext) childFields_Valkey(ctx context.Context, field graphq return ec.fieldContext_Valkey_notifyKeyspaceEvents(ctx, field) case "databases": return ec.fieldContext_Valkey_databases(ctx, field) - case "persistence": - return ec.fieldContext_Valkey_persistence(ctx, field) + case "persistenceDisabled": + return ec.fieldContext_Valkey_persistenceDisabled(ctx, field) case "issues": return ec.fieldContext_Valkey_issues(ctx, field) case "activityLog": @@ -36474,14 +36364,6 @@ func (ec *executionContext) childFields_ValkeyMaintenanceUpdateEdge(ctx context. return nil, fmt.Errorf("no field named %q was found under type ValkeyMaintenanceUpdateEdge", field.Name) } -func (ec *executionContext) childFields_ValkeyPersistence(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - switch field.Name { - case "disabled": - return ec.fieldContext_ValkeyPersistence_disabled(ctx, field) - } - return nil, fmt.Errorf("no field named %q was found under type ValkeyPersistence", field.Name) -} - func (ec *executionContext) childFields_ValkeyTierFacetItem(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "tier": diff --git a/internal/graph/gengql/valkey.generated.go b/internal/graph/gengql/valkey.generated.go index b4a8ad583..e0116101e 100644 --- a/internal/graph/gengql/valkey.generated.go +++ b/internal/graph/gengql/valkey.generated.go @@ -726,36 +726,27 @@ func (ec *executionContext) fieldContext_Valkey_databases(_ context.Context, fie return graphql.NewScalarFieldContext("Valkey", field, false, false, errors.New("field of type Int does not have child fields")) } -func (ec *executionContext) _Valkey_persistence(ctx context.Context, field graphql.CollectedField, obj *valkey.Valkey) (ret graphql.Marshaler) { +func (ec *executionContext) _Valkey_persistenceDisabled(ctx context.Context, field graphql.CollectedField, obj *valkey.Valkey) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_Valkey_persistence(ctx, field) + return ec.fieldContext_Valkey_persistenceDisabled(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.Persistence, nil + return obj.PersistenceDisabled, nil }, nil, - func(ctx context.Context, selections ast.SelectionSet, v valkey.ValkeyPersistence) graphql.Marshaler { - return ec.marshalNValkeyPersistence2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋvalkeyᚐValkeyPersistence(ctx, selections, v) + func(ctx context.Context, selections ast.SelectionSet, v bool) graphql.Marshaler { + return ec.marshalNBoolean2bool(ctx, selections, v) }, true, true, ) } -func (ec *executionContext) fieldContext_Valkey_persistence(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "Valkey", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.childFields_ValkeyPersistence(ctx, field) - }, - } - return fc, nil +func (ec *executionContext) fieldContext_Valkey_persistenceDisabled(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("Valkey", field, false, false, errors.New("field of type Boolean does not have child fields")) } func (ec *executionContext) _Valkey_issues(ctx context.Context, field graphql.CollectedField, obj *valkey.Valkey) (ret graphql.Marshaler) { @@ -1846,29 +1837,6 @@ func (ec *executionContext) fieldContext_ValkeyFacets_tiers(_ context.Context, f return fc, nil } -func (ec *executionContext) _ValkeyPersistence_disabled(ctx context.Context, field graphql.CollectedField, obj *valkey.ValkeyPersistence) (ret graphql.Marshaler) { - return graphql.ResolveField( - ctx, - ec.OperationContext, - field, - func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ValkeyPersistence_disabled(ctx, field) - }, - func(ctx context.Context) (any, error) { - return obj.Disabled, nil - }, - nil, - func(ctx context.Context, selections ast.SelectionSet, v bool) graphql.Marshaler { - return ec.marshalNBoolean2bool(ctx, selections, v) - }, - true, - true, - ) -} -func (ec *executionContext) fieldContext_ValkeyPersistence_disabled(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("ValkeyPersistence", field, false, false, errors.New("field of type Boolean does not have child fields")) -} - func (ec *executionContext) _ValkeyTierFacetItem_tier(ctx context.Context, field graphql.CollectedField, obj *valkey.ValkeyTierFacetItem) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -2305,7 +2273,7 @@ func (ec *executionContext) unmarshalInputCreateValkeyInput(ctx context.Context, asMap[k] = v } - fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "maxMemoryPolicy", "notifyKeyspaceEvents", "databases", "persistence"} + fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "maxMemoryPolicy", "notifyKeyspaceEvents", "databases", "persistenceDisabled"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -2368,13 +2336,13 @@ func (ec *executionContext) unmarshalInputCreateValkeyInput(ctx context.Context, return it, err } it.Databases = data - case "persistence": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("persistence")) - data, err := ec.unmarshalOValkeyPersistenceInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋvalkeyᚐValkeyPersistenceInput(ctx, v) + case "persistenceDisabled": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("persistenceDisabled")) + data, err := ec.unmarshalOBoolean2ᚖbool(ctx, v) if err != nil { return it, err } - it.Persistence = data + it.PersistenceDisabled = data } } return it, nil @@ -2435,7 +2403,7 @@ func (ec *executionContext) unmarshalInputUpdateValkeyInput(ctx context.Context, asMap[k] = v } - fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "maxMemoryPolicy", "notifyKeyspaceEvents", "databases", "persistence"} + fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "maxMemoryPolicy", "notifyKeyspaceEvents", "databases", "persistenceDisabled"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -2498,13 +2466,13 @@ func (ec *executionContext) unmarshalInputUpdateValkeyInput(ctx context.Context, return it, err } it.Databases = data - case "persistence": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("persistence")) - data, err := ec.unmarshalOValkeyPersistenceInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋvalkeyᚐValkeyPersistenceInput(ctx, v) + case "persistenceDisabled": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("persistenceDisabled")) + data, err := ec.unmarshalOBoolean2ᚖbool(ctx, v) if err != nil { return it, err } - it.Persistence = data + it.PersistenceDisabled = data } } return it, nil @@ -2628,36 +2596,6 @@ func (ec *executionContext) unmarshalInputValkeyOrder(ctx context.Context, obj a return it, nil } -func (ec *executionContext) unmarshalInputValkeyPersistenceInput(ctx context.Context, obj any) (valkey.ValkeyPersistenceInput, error) { - var it valkey.ValkeyPersistenceInput - if obj == nil { - return it, nil - } - - asMap := map[string]any{} - for k, v := range obj.(map[string]any) { - asMap[k] = v - } - - fieldsInOrder := [...]string{"disabled"} - for _, k := range fieldsInOrder { - v, ok := asMap[k] - if !ok { - continue - } - switch k { - case "disabled": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("disabled")) - data, err := ec.unmarshalNBoolean2bool(ctx, v) - if err != nil { - return it, err - } - it.Disabled = data - } - } - return it, nil -} - // endregion **************************** input.gotpl ***************************** // region ************************** interface.gotpl *************************** @@ -3116,8 +3054,8 @@ func (ec *executionContext) _Valkey(ctx context.Context, sel ast.SelectionSet, o if out.Values[i] == graphql.Null { atomic.AddUint32(&out.Invalids, 1) } - case "persistence": - out.Values[i] = ec._Valkey_persistence(ctx, field, obj) + case "persistenceDisabled": + out.Values[i] = ec._Valkey_persistenceDisabled(ctx, field, obj) if out.Values[i] == graphql.Null { atomic.AddUint32(&out.Invalids, 1) } @@ -3827,45 +3765,6 @@ func (ec *executionContext) _ValkeyFacets(ctx context.Context, sel ast.Selection return out } -var valkeyPersistenceImplementors = []string{"ValkeyPersistence"} - -func (ec *executionContext) _ValkeyPersistence(ctx context.Context, sel ast.SelectionSet, obj *valkey.ValkeyPersistence) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, valkeyPersistenceImplementors) - - out := graphql.NewFieldSet(fields) - deferred := make(map[string]*graphql.FieldSet) - for i, field := range fields { - switch field.Name { - case "__typename": - out.Values[i] = graphql.MarshalString("ValkeyPersistence") - case "disabled": - out.Values[i] = ec._ValkeyPersistence_disabled(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ - } - default: - panic("unknown field " + strconv.Quote(field.Name)) - } - } - out.Dispatch(ctx) - if out.Invalids > 0 { - return graphql.Null - } - - atomic.AddInt32(&ec.Deferred, int32(min(len(deferred), math.MaxInt32))) - - for label, dfs := range deferred { - ec.ProcessDeferredGroup(graphql.DeferredGroup{ - Label: label, - Path: graphql.GetPath(ctx), - FieldSet: dfs, - Context: ctx, - }) - } - - return out -} - var valkeyTierFacetItemImplementors = []string{"ValkeyTierFacetItem"} func (ec *executionContext) _ValkeyTierFacetItem(ctx context.Context, sel ast.SelectionSet, obj *valkey.ValkeyTierFacetItem) graphql.Marshaler { @@ -4326,10 +4225,6 @@ func (ec *executionContext) marshalNValkeyOrderField2githubᚗcomᚋnaisᚋapi return v } -func (ec *executionContext) marshalNValkeyPersistence2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋvalkeyᚐValkeyPersistence(ctx context.Context, sel ast.SelectionSet, v valkey.ValkeyPersistence) graphql.Marshaler { - return ec._ValkeyPersistence(ctx, sel, &v) -} - func (ec *executionContext) unmarshalNValkeyState2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋvalkeyᚐValkeyState(ctx context.Context, v any) (valkey.ValkeyState, error) { var res valkey.ValkeyState err := res.UnmarshalGQL(v) @@ -4463,14 +4358,6 @@ func (ec *executionContext) unmarshalOValkeyOrder2ᚖgithubᚗcomᚋnaisᚋapi return &res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) unmarshalOValkeyPersistenceInput2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋvalkeyᚐValkeyPersistenceInput(ctx context.Context, v any) (*valkey.ValkeyPersistenceInput, error) { - if v == nil { - return nil, nil - } - res, err := ec.unmarshalInputValkeyPersistenceInput(ctx, v) - return &res, graphql.ErrorOnPath(ctx, err) -} - func (ec *executionContext) unmarshalOValkeyTier2ᚕgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋvalkeyᚐValkeyTierᚄ(ctx context.Context, v any) ([]valkey.ValkeyTier, error) { if v == nil { return nil, nil diff --git a/internal/graph/schema/opensearch.graphqls b/internal/graph/schema/opensearch.graphqls index 8608ed554..4d19e2b92 100644 --- a/internal/graph/schema/opensearch.graphqls +++ b/internal/graph/schema/opensearch.graphqls @@ -82,12 +82,14 @@ type OpenSearch implements Persistence & Node { memory: OpenSearchMemory! "Available storage in GB." storageGB: Int! - "Shard indexing back pressure settings for the OpenSearch instance." - shardIndexingPressure: OpenSearchShardIndexingPressure! - "Index settings for the OpenSearch instance." - indices: OpenSearchIndices! - "HTTP settings for the OpenSearch instance." - http: OpenSearchHTTP! + "Whether shard indexing back pressure is enabled." + shardIndexingPressureEnabled: Boolean! + "Whether shard indexing back pressure runs in enforced mode. In enforced mode requests that may degrade cluster performance are rejected; in shadow mode (enforced false) metrics are tracked but no requests are rejected." + shardIndexingPressureEnforced: Boolean! + "Maximum number of clauses a Lucene BooleanQuery can contain. When not set, the instance uses the default of 1024. Increasing this value may cause performance issues." + indicesQueryBoolMaxClauseCount: Int + "Maximum content length, in a human-readable quantity (e.g. \"100Mi\", \"1Gi\"), for requests to the OpenSearch HTTP API. When not set, the instance uses the default of 100Mi." + httpMaxContentLength: String "Issues that affects the instance." issues( "Get the first n items in the connection. This can be used in combination with the after parameter." @@ -118,40 +120,6 @@ enum OpenSearchState { UNKNOWN } -type OpenSearchShardIndexingPressure { - "Whether shard indexing back pressure is enabled." - enabled: Boolean! - "Whether back pressure runs in enforced mode. In enforced mode requests that may degrade cluster performance are rejected; in shadow mode (enforced false) metrics are tracked but no requests are rejected." - enforced: Boolean! -} - -input OpenSearchShardIndexingPressureInput { - "Enable or disable shard indexing back pressure. Defaults to false." - enabled: Boolean! - "Run back pressure in enforced mode. Defaults to false (shadow mode)." - enforced: Boolean! -} - -type OpenSearchIndices { - "Maximum number of clauses a Lucene BooleanQuery can contain. When not set, the instance uses the default of 1024. Increasing this value may cause performance issues." - queryBoolMaxClauseCount: Int -} - -input OpenSearchIndicesInput { - "Maximum number of clauses a Lucene BooleanQuery can contain. Must be between 64 and 4096. When not set, the instance uses the default of 1024." - queryBoolMaxClauseCount: Int -} - -type OpenSearchHTTP { - "Maximum content length, in a human-readable quantity (e.g. \"100Mi\", \"1Gi\"), for requests to the OpenSearch HTTP API. When not set, the instance uses the default of 100Mi." - maxContentLength: String -} - -input OpenSearchHTTPInput { - "Maximum content length for requests to the OpenSearch HTTP API. Specified as a human-readable quantity (e.g. \"100Mi\", \"1Gi\"); unitless values are interpreted as bytes. Must be between 1 byte and 2147483647 bytes (around 2047Mi). When not set, the instance uses the default of 100Mi." - maxContentLength: String -} - type OpenSearchAccess { workload: Workload! access: String! @@ -288,12 +256,14 @@ input CreateOpenSearchInput { version: OpenSearchMajorVersion! "Available storage in GB." storageGB: Int! - "Shard indexing back pressure settings. Defaults to disabled." - shardIndexingPressure: OpenSearchShardIndexingPressureInput - "Index settings for the OpenSearch instance." - indices: OpenSearchIndicesInput - "HTTP settings for the OpenSearch instance." - http: OpenSearchHTTPInput + "Enable shard indexing back pressure. Defaults to false." + shardIndexingPressureEnabled: Boolean + "Run shard indexing back pressure in enforced mode. Defaults to false (shadow mode)." + shardIndexingPressureEnforced: Boolean + "Maximum number of clauses a Lucene BooleanQuery can contain. Must be between 64 and 4096. When not set, the instance uses the default of 1024." + indicesQueryBoolMaxClauseCount: Int + "Maximum content length for requests to the OpenSearch HTTP API. Specified as a human-readable quantity (e.g. \"100Mi\", \"1Gi\"); unitless values are interpreted as bytes. Must be between 1 byte and 2147483647 bytes (around 2047Mi). When not set, the instance uses the default of 100Mi." + httpMaxContentLength: String } type CreateOpenSearchPayload { @@ -316,12 +286,14 @@ input UpdateOpenSearchInput { version: OpenSearchMajorVersion! "Available storage in GB." storageGB: Int! - "Shard indexing back pressure settings. Defaults to disabled." - shardIndexingPressure: OpenSearchShardIndexingPressureInput - "Index settings for the OpenSearch instance." - indices: OpenSearchIndicesInput - "HTTP settings for the OpenSearch instance." - http: OpenSearchHTTPInput + "Enable shard indexing back pressure. Defaults to false." + shardIndexingPressureEnabled: Boolean + "Run shard indexing back pressure in enforced mode. Defaults to false (shadow mode)." + shardIndexingPressureEnforced: Boolean + "Maximum number of clauses a Lucene BooleanQuery can contain. Must be between 64 and 4096. When not set, the instance uses the default of 1024." + indicesQueryBoolMaxClauseCount: Int + "Maximum content length for requests to the OpenSearch HTTP API. Specified as a human-readable quantity (e.g. \"100Mi\", \"1Gi\"); unitless values are interpreted as bytes. Must be between 1 byte and 2147483647 bytes (around 2047Mi). When not set, the instance uses the default of 100Mi." + httpMaxContentLength: String } type UpdateOpenSearchPayload { diff --git a/internal/graph/schema/valkey.graphqls b/internal/graph/schema/valkey.graphqls index 5d22f8f6e..747916d7c 100644 --- a/internal/graph/schema/valkey.graphqls +++ b/internal/graph/schema/valkey.graphqls @@ -86,8 +86,8 @@ type Valkey implements Persistence & Node { notifyKeyspaceEvents: String "Number of databases the Valkey instance is configured with. Default is 16. Minimum 1, maximum 128. Changing this will cause a restart of the Valkey service." databases: Int! - "Persistence and backup settings for the Valkey instance." - persistence: ValkeyPersistence! + "Whether persistence (RDB dumps and backups) is disabled. If true, all data is lost if the instance is restarted for any reason." + persistenceDisabled: Boolean! "Issues that affects the instance." issues( "Get the first n items in the connection. This can be used in combination with the after parameter." @@ -118,16 +118,6 @@ enum ValkeyState { UNKNOWN } -type ValkeyPersistence { - "Whether persistence (RDB dumps and backups) is disabled. If true, all data is lost if the instance is restarted for any reason." - disabled: Boolean! -} - -input ValkeyPersistenceInput { - "Disable persistence (RDB dumps and backups). Defaults to false." - disabled: Boolean! -} - type ValkeyAccess { workload: Workload! access: String! @@ -283,8 +273,8 @@ input CreateValkeyInput { notifyKeyspaceEvents: String "Number of databases. Default is 16. Minimum 1, maximum 128. Changing this will cause a restart of the Valkey service." databases: Int - "Persistence and backup settings for the Valkey instance. Defaults to enabled." - persistence: ValkeyPersistenceInput + "Disable persistence (RDB dumps and backups). Defaults to false." + persistenceDisabled: Boolean } type CreateValkeyPayload { @@ -309,8 +299,8 @@ input UpdateValkeyInput { notifyKeyspaceEvents: String "Number of databases. Default is 16. Minimum 1, maximum 128. Changing this will cause a restart of the Valkey service." databases: Int - "Persistence and backup settings for the Valkey instance. Defaults to enabled." - persistence: ValkeyPersistenceInput + "Disable persistence (RDB dumps and backups). Defaults to false." + persistenceDisabled: Boolean } type UpdateValkeyPayload { diff --git a/internal/persistence/opensearch/models.go b/internal/persistence/opensearch/models.go index 12554a69e..872e52b46 100644 --- a/internal/persistence/opensearch/models.go +++ b/internal/persistence/opensearch/models.go @@ -54,19 +54,22 @@ type OpenSearchTierFacetItem struct { } type OpenSearch struct { - Name string `json:"name"` - Status *naiscrd.OpenSearchStatus `json:"status"` - TerminationProtection bool `json:"terminationProtection"` - Tier OpenSearchTier `json:"tier"` - Memory OpenSearchMemory `json:"memory"` - StorageGB StorageGB `json:"storageGB"` - ShardIndexingPressure OpenSearchShardIndexingPressure `json:"shardIndexingPressure"` - Indices OpenSearchIndices `json:"indices"` - HTTP OpenSearchHTTP `json:"http"` - TeamSlug slug.Slug `json:"-"` - EnvironmentName string `json:"-"` - WorkloadReference *workload.Reference `json:"-"` - MajorVersion OpenSearchMajorVersion `json:"-"` + Name string `json:"name"` + Status *naiscrd.OpenSearchStatus `json:"status"` + TerminationProtection bool `json:"terminationProtection"` + Tier OpenSearchTier `json:"tier"` + Memory OpenSearchMemory `json:"memory"` + StorageGB StorageGB `json:"storageGB"` + + ShardIndexingPressureEnabled bool `json:"shardIndexingPressureEnabled"` + ShardIndexingPressureEnforced bool `json:"shardIndexingPressureEnforced"` + IndicesQueryBoolMaxClauseCount *int `json:"indicesQueryBoolMaxClauseCount,omitempty"` + HTTPMaxContentLength *string `json:"httpMaxContentLength,omitempty"` + + TeamSlug slug.Slug `json:"-"` + EnvironmentName string `json:"-"` + WorkloadReference *workload.Reference `json:"-"` + MajorVersion OpenSearchMajorVersion `json:"-"` } func (OpenSearch) IsPersistence() {} @@ -106,32 +109,6 @@ type OpenSearchAccess struct { WorkloadReference *workload.Reference `json:"-"` } -type OpenSearchShardIndexingPressure struct { - Enabled bool `json:"enabled"` - Enforced bool `json:"enforced"` -} - -type OpenSearchShardIndexingPressureInput struct { - Enabled bool `json:"enabled"` - Enforced bool `json:"enforced"` -} - -type OpenSearchIndices struct { - QueryBoolMaxClauseCount *int `json:"queryBoolMaxClauseCount,omitempty"` -} - -type OpenSearchIndicesInput struct { - QueryBoolMaxClauseCount *int `json:"queryBoolMaxClauseCount,omitempty"` -} - -type OpenSearchHTTP struct { - MaxContentLength *string `json:"maxContentLength,omitempty"` -} - -type OpenSearchHTTPInput struct { - MaxContentLength *string `json:"maxContentLength,omitempty"` -} - type OpenSearchStatus struct { State string `json:"state"` Conditions []metav1.Condition `json:"conditions"` @@ -249,16 +226,16 @@ func toOpenSearch(u *unstructured.Unstructured, envName string) (*OpenSearch, er shardIndexingPressureEnabled, _, _ := unstructured.NestedBool(u.Object, specShardIndexingPressureEnabled...) shardIndexingPressureEnforced, _, _ := unstructured.NestedBool(u.Object, specShardIndexingPressureEnforced...) - indices := OpenSearchIndices{} + var queryBoolMaxClauseCount *int if v, found, _ := unstructured.NestedNumberAsFloat64(u.Object, specIndicesQueryBoolMaxClauseCount...); found { n := int(v) - indices.QueryBoolMaxClauseCount = &n + queryBoolMaxClauseCount = &n } - http := OpenSearchHTTP{} + var maxContentLength *string if v, found, _ := unstructured.NestedNumberAsFloat64(u.Object, specHTTPMaxContentLength...); found { s := resource.NewQuantity(int64(v), resource.BinarySI).String() - http.MaxContentLength = &s + maxContentLength = &s } return &OpenSearch{ @@ -270,54 +247,53 @@ func toOpenSearch(u *unstructured.Unstructured, envName string) (*OpenSearch, er Conditions: obj.Status.Conditions, }, }, - TeamSlug: slug.Slug(obj.GetNamespace()), - WorkloadReference: workload.ReferenceFromOwnerReferences(obj.GetOwnerReferences()), - Tier: machine.Tier, - Memory: machine.Memory, - MajorVersion: majorVersion, - StorageGB: storageGB, - ShardIndexingPressure: OpenSearchShardIndexingPressure{ - Enabled: shardIndexingPressureEnabled, - Enforced: shardIndexingPressureEnforced, - }, - Indices: indices, - HTTP: http, + TeamSlug: slug.Slug(obj.GetNamespace()), + WorkloadReference: workload.ReferenceFromOwnerReferences(obj.GetOwnerReferences()), + Tier: machine.Tier, + Memory: machine.Memory, + MajorVersion: majorVersion, + StorageGB: storageGB, + ShardIndexingPressureEnabled: shardIndexingPressureEnabled, + ShardIndexingPressureEnforced: shardIndexingPressureEnforced, + IndicesQueryBoolMaxClauseCount: queryBoolMaxClauseCount, + HTTPMaxContentLength: maxContentLength, }, nil } func toOpenSearchFromNais(o *naiscrd.OpenSearch, envName string) (*OpenSearch, error) { majorVersion := fromMapperatorVersion(o.Spec.Version) - shardIndexingPressure := OpenSearchShardIndexingPressure{} + var shardIndexingPressureEnabled, shardIndexingPressureEnforced bool if o.Spec.ShardIndexingPressure != nil { - shardIndexingPressure.Enabled = o.Spec.ShardIndexingPressure.Enabled - shardIndexingPressure.Enforced = o.Spec.ShardIndexingPressure.Enforced + shardIndexingPressureEnabled = o.Spec.ShardIndexingPressure.Enabled + shardIndexingPressureEnforced = o.Spec.ShardIndexingPressure.Enforced } - indices := OpenSearchIndices{} + var queryBoolMaxClauseCount *int if o.Spec.Indices != nil { - indices.QueryBoolMaxClauseCount = o.Spec.Indices.QueryBoolMaxClauseCount + queryBoolMaxClauseCount = o.Spec.Indices.QueryBoolMaxClauseCount } - http := OpenSearchHTTP{} + var maxContentLength *string if o.Spec.Http != nil && o.Spec.Http.MaxContentLength != nil { s := o.Spec.Http.MaxContentLength.String() - http.MaxContentLength = &s + maxContentLength = &s } return &OpenSearch{ - Name: o.Name, - EnvironmentName: envName, - Status: o.Status, - TeamSlug: slug.Slug(o.Namespace), - WorkloadReference: workload.ReferenceFromOwnerReferences(o.OwnerReferences), - Tier: fromMapperatorTier(o.Spec.Tier), - Memory: fromMapperatorMemory(o.Spec.Memory), - MajorVersion: majorVersion, - StorageGB: StorageGB(o.Spec.StorageGB), - ShardIndexingPressure: shardIndexingPressure, - Indices: indices, - HTTP: http, + Name: o.Name, + EnvironmentName: envName, + Status: o.Status, + TeamSlug: slug.Slug(o.Namespace), + WorkloadReference: workload.ReferenceFromOwnerReferences(o.OwnerReferences), + Tier: fromMapperatorTier(o.Spec.Tier), + Memory: fromMapperatorMemory(o.Spec.Memory), + MajorVersion: majorVersion, + StorageGB: StorageGB(o.Spec.StorageGB), + ShardIndexingPressureEnabled: shardIndexingPressureEnabled, + ShardIndexingPressureEnforced: shardIndexingPressureEnforced, + IndicesQueryBoolMaxClauseCount: queryBoolMaxClauseCount, + HTTPMaxContentLength: maxContentLength, }, nil } @@ -358,13 +334,14 @@ func (o *OpenSearchMetadataInput) ValidationErrors(ctx context.Context) *validat type OpenSearchInput struct { OpenSearchMetadataInput - Tier OpenSearchTier `json:"tier"` - Memory OpenSearchMemory `json:"memory"` - Version OpenSearchMajorVersion `json:"version"` - StorageGB StorageGB `json:"storageGB"` - ShardIndexingPressure *OpenSearchShardIndexingPressureInput `json:"shardIndexingPressure,omitempty"` - Indices *OpenSearchIndicesInput `json:"indices,omitempty"` - HTTP *OpenSearchHTTPInput `json:"http,omitempty"` + Tier OpenSearchTier `json:"tier"` + Memory OpenSearchMemory `json:"memory"` + Version OpenSearchMajorVersion `json:"version"` + StorageGB StorageGB `json:"storageGB"` + ShardIndexingPressureEnabled *bool `json:"shardIndexingPressureEnabled,omitempty"` + ShardIndexingPressureEnforced *bool `json:"shardIndexingPressureEnforced,omitempty"` + IndicesQueryBoolMaxClauseCount *int `json:"indicesQueryBoolMaxClauseCount,omitempty"` + HTTPMaxContentLength *string `json:"httpMaxContentLength,omitempty"` } func (o *OpenSearchInput) Validate(ctx context.Context) error { @@ -380,18 +357,18 @@ func (o *OpenSearchInput) Validate(ctx context.Context) error { verr.Add("version", "Invalid OpenSearch version: %s.", o.Version.String()) } - if o.Indices != nil && o.Indices.QueryBoolMaxClauseCount != nil { - if c := *o.Indices.QueryBoolMaxClauseCount; c < 64 || c > 4096 { - verr.Add("queryBoolMaxClauseCount", "Query bool max clause count must be between 64 and 4096.") + if o.IndicesQueryBoolMaxClauseCount != nil { + if c := *o.IndicesQueryBoolMaxClauseCount; c < 64 || c > 4096 { + verr.Add("indicesQueryBoolMaxClauseCount", "Query bool max clause count must be between 64 and 4096.") } } - if o.HTTP != nil && o.HTTP.MaxContentLength != nil { - q, err := resource.ParseQuantity(*o.HTTP.MaxContentLength) + if o.HTTPMaxContentLength != nil { + q, err := resource.ParseQuantity(*o.HTTPMaxContentLength) if err != nil { - verr.Add("maxContentLength", "Max content length must be a valid quantity (e.g. \"100Mi\", \"1Gi\").") + verr.Add("httpMaxContentLength", "Max content length must be a valid quantity (e.g. \"100Mi\", \"1Gi\").") } else if b := q.Value(); b < 1 || b > 2147483647 { - verr.Add("maxContentLength", "Max content length must be between 1 byte and 2147483647 bytes (around 2047Mi).") + verr.Add("httpMaxContentLength", "Max content length must be between 1 byte and 2147483647 bytes (around 2047Mi).") } } diff --git a/internal/persistence/opensearch/queries.go b/internal/persistence/opensearch/queries.go index d53125bd9..c76ed6c20 100644 --- a/internal/persistence/opensearch/queries.go +++ b/internal/persistence/opensearch/queries.go @@ -25,6 +25,7 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" ) var ( @@ -217,21 +218,21 @@ func Create(ctx context.Context, input CreateOpenSearchInput) (*CreateOpenSearch res.SetAnnotations(kubernetes.WithCommonAnnotations(nil, authz.ActorFromContext(ctx).User.Identity())) kubernetes.SetManagedByConsoleLabel(res) - if input.ShardIndexingPressure != nil { + if input.ShardIndexingPressureEnabled != nil || input.ShardIndexingPressureEnforced != nil { res.Spec.ShardIndexingPressure = &naiscrd.OpenSearchShardIndexingPressure{ - Enabled: input.ShardIndexingPressure.Enabled, - Enforced: input.ShardIndexingPressure.Enforced, + Enabled: ptr.Deref(input.ShardIndexingPressureEnabled, false), + Enforced: ptr.Deref(input.ShardIndexingPressureEnforced, false), } } - if input.Indices != nil && input.Indices.QueryBoolMaxClauseCount != nil { + if input.IndicesQueryBoolMaxClauseCount != nil { res.Spec.Indices = &naiscrd.OpenSearchIndices{ - QueryBoolMaxClauseCount: input.Indices.QueryBoolMaxClauseCount, + QueryBoolMaxClauseCount: input.IndicesQueryBoolMaxClauseCount, } } - if input.HTTP != nil && input.HTTP.MaxContentLength != nil { - q, err := resource.ParseQuantity(*input.HTTP.MaxContentLength) + if input.HTTPMaxContentLength != nil { + q, err := resource.ParseQuantity(*input.HTTPMaxContentLength) if err != nil { return nil, err } @@ -496,7 +497,7 @@ func updateStorage(openSearch *naiscrd.OpenSearch, input UpdateOpenSearchInput) } func updateShardIndexingPressure(openSearch *naiscrd.OpenSearch, input UpdateOpenSearchInput) ([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, error) { - if input.ShardIndexingPressure == nil { + if input.ShardIndexingPressureEnabled == nil && input.ShardIndexingPressureEnforced == nil { return nil, nil } @@ -506,20 +507,23 @@ func updateShardIndexingPressure(openSearch *naiscrd.OpenSearch, input UpdateOpe oldEnforced = openSearch.Spec.ShardIndexingPressure.Enforced } + newEnabled := ptr.Deref(input.ShardIndexingPressureEnabled, oldEnabled) + newEnforced := ptr.Deref(input.ShardIndexingPressureEnforced, oldEnforced) + changes := make([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, 0) - if oldEnabled != input.ShardIndexingPressure.Enabled { + if oldEnabled != newEnabled { changes = append(changes, &OpenSearchUpdatedActivityLogEntryDataUpdatedField{ - Field: "shardIndexingPressure.enabled", + Field: "shardIndexingPressureEnabled", OldValue: new(strconv.FormatBool(oldEnabled)), - NewValue: new(strconv.FormatBool(input.ShardIndexingPressure.Enabled)), + NewValue: new(strconv.FormatBool(newEnabled)), }) } - if oldEnforced != input.ShardIndexingPressure.Enforced { + if oldEnforced != newEnforced { changes = append(changes, &OpenSearchUpdatedActivityLogEntryDataUpdatedField{ - Field: "shardIndexingPressure.enforced", + Field: "shardIndexingPressureEnforced", OldValue: new(strconv.FormatBool(oldEnforced)), - NewValue: new(strconv.FormatBool(input.ShardIndexingPressure.Enforced)), + NewValue: new(strconv.FormatBool(newEnforced)), }) } @@ -528,15 +532,15 @@ func updateShardIndexingPressure(openSearch *naiscrd.OpenSearch, input UpdateOpe } openSearch.Spec.ShardIndexingPressure = &naiscrd.OpenSearchShardIndexingPressure{ - Enabled: input.ShardIndexingPressure.Enabled, - Enforced: input.ShardIndexingPressure.Enforced, + Enabled: newEnabled, + Enforced: newEnforced, } return changes, nil } func updateIndices(openSearch *naiscrd.OpenSearch, input UpdateOpenSearchInput) ([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, error) { - if input.Indices == nil { + if input.IndicesQueryBoolMaxClauseCount == nil { return nil, nil } @@ -544,7 +548,7 @@ func updateIndices(openSearch *naiscrd.OpenSearch, input UpdateOpenSearchInput) if openSearch.Spec.Indices != nil { oldCount = openSearch.Spec.Indices.QueryBoolMaxClauseCount } - newCount := input.Indices.QueryBoolMaxClauseCount + newCount := input.IndicesQueryBoolMaxClauseCount if equalIntPtr(oldCount, newCount) { return nil, nil @@ -556,7 +560,7 @@ func updateIndices(openSearch *naiscrd.OpenSearch, input UpdateOpenSearchInput) return []*OpenSearchUpdatedActivityLogEntryDataUpdatedField{ { - Field: "indices.queryBoolMaxClauseCount", + Field: "indicesQueryBoolMaxClauseCount", OldValue: intPtrToStringPtr(oldCount), NewValue: intPtrToStringPtr(newCount), }, @@ -564,11 +568,11 @@ func updateIndices(openSearch *naiscrd.OpenSearch, input UpdateOpenSearchInput) } func updateHTTP(openSearch *naiscrd.OpenSearch, input UpdateOpenSearchInput) ([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, error) { - if input.HTTP == nil || input.HTTP.MaxContentLength == nil { + if input.HTTPMaxContentLength == nil { return nil, nil } - newQ, err := resource.ParseQuantity(*input.HTTP.MaxContentLength) + newQ, err := resource.ParseQuantity(*input.HTTPMaxContentLength) if err != nil { return nil, err } @@ -589,7 +593,7 @@ func updateHTTP(openSearch *naiscrd.OpenSearch, input UpdateOpenSearchInput) ([] return []*OpenSearchUpdatedActivityLogEntryDataUpdatedField{ { - Field: "http.maxContentLength", + Field: "httpMaxContentLength", OldValue: oldValue, NewValue: new(newQ.String()), }, diff --git a/internal/persistence/valkey/models.go b/internal/persistence/valkey/models.go index 9a8f453ea..e0d7579e5 100644 --- a/internal/persistence/valkey/models.go +++ b/internal/persistence/valkey/models.go @@ -58,7 +58,7 @@ type Valkey struct { MaxMemoryPolicy ValkeyMaxMemoryPolicy `json:"maxMemoryPolicy,omitempty"` NotifyKeyspaceEvents string `json:"notifyKeyspaceEvents,omitempty"` Databases int `json:"databases"` - Persistence ValkeyPersistence `json:"persistence"` + PersistenceDisabled bool `json:"persistenceDisabled"` TeamSlug slug.Slug `json:"-"` EnvironmentName string `json:"-"` WorkloadReference *workload.Reference `json:"-"` @@ -101,14 +101,6 @@ type ValkeyAccess struct { WorkloadReference *workload.Reference `json:"-"` } -type ValkeyPersistence struct { - Disabled bool `json:"disabled"` -} - -type ValkeyPersistenceInput struct { - Disabled bool `json:"disabled"` -} - type ValkeyStatus struct { State string `json:"state"` Conditions []metav1.Condition `json:"conditions"` @@ -239,7 +231,7 @@ func toValkey(u *unstructured.Unstructured, envName string) (*Valkey, error) { MaxMemoryPolicy: maxMemoryPolicy, NotifyKeyspaceEvents: notifyKeyspaceEvents, Databases: int(numberOfDatabases), - Persistence: ValkeyPersistence{Disabled: persistenceDisabled}, + PersistenceDisabled: persistenceDisabled, }, nil } @@ -258,9 +250,9 @@ func toValkeyFromNais(v *naiscrd.Valkey, envName string) (*Valkey, error) { databases = *v.Spec.Databases } - persistence := ValkeyPersistence{} + persistenceDisabled := false if v.Spec.Persistence != nil { - persistence.Disabled = v.Spec.Persistence.Disabled + persistenceDisabled = v.Spec.Persistence.Disabled } return &Valkey{ @@ -274,7 +266,7 @@ func toValkeyFromNais(v *naiscrd.Valkey, envName string) (*Valkey, error) { NotifyKeyspaceEvents: v.Spec.NotifyKeyspaceEvents, MaxMemoryPolicy: mmp, Databases: databases, - Persistence: persistence, + PersistenceDisabled: persistenceDisabled, }, nil } @@ -315,12 +307,12 @@ func (v *ValkeyMetadataInput) ValidationErrors(ctx context.Context) *validate.Va type ValkeyInput struct { ValkeyMetadataInput - Tier ValkeyTier `json:"tier"` - Memory ValkeyMemory `json:"memory"` - MaxMemoryPolicy *ValkeyMaxMemoryPolicy `json:"maxMemoryPolicy,omitempty"` - NotifyKeyspaceEvents *string `json:"notifyKeyspaceEvents,omitempty"` - Databases *int `json:"databases,omitempty"` - Persistence *ValkeyPersistenceInput `json:"persistence,omitempty"` + Tier ValkeyTier `json:"tier"` + Memory ValkeyMemory `json:"memory"` + MaxMemoryPolicy *ValkeyMaxMemoryPolicy `json:"maxMemoryPolicy,omitempty"` + NotifyKeyspaceEvents *string `json:"notifyKeyspaceEvents,omitempty"` + Databases *int `json:"databases,omitempty"` + PersistenceDisabled *bool `json:"persistenceDisabled,omitempty"` } func (v *ValkeyInput) Validate(ctx context.Context) error { diff --git a/internal/persistence/valkey/queries.go b/internal/persistence/valkey/queries.go index bc90e9be3..a21660d91 100644 --- a/internal/persistence/valkey/queries.go +++ b/internal/persistence/valkey/queries.go @@ -180,9 +180,9 @@ func Create(ctx context.Context, input CreateValkeyInput) (*CreateValkeyPayload, res.Spec.Databases = input.Databases } - if input.Persistence != nil { + if input.PersistenceDisabled != nil { res.Spec.Persistence = &naiscrd.ValkeyPersistence{ - Disabled: input.Persistence.Disabled, + Disabled: *input.PersistenceDisabled, } } @@ -512,7 +512,7 @@ func updateDatabases(valkey *naiscrd.Valkey, input UpdateValkeyInput) ([]*Valkey } func updatePersistence(valkey *naiscrd.Valkey, input UpdateValkeyInput) ([]*ValkeyUpdatedActivityLogEntryDataUpdatedField, error) { - if input.Persistence == nil { + if input.PersistenceDisabled == nil { return nil, nil } @@ -521,20 +521,20 @@ func updatePersistence(valkey *naiscrd.Valkey, input UpdateValkeyInput) ([]*Valk oldDisabled = valkey.Spec.Persistence.Disabled } - if oldDisabled == input.Persistence.Disabled { + if oldDisabled == *input.PersistenceDisabled { return nil, nil } changes := []*ValkeyUpdatedActivityLogEntryDataUpdatedField{ { - Field: "persistence.disabled", + Field: "persistenceDisabled", OldValue: new(strconv.FormatBool(oldDisabled)), - NewValue: new(strconv.FormatBool(input.Persistence.Disabled)), + NewValue: new(strconv.FormatBool(*input.PersistenceDisabled)), }, } valkey.Spec.Persistence = &naiscrd.ValkeyPersistence{ - Disabled: input.Persistence.Disabled, + Disabled: *input.PersistenceDisabled, } return changes, nil }