From 3ac8903a5daafc8e6ae7acb0f74690e2ad6b0f20 Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Tue, 10 Feb 2026 13:55:28 +0100 Subject: [PATCH 01/21] 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/graph/valkey.resolvers.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 | 391 +++++++++++----------- 11 files changed, 361 insertions(+), 440 deletions(-) create mode 100644 internal/kubernetes/utils.go diff --git a/integration_tests/valkey_crud.lua b/integration_tests/valkey_crud.lua index 312706067..75dd3b6dc 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 9864a1ed5..a2e24b44f 100644 --- a/internal/cmd/api/http.go +++ b/internal/cmd/api/http.go @@ -350,7 +350,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, log) + ctx = valkey.NewLoaderContext(ctx, tenantName, watchers.ValkeyWatcher, watchers.NaisValkeyWatcher, aivenClient, log) ctx = price.NewLoaderContext(ctx, priceRetriever, log) ctx = utilization.NewLoaderContext(ctx, prometheusClient, log) ctx = alerts.NewLoaderContext(ctx, prometheusClient, log) diff --git a/internal/graph/valkey.resolvers.go b/internal/graph/valkey.resolvers.go index a276b6973..13ced3eab 100644 --- a/internal/graph/valkey.resolvers.go +++ b/internal/graph/valkey.resolvers.go @@ -65,7 +65,7 @@ func (r *teamEnvironmentResolver) Valkey(ctx context.Context, obj *team.TeamEnvi func (r *teamInventoryCountsResolver) Valkeys(ctx context.Context, obj *team.TeamInventoryCounts) (*valkey.TeamInventoryCountValkeys, error) { return &valkey.TeamInventoryCountValkeys{ - Total: len(valkey.ListAllForTeam(ctx, obj.TeamSlug, nil)), + Total: len(valkey.ListAllForTeam(ctx, obj.TeamSlug)), }, nil } 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 31f30a200..44f53d084 100644 --- a/internal/persistence/valkey/dataloader.go +++ b/internal/persistence/valkey/dataloader.go @@ -3,19 +3,29 @@ 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" "github.com/sirupsen/logrus" "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, logger logrus.FieldLogger) context.Context { - return context.WithValue(ctx, loadersKey, newLoaders(tenantName, valkeyWatcher, aivenClient, logger)) +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, logger logrus.FieldLogger) context.Context { + return context.WithValue(ctx, loadersKey, newLoaders(tenantName, valkeyWatcher, naisValkeyWatcher, aivenClient, logger)) } func NewWatcher(ctx context.Context, mgr *watcher.Manager) *watcher.Watcher[*Valkey] { @@ -34,6 +44,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) } @@ -42,20 +68,32 @@ type loaders struct { client *client tenantName string watcher *watcher.Watcher[*Valkey] + naisWatcher *watcher.Watcher[*Valkey] aivenClient aiven.AivenClient log logrus.FieldLogger } -func newLoaders(tenantName string, watcher *watcher.Watcher[*Valkey], aivenClient aiven.AivenClient, logger logrus.FieldLogger) *loaders { - client := &client{ - watcher: watcher, - } +func newLoaders(tenantName string, watcher, naisValkeyWatcher *watcher.Watcher[*Valkey], aivenClient aiven.AivenClient, logger logrus.FieldLogger) *loaders { + client := &client{} return &loaders{ client: client, tenantName: tenantName, watcher: watcher, + naisWatcher: naisValkeyWatcher, aivenClient: aivenClient, log: logger, } } + +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 2b18eb851..c03a2766d 100644 --- a/internal/persistence/valkey/models.go +++ b/internal/persistence/valkey/models.go @@ -17,6 +17,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" @@ -55,7 +57,7 @@ type ValkeyTierFacetItem struct { 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"` @@ -182,11 +184,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 = "" } @@ -212,9 +222,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()), @@ -228,6 +239,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 } @@ -367,9 +400,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 } } @@ -379,21 +412,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 9d5a4ac0f..9978d47bb 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,15 +45,19 @@ 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, filter *ValkeyFilter) (*ValkeyConnection, error) { - all := ListAllForTeam(ctx, teamSlug, filter) + all := ListAllForTeam(ctx, teamSlug) if orderBy == nil { orderBy = &ValkeyOrder{ @@ -63,8 +69,10 @@ func ListForTeam(ctx context.Context, teamSlug slug.Slug, page *pagination.Pagin return SortFilterValkey.PaginatedList(ctx, all, page, orderBy.Field, orderBy.Direction, filter), nil } -func ListAllForTeam(ctx context.Context, teamSlug slug.Slug, filter *ValkeyFilter) []*Valkey { - all := fromContext(ctx).client.watcher.GetByNamespace(teamSlug.String(), watcher.WithoutDeleted()) +func ListAllForTeam(ctx context.Context, teamSlug slug.Slug) []*Valkey { + 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) } @@ -98,7 +106,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 { @@ -130,77 +140,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{ @@ -211,11 +184,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 } @@ -230,44 +212,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...) changes = append(changes, updateLabels(valkey, input)...) @@ -282,21 +257,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{ @@ -314,7 +284,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 } @@ -394,158 +369,119 @@ 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 - } - - oldAivenPolicy, found, err := unstructured.NestedString(valkey.Object, specMaxMemoryPolicy...) - if err != nil { - return nil, err + return nil, nil } - if found && oldAivenPolicy == input.MaxMemoryPolicy.ToAivenString() { - return changes, nil + if string(valkey.Spec.MaxMemoryPolicy) == input.MaxMemoryPolicy.ToAivenString() { + return nil, 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 = new(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, + Field: "databases", + 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. - if err := unstructured.SetNestedField(valkey.Object, float64(*input.Databases), specNumberOfDatabases...); err != nil { - return nil, err - } - return changes, nil } @@ -588,13 +524,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 } @@ -618,7 +553,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 } @@ -662,3 +597,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 3c2cb8c8f2ef76a81853c5b641750396d2af46c7 Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Wed, 11 Feb 2026 11:49:59 +0100 Subject: [PATCH 02/21] 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 9978d47bb..bc4fcfb17 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"} @@ -184,6 +183,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 1e4fedb49bb1a38381460ee4e463115bd88120da Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Wed, 11 Feb 2026 12:54:40 +0100 Subject: [PATCH 03/21] 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 bc4fcfb17..ee74d8e69 100644 --- a/internal/persistence/valkey/queries.go +++ b/internal/persistence/valkey/queries.go @@ -147,8 +147,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{ @@ -249,7 +251,11 @@ func Update(ctx context.Context, input UpdateValkeyInput) (*UpdateValkeyPayload, changes = append(changes, updateLabels(valkey, input)...) 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 7c0d294da507c6f94368fa01c49582bf91aba576 Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Fri, 13 Feb 2026 10:52:42 +0100 Subject: [PATCH 04/21] 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 ee74d8e69..b3277e350 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" @@ -546,18 +547,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 28ccb977366d5ae6f8986dd4d5bcb1546e5caa07 Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Fri, 13 Feb 2026 11:18:09 +0100 Subject: [PATCH 05/21] 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 44f53d084..5471e7e03 100644 --- a/internal/persistence/valkey/dataloader.go +++ b/internal/persistence/valkey/dataloader.go @@ -18,12 +18,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, logger logrus.FieldLogger) context.Context { return context.WithValue(ctx, loadersKey, newLoaders(tenantName, valkeyWatcher, naisValkeyWatcher, aivenClient, logger)) } @@ -55,7 +49,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 } @@ -87,10 +85,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 b3277e350..9472441ab 100644 --- a/internal/persistence/valkey/queries.go +++ b/internal/persistence/valkey/queries.go @@ -106,15 +106,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 1d937afac76345722975a2b01e4507eb7c0a71bb 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/21] 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/watchers/watchers.go | 3 + internal/persistence/opensearch/client.go | 5 +- internal/persistence/opensearch/dataloader.go | 47 +- internal/persistence/opensearch/models.go | 57 ++- internal/persistence/opensearch/queries.go | 421 +++++++++--------- 9 files changed, 356 insertions(+), 488 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 a2e24b44f..7f1b541fd 100644 --- a/internal/cmd/api/http.go +++ b/internal/cmd/api/http.go @@ -349,7 +349,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, log) 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/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 72a6ab567..3baa6e6e3 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,22 +73,22 @@ 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 log logrus.FieldLogger } -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, @@ -72,6 +96,17 @@ func newLoaders(tenantName string, watcher *watcher.Watcher[*OpenSearch], aivenC } } +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 6ca38f8a3..0b006cf19 100644 --- a/internal/persistence/opensearch/models.go +++ b/internal/persistence/opensearch/models.go @@ -20,6 +20,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" @@ -57,18 +59,18 @@ type OpenSearchTierFacetItem struct { } 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"` - Labels []*model.ResourceLabel `json:"labels"` - 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"` + Labels []*model.ResourceLabel `json:"labels"` + TeamSlug slug.Slug `json:"-"` + EnvironmentName string `json:"-"` + WorkloadReference *workload.Reference `json:"-"` + AivenProject string `json:"-"` + MajorVersion OpenSearchMajorVersion `json:"-"` } func (OpenSearch) IsPersistence() {} @@ -184,6 +186,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...) @@ -218,9 +228,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()), @@ -233,6 +244,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 f29857ece..dbe9d1d0d 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) { @@ -74,7 +79,7 @@ func State(ctx context.Context, os *OpenSearch) (OpenSearchState, error) { } func ListForTeam(ctx context.Context, teamSlug slug.Slug, page *pagination.Pagination, orderBy *OpenSearchOrder, filter *OpenSearchFilter) (*OpenSearchConnection, error) { - all := ListAllForTeam(ctx, teamSlug, filter) + all := ListAllForTeam(ctx, teamSlug) if orderBy == nil { orderBy = &OpenSearchOrder{ @@ -86,8 +91,10 @@ func ListForTeam(ctx context.Context, teamSlug slug.Slug, page *pagination.Pagin return SortFilterOpenSearch.PaginatedList(ctx, all, page, orderBy.Field, orderBy.Direction, filter), nil } -func ListAllForTeam(ctx context.Context, teamSlug slug.Slug, filter *OpenSearchFilter) []*OpenSearch { - all := fromContext(ctx).client.watcher.GetByNamespace(teamSlug.String(), watcher.WithoutDeleted()) +func ListAllForTeam(ctx context.Context, teamSlug slug.Slug) []*OpenSearch { + 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) } @@ -155,7 +162,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 Create(ctx context.Context, input CreateOpenSearchInput) (*CreateOpenSearchPayload, error) { @@ -163,63 +170,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, @@ -233,7 +213,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 } @@ -248,43 +240,40 @@ 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...) changes = append(changes, updateLabels(openSearch, input)...) if len(changes) == 0 { - // No changes to update os, err := toOpenSearch(openSearch, input.EnvironmentName) if err != nil { return nil, err @@ -295,21 +284,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{ @@ -327,13 +311,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 } @@ -394,148 +383,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 } @@ -571,36 +503,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 } @@ -619,3 +550,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 e1f786bdd5075112da53bee80c6e02a77552e626 Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Fri, 13 Feb 2026 12:01:03 +0100 Subject: [PATCH 07/21] 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 dbe9d1d0d..cd974fba4 100644 --- a/internal/persistence/opensearch/queries.go +++ b/internal/persistence/opensearch/queries.go @@ -201,18 +201,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 @@ -225,6 +213,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 9472441ab..384d75e4c 100644 --- a/internal/persistence/valkey/queries.go +++ b/internal/persistence/valkey/queries.go @@ -176,18 +176,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 @@ -200,6 +188,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 @@ -580,8 +580,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 a40c9a28267924d770b9d6eebebca1f4db09c178 Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Fri, 13 Feb 2026 13:13:57 +0100 Subject: [PATCH 08/21] 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 0b006cf19..c69327bad 100644 --- a/internal/persistence/opensearch/models.go +++ b/internal/persistence/opensearch/models.go @@ -69,7 +69,6 @@ type OpenSearch struct { TeamSlug slug.Slug `json:"-"` EnvironmentName string `json:"-"` WorkloadReference *workload.Reference `json:"-"` - AivenProject string `json:"-"` MajorVersion OpenSearchMajorVersion `json:"-"` } @@ -235,7 +234,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 cd974fba4..f8fad1ef3 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. @@ -128,8 +132,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 c03a2766d..d2482f9ac 100644 --- a/internal/persistence/valkey/models.go +++ b/internal/persistence/valkey/models.go @@ -68,7 +68,6 @@ type Valkey struct { TeamSlug slug.Slug `json:"-"` EnvironmentName string `json:"-"` WorkloadReference *workload.Reference `json:"-"` - AivenProject string `json:"-"` } func (Valkey) IsPersistence() {} @@ -229,7 +228,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 384d75e4c..2dd44179e 100644 --- a/internal/persistence/valkey/queries.go +++ b/internal/persistence/valkey/queries.go @@ -580,11 +580,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 e10f3eccd8f5ccc41b2f919c2a67b70677397311 Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Fri, 13 Feb 2026 13:25:53 +0100 Subject: [PATCH 09/21] 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 2f7923c9ef02d0338d123796d41870166dc14bd9 Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Fri, 13 Feb 2026 13:38:36 +0100 Subject: [PATCH 10/21] 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 32dfeca45..2eb5473a8 100644 --- a/internal/kubernetes/watcher/watcher.go +++ b/internal/kubernetes/watcher/watcher.go @@ -277,6 +277,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 f8fad1ef3..149c18787 100644 --- a/internal/persistence/opensearch/queries.go +++ b/internal/persistence/opensearch/queries.go @@ -539,7 +539,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 2dd44179e..7762bd26a 100644 --- a/internal/persistence/valkey/queries.go +++ b/internal/persistence/valkey/queries.go @@ -559,7 +559,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 97d47ab975252f29a52fee1507d406adf0bcd745 Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Fri, 13 Feb 2026 13:57:00 +0100 Subject: [PATCH 11/21] 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 149c18787..d4762834a 100644 --- a/internal/persistence/opensearch/queries.go +++ b/internal/persistence/opensearch/queries.go @@ -156,7 +156,7 @@ func GetOpenSearchVersion(ctx context.Context, os *OpenSearch) (*OpenSearchVersi } if major == "" { - major = OpenSearchMajorVersionV2 + major = OpenSearchMajorVersionV3_3 } return &OpenSearchVersion{ From 5f13d4d3324a0499d2932e6fbb30a0e5056e4c3e Mon Sep 17 00:00:00 2001 From: Trong Huu Nguyen Date: Wed, 3 Jun 2026 09:07:35 +0200 Subject: [PATCH 12/21] fix and cleanup after rebase --- integration_tests/valkey_crud.lua | 10 ++++----- internal/persistence/valkey/models.go | 7 +++++++ internal/persistence/valkey/queries.go | 28 ++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/integration_tests/valkey_crud.lua b/integration_tests/valkey_crud.lua index 75dd3b6dc..535ebe0fa 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/persistence/valkey/models.go b/internal/persistence/valkey/models.go index d2482f9ac..086c0d475 100644 --- a/internal/persistence/valkey/models.go +++ b/internal/persistence/valkey/models.go @@ -246,6 +246,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, @@ -256,6 +262,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 7762bd26a..92bad971f 100644 --- a/internal/persistence/valkey/queries.go +++ b/internal/persistence/valkey/queries.go @@ -175,12 +175,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 @@ -249,6 +261,16 @@ func Update(ctx context.Context, input UpdateValkeyInput) (*UpdateValkeyPayload, changes = append(changes, updateLabels(valkey, input)...) + // 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 { @@ -269,6 +291,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{}) From 6ee6acd70559a66c6c9543bbf5916e718f5b7538 Mon Sep 17 00:00:00 2001 From: Trong Huu Nguyen Date: Wed, 3 Jun 2026 09:48:23 +0200 Subject: [PATCH 13/21] feat(valkey): support persistence configuration --- go.mod | 24 +-- go.sum | 56 +++--- .../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, 356 insertions(+), 47 deletions(-) diff --git a/go.mod b/go.mod index fbb9d0ec3..0da8dcfdb 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-20260826091953-1b518b13ca28 @@ -83,11 +83,11 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20260630182238-925bb5da69e7 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 - k8s.io/api v0.35.3 - k8s.io/apimachinery v0.35.3 - k8s.io/client-go v0.35.3 + k8s.io/api v0.36.0 + k8s.io/apimachinery v0.36.1 + k8s.io/client-go v0.36.0 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 ) @@ -407,9 +407,9 @@ require ( github.com/zitadel/logging v0.6.1 // indirect github.com/zitadel/schema v1.3.0 // indirect go.etcd.io/bbolt v1.5.0 // 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.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/collector/component v1.59.0 // indirect @@ -467,13 +467,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 a82438b13..65eaa4c3f 100644 --- a/go.sum +++ b/go.sum @@ -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= @@ -828,11 +828,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.153.0 h1:hrLQLVZ4YXs35G9QvPO+xcu6qMnUpCt3WOpBDM7dR+E= github.com/open-telemetry/opentelemetry-collector-contrib/internal/exp/metrics v0.153.0/go.mod h1:+qlIXDaBLIX+egj/okymUG88KSdAD8nIEHY8+tXt8+M= github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.153.0 h1:kPPv0sQQH2R2CitTPYXDfq1z1ekStnmk39C8RkOrsbI= @@ -1121,12 +1121,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.5.0 h1:S7GAl7Fxv12yohbwFfIbQCGDWbQbtDGPET4P/bD4lxU= go.etcd.io/bbolt v1.5.0/go.mod h1:mkltfYE5aUHQxUct9N9V+Kp7aSjFqjgrhcXIS70Lrdk= -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.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= @@ -1460,20 +1460,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.3 h1:pA2fiBc6+N9PDf7SAiluKGEBuScsTzd2uYBkA5RzNWQ= -k8s.io/api v0.35.3/go.mod h1:9Y9tkBcFwKNq2sxwZTQh1Njh9qHl81D0As56tu42GA4= -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.3 h1:s1lZbpN4uI6IxeTM2cpdtrwHcSOBML1ODNTCCfsP1pg= -k8s.io/client-go v0.35.3/go.mod h1:RzoXkc0mzpWIDvBrRnD+VlfXP+lRzqQjCmKtiwZ8Q9c= +k8s.io/api v0.36.0 h1:SgqDhZzHdOtMk40xVSvCXkP9ME0H05hPM3p9AB1kL80= +k8s.io/api v0.36.0/go.mod h1:m1LVrGPNYax5NBHdO+QuAedXyuzTt4RryI/qnmNvs34= +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.0 h1:pOYi7C4RHChYjMiHpZSpSbIM6ZxVbRXBy7CuiIwqA3c= +k8s.io/client-go v0.36.0/go.mod h1:ZKKcpwF0aLYfkHFCjillCKaTK/yBkEDHTDXCFY6AS9Y= 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= @@ -1484,13 +1484,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 535ebe0fa..ce7edef2b 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 bb3a21a01..1e2c865ec 100644 --- a/internal/graph/gengql/root_.generated.go +++ b/internal/graph/gengql/root_.generated.go @@ -3467,6 +3467,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 @@ -3592,6 +3593,10 @@ 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 @@ -18378,6 +18383,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 @@ -18887,6 +18899,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 "ValkeyTierFacetItem.count": if e.ComplexityRoot.ValkeyTierFacetItem.Count == nil { break @@ -19780,6 +19799,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputValkeyAccessOrder, ec.unmarshalInputValkeyFilter, ec.unmarshalInputValkeyOrder, + ec.unmarshalInputValkeyPersistenceInput, ec.unmarshalInputViewSecretValuesInput, ec.unmarshalInputVulnerabilitySummaryOrder, ec.unmarshalInputWorkloadLogSubscriptionFilter, @@ -31581,6 +31601,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." @@ -31611,6 +31633,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! @@ -31784,6 +31816,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 { @@ -31810,6 +31844,8 @@ input UpdateValkeyInput { databases: Int "User-defined labels for the instance. When provided, replaces all existing user-defined labels. When omitted, labels are left unchanged." labels: [ResourceLabelInput!] + "Persistence and backup settings for the Valkey instance. Defaults to enabled." + persistence: ValkeyPersistenceInput } type UpdateValkeyPayload { @@ -38019,6 +38055,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": @@ -38179,6 +38217,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_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 792bdd005..388d35c22 100644 --- a/internal/graph/gengql/valkey.generated.go +++ b/internal/graph/gengql/valkey.generated.go @@ -727,6 +727,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, @@ -2109,6 +2141,29 @@ func (ec *executionContext) fieldContext_ValkeyFacets_labels(_ context.Context, 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, @@ -2545,7 +2600,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 { @@ -2608,6 +2663,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 @@ -2668,7 +2730,7 @@ func (ec *executionContext) unmarshalInputUpdateValkeyInput(ctx context.Context, asMap[k] = v } - fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "maxMemoryPolicy", "notifyKeyspaceEvents", "databases", "labels"} + fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "maxMemoryPolicy", "notifyKeyspaceEvents", "databases", "labels", "persistence"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -2738,6 +2800,13 @@ func (ec *executionContext) unmarshalInputUpdateValkeyInput(ctx context.Context, return it, err } it.Labels = 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 @@ -2868,6 +2937,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 *************************** @@ -3295,6 +3394,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 @@ -4216,6 +4320,45 @@ 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 { @@ -4696,6 +4839,10 @@ func (ec *executionContext) marshalNValkeyPermission2githubᚗ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) @@ -4829,6 +4976,14 @@ 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/valkey.graphqls b/internal/graph/schema/valkey.graphqls index 371979d4a..4b468b88c 100644 --- a/internal/graph/schema/valkey.graphqls +++ b/internal/graph/schema/valkey.graphqls @@ -87,6 +87,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." @@ -117,6 +119,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! @@ -290,6 +302,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 { @@ -316,6 +330,8 @@ input UpdateValkeyInput { databases: Int "User-defined labels for the instance. When provided, replaces all existing user-defined labels. When omitted, labels are left unchanged." labels: [ResourceLabelInput!] + "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 086c0d475..b5869bcf1 100644 --- a/internal/persistence/valkey/models.go +++ b/internal/persistence/valkey/models.go @@ -64,6 +64,7 @@ type Valkey struct { MaxMemoryPolicy ValkeyMaxMemoryPolicy `json:"maxMemoryPolicy,omitempty"` NotifyKeyspaceEvents string `json:"notifyKeyspaceEvents,omitempty"` Databases int `json:"databases"` + Persistence ValkeyPersistence `json:"persistence"` Labels []*model.ResourceLabel `json:"labels"` TeamSlug slug.Slug `json:"-"` EnvironmentName string `json:"-"` @@ -107,6 +108,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"` @@ -207,6 +216,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) @@ -234,6 +247,7 @@ func toValkey(u *unstructured.Unstructured, envName string) (*Valkey, error) { NotifyKeyspaceEvents: notifyKeyspaceEvents, Databases: int(numberOfDatabases), Labels: model.UserLabels(obj.GetLabels()), + Persistence: ValkeyPersistence{Disabled: persistenceDisabled}, }, nil } @@ -252,6 +266,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, @@ -263,6 +282,7 @@ func toValkeyFromNais(v *naiscrd.Valkey, envName string) (*Valkey, error) { NotifyKeyspaceEvents: v.Spec.NotifyKeyspaceEvents, MaxMemoryPolicy: mmp, Databases: databases, + Persistence: persistence, }, nil } @@ -303,11 +323,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 92bad971f..31f92939e 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) { @@ -179,6 +180,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 @@ -249,6 +256,7 @@ func Update(ctx context.Context, input UpdateValkeyInput) (*UpdateValkeyPayload, updateMaxMemoryPolicy, updateNotifyKeyspaceEvents, updateDatabases, + updatePersistence, } for _, f := range updateFuncs { @@ -554,6 +562,34 @@ func formatUserLabels(labels []*model.ResourceLabel) string { return strings.Join(parts, ", ") } +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 11f399a57f29d0955a302df4780d33f20268a7aa Mon Sep 17 00:00:00 2001 From: Trong Huu Nguyen Date: Wed, 3 Jun 2026 10:03:47 +0200 Subject: [PATCH 14/21] 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 | 74 ++++--- internal/persistence/opensearch/queries.go | 51 +++++ 7 files changed, 428 insertions(+), 26 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 6900d0aa4..377c69e41 100644 --- a/internal/graph/gengql/opensearch.generated.go +++ b/internal/graph/gengql/opensearch.generated.go @@ -659,6 +659,38 @@ func (ec *executionContext) fieldContext_OpenSearch_labels(_ context.Context, fi return fc, nil } +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, @@ -2041,6 +2073,52 @@ func (ec *executionContext) fieldContext_OpenSearchFacets_labels(_ context.Conte 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) _OpenSearchTierFacetItem_tier(ctx context.Context, field graphql.CollectedField, obj *opensearch.OpenSearchTierFacetItem) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -2578,7 +2656,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 { @@ -2634,6 +2712,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 @@ -2808,6 +2893,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 { @@ -2819,7 +2941,7 @@ func (ec *executionContext) unmarshalInputUpdateOpenSearchInput(ctx context.Cont asMap[k] = v } - fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "version", "storageGB", "labels"} + fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "version", "storageGB", "labels", "shardIndexingPressure"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -2882,6 +3004,13 @@ func (ec *executionContext) unmarshalInputUpdateOpenSearchInput(ctx context.Cont return it, err } it.Labels = 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 @@ -3268,6 +3397,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 @@ -4189,6 +4323,50 @@ func (ec *executionContext) _OpenSearchFacets(ctx context.Context, sel ast.Selec 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 { @@ -4765,6 +4943,10 @@ func (ec *executionContext) marshalNOpenSearchPermission2githubᚗ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) @@ -4926,6 +5108,14 @@ 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 1e2c865ec..115a6034f 100644 --- a/internal/graph/gengql/root_.generated.go +++ b/internal/graph/gengql/root_.generated.go @@ -1650,6 +1650,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 @@ -1777,6 +1778,11 @@ 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 @@ -10358,6 +10364,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 @@ -10881,6 +10894,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 "OpenSearchTierFacetItem.count": if e.ComplexityRoot.OpenSearchTierFacetItem.Count == nil { break @@ -19741,6 +19768,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputOpenSearchAccessOrder, ec.unmarshalInputOpenSearchFilter, ec.unmarshalInputOpenSearchOrder, + ec.unmarshalInputOpenSearchShardIndexingPressureInput, ec.unmarshalInputPostgresInstanceFilter, ec.unmarshalInputPostgresInstanceOrder, ec.unmarshalInputReconcilerConfigInput, @@ -25503,6 +25531,8 @@ type OpenSearch implements Persistence & Node { storageGB: Int! "User-defined labels attached to the instance." labels: [ResourceLabel!]! + "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." @@ -25545,6 +25575,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! @@ -25689,6 +25733,8 @@ input CreateOpenSearchInput { version: OpenSearchMajorVersion! "Available storage in GB." storageGB: Int! + "Shard indexing back pressure settings. Defaults to disabled." + shardIndexingPressure: OpenSearchShardIndexingPressureInput } type CreateOpenSearchPayload { @@ -25713,6 +25759,8 @@ input UpdateOpenSearchInput { storageGB: Int! "User-defined labels for the instance. When provided, replaces all existing user-defined labels. When omitted, labels are left unchanged." labels: [ResourceLabelInput!] + "Shard indexing back pressure settings. Defaults to disabled." + shardIndexingPressure: OpenSearchShardIndexingPressureInput } type UpdateOpenSearchPayload { @@ -35693,6 +35741,8 @@ func (ec *executionContext) childFields_OpenSearch(ctx context.Context, field gr return ec.fieldContext_OpenSearch_storageGB(ctx, field) case "labels": return ec.fieldContext_OpenSearch_labels(ctx, field) + case "shardIndexingPressure": + return ec.fieldContext_OpenSearch_shardIndexingPressure(ctx, field) case "issues": return ec.fieldContext_OpenSearch_issues(ctx, field) case "activityLog": @@ -35853,6 +35903,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_OpenSearchTierFacetItem(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "tier": diff --git a/internal/graph/schema/opensearch.graphqls b/internal/graph/schema/opensearch.graphqls index 0c6887512..d1a607574 100644 --- a/internal/graph/schema/opensearch.graphqls +++ b/internal/graph/schema/opensearch.graphqls @@ -83,6 +83,8 @@ type OpenSearch implements Persistence & Node { storageGB: Int! "User-defined labels attached to the instance." labels: [ResourceLabel!]! + "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." @@ -125,6 +127,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! @@ -269,6 +285,8 @@ input CreateOpenSearchInput { version: OpenSearchMajorVersion! "Available storage in GB." storageGB: Int! + "Shard indexing back pressure settings. Defaults to disabled." + shardIndexingPressure: OpenSearchShardIndexingPressureInput } type CreateOpenSearchPayload { @@ -293,6 +311,8 @@ input UpdateOpenSearchInput { storageGB: Int! "User-defined labels for the instance. When provided, replaces all existing user-defined labels. When omitted, labels are left unchanged." labels: [ResourceLabelInput!] + "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 c69327bad..997078d0d 100644 --- a/internal/persistence/opensearch/models.go +++ b/internal/persistence/opensearch/models.go @@ -59,17 +59,18 @@ 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"` - Labels []*model.ResourceLabel `json:"labels"` - 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"` + Labels []*model.ResourceLabel `json:"labels"` + TeamSlug slug.Slug `json:"-"` + EnvironmentName string `json:"-"` + WorkloadReference *workload.Reference `json:"-"` + MajorVersion OpenSearchMajorVersion `json:"-"` } func (OpenSearch) IsPersistence() {} @@ -109,6 +110,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"` @@ -223,6 +234,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, @@ -239,22 +253,33 @@ func toOpenSearch(u *unstructured.Unstructured, envName string) (*OpenSearch, er MajorVersion: majorVersion, StorageGB: storageGB, Labels: model.UserLabels(obj.GetLabels()), + 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 } @@ -295,10 +320,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 d4762834a..7fbddda30 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) { @@ -209,6 +212,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 @@ -269,6 +279,7 @@ func Update(ctx context.Context, input UpdateOpenSearchInput) (*UpdateOpenSearch updateMemory, updateVersion, updateStorage, + updateShardIndexingPressure, } for _, f := range updateFuncs { @@ -506,6 +517,46 @@ func formatUserLabels(labels []*model.ResourceLabel) string { return strings.Join(parts, ", ") } +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 8f56e3cdb4020d502ba75b2dcc9c06195c78cf0e Mon Sep 17 00:00:00 2001 From: Trong Huu Nguyen Date: Wed, 3 Jun 2026 10:18:03 +0200 Subject: [PATCH 15/21] 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 377c69e41..c093e6678 100644 --- a/internal/graph/gengql/opensearch.generated.go +++ b/internal/graph/gengql/opensearch.generated.go @@ -691,6 +691,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, @@ -2073,6 +2105,29 @@ func (ec *executionContext) fieldContext_OpenSearchFacets_labels(_ context.Conte 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, @@ -2656,7 +2711,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 { @@ -2719,6 +2774,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 @@ -2856,6 +2918,36 @@ func (ec *executionContext) unmarshalInputOpenSearchFilter(ctx context.Context, 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 { @@ -2941,7 +3033,7 @@ func (ec *executionContext) unmarshalInputUpdateOpenSearchInput(ctx context.Cont asMap[k] = v } - fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "version", "storageGB", "labels", "shardIndexingPressure"} + fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "version", "storageGB", "labels", "shardIndexingPressure", "indices"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -3011,6 +3103,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 @@ -3402,6 +3501,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 @@ -4323,6 +4427,42 @@ func (ec *executionContext) _OpenSearchFacets(ctx context.Context, sel ast.Selec 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 { @@ -4903,6 +5043,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) @@ -5100,6 +5244,14 @@ func (ec *executionContext) unmarshalOOpenSearchFilter2ᚖgithubᚗcomᚋnaisᚋ 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 115a6034f..5640ec66c 100644 --- a/internal/graph/gengql/root_.generated.go +++ b/internal/graph/gengql/root_.generated.go @@ -1645,6 +1645,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 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 Labels func(childComplexity int) int Maintenance func(childComplexity int) int @@ -1746,6 +1747,10 @@ type ComplexityRoot struct { Tiers func(childComplexity int) int } + OpenSearchIndices struct { + QueryBoolMaxClauseCount func(childComplexity int) int + } + OpenSearchIssue struct { Event func(childComplexity int) int ID func(childComplexity int) int @@ -10324,6 +10329,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 @@ -10770,6 +10782,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.OpenSearchFacets.Tiers(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 @@ -19767,6 +19786,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputMetricsRangeInput, ec.unmarshalInputOpenSearchAccessOrder, ec.unmarshalInputOpenSearchFilter, + ec.unmarshalInputOpenSearchIndicesInput, ec.unmarshalInputOpenSearchOrder, ec.unmarshalInputOpenSearchShardIndexingPressureInput, ec.unmarshalInputPostgresInstanceFilter, @@ -25533,6 +25553,8 @@ type OpenSearch implements Persistence & Node { labels: [ResourceLabel!]! "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." @@ -25589,6 +25611,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! @@ -25735,6 +25767,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 { @@ -25761,6 +25795,8 @@ input UpdateOpenSearchInput { labels: [ResourceLabelInput!] "Shard indexing back pressure settings. Defaults to disabled." shardIndexingPressure: OpenSearchShardIndexingPressureInput + "Index settings for the OpenSearch instance." + indices: OpenSearchIndicesInput } type UpdateOpenSearchPayload { @@ -35743,6 +35779,8 @@ func (ec *executionContext) childFields_OpenSearch(ctx context.Context, field gr return ec.fieldContext_OpenSearch_labels(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": @@ -35857,6 +35895,14 @@ 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_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 d1a607574..37659931c 100644 --- a/internal/graph/schema/opensearch.graphqls +++ b/internal/graph/schema/opensearch.graphqls @@ -85,6 +85,8 @@ type OpenSearch implements Persistence & Node { labels: [ResourceLabel!]! "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." @@ -141,6 +143,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! @@ -287,6 +299,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 { @@ -313,6 +327,8 @@ input UpdateOpenSearchInput { labels: [ResourceLabelInput!] "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 997078d0d..2162424d4 100644 --- a/internal/persistence/opensearch/models.go +++ b/internal/persistence/opensearch/models.go @@ -67,6 +67,7 @@ type OpenSearch struct { StorageGB StorageGB `json:"storageGB"` ShardIndexingPressure OpenSearchShardIndexingPressure `json:"shardIndexingPressure"` Labels []*model.ResourceLabel `json:"labels"` + Indices OpenSearchIndices `json:"indices"` TeamSlug slug.Slug `json:"-"` EnvironmentName string `json:"-"` WorkloadReference *workload.Reference `json:"-"` @@ -120,6 +121,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"` @@ -237,6 +246,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, @@ -257,6 +272,7 @@ func toOpenSearch(u *unstructured.Unstructured, envName string) (*OpenSearch, er Enabled: shardIndexingPressureEnabled, Enforced: shardIndexingPressureEnforced, }, + Indices: indices, }, nil } @@ -269,6 +285,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, @@ -280,6 +301,7 @@ func toOpenSearchFromNais(o *naiscrd.OpenSearch, envName string) (*OpenSearch, e MajorVersion: majorVersion, StorageGB: StorageGB(o.Spec.StorageGB), ShardIndexingPressure: shardIndexingPressure, + Indices: indices, }, nil } @@ -325,6 +347,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 { @@ -344,6 +367,12 @@ func (o *OpenSearchInput) ValidationErrors(ctx context.Context) *validate.Valida 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 7fbddda30..17207fd73 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) { @@ -219,6 +221,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 @@ -280,6 +288,7 @@ func Update(ctx context.Context, input UpdateOpenSearchInput) (*UpdateOpenSearch updateVersion, updateStorage, updateShardIndexingPressure, + updateIndices, } for _, f := range updateFuncs { @@ -557,6 +566,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 ea9383888d920069e1f39152fa01d1b7554c4c1c Mon Sep 17 00:00:00 2001 From: Trong Huu Nguyen Date: Wed, 3 Jun 2026 10:29:35 +0200 Subject: [PATCH 16/21] 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 c093e6678..21858b87a 100644 --- a/internal/graph/gengql/opensearch.generated.go +++ b/internal/graph/gengql/opensearch.generated.go @@ -723,6 +723,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, @@ -2105,6 +2137,29 @@ func (ec *executionContext) fieldContext_OpenSearchFacets_labels(_ context.Conte 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, @@ -2711,7 +2766,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 { @@ -2781,6 +2836,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 @@ -2918,6 +2980,36 @@ 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 { @@ -3033,7 +3125,7 @@ func (ec *executionContext) unmarshalInputUpdateOpenSearchInput(ctx context.Cont asMap[k] = v } - fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "version", "storageGB", "labels", "shardIndexingPressure", "indices"} + fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "version", "storageGB", "labels", "shardIndexingPressure", "indices", "http"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -3110,6 +3202,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 @@ -3506,6 +3605,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 @@ -4427,6 +4531,42 @@ 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 { @@ -5043,6 +5183,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) } @@ -5244,6 +5388,14 @@ 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 diff --git a/internal/graph/gengql/root_.generated.go b/internal/graph/gengql/root_.generated.go index 5640ec66c..ac45ebe02 100644 --- a/internal/graph/gengql/root_.generated.go +++ b/internal/graph/gengql/root_.generated.go @@ -1644,6 +1644,7 @@ type ComplexityRoot 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 + 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 @@ -1747,6 +1748,10 @@ type ComplexityRoot struct { Tiers func(childComplexity int) int } + OpenSearchHTTP struct { + MaxContentLength func(childComplexity int) int + } + OpenSearchIndices struct { QueryBoolMaxClauseCount func(childComplexity int) int } @@ -10322,6 +10327,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.OpenSearch.Cost(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 @@ -10782,6 +10794,13 @@ 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 @@ -19786,6 +19805,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputMetricsRangeInput, ec.unmarshalInputOpenSearchAccessOrder, ec.unmarshalInputOpenSearchFilter, + ec.unmarshalInputOpenSearchHTTPInput, ec.unmarshalInputOpenSearchIndicesInput, ec.unmarshalInputOpenSearchOrder, ec.unmarshalInputOpenSearchShardIndexingPressureInput, @@ -25555,6 +25575,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." @@ -25621,6 +25643,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! @@ -25769,6 +25801,8 @@ input CreateOpenSearchInput { shardIndexingPressure: OpenSearchShardIndexingPressureInput "Index settings for the OpenSearch instance." indices: OpenSearchIndicesInput + "HTTP settings for the OpenSearch instance." + http: OpenSearchHTTPInput } type CreateOpenSearchPayload { @@ -25797,6 +25831,8 @@ input UpdateOpenSearchInput { shardIndexingPressure: OpenSearchShardIndexingPressureInput "Index settings for the OpenSearch instance." indices: OpenSearchIndicesInput + "HTTP settings for the OpenSearch instance." + http: OpenSearchHTTPInput } type UpdateOpenSearchPayload { @@ -35781,6 +35817,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": @@ -35895,6 +35933,14 @@ 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": diff --git a/internal/graph/schema/opensearch.graphqls b/internal/graph/schema/opensearch.graphqls index 37659931c..b3cd9304e 100644 --- a/internal/graph/schema/opensearch.graphqls +++ b/internal/graph/schema/opensearch.graphqls @@ -87,6 +87,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." @@ -153,6 +155,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! @@ -301,6 +313,8 @@ input CreateOpenSearchInput { shardIndexingPressure: OpenSearchShardIndexingPressureInput "Index settings for the OpenSearch instance." indices: OpenSearchIndicesInput + "HTTP settings for the OpenSearch instance." + http: OpenSearchHTTPInput } type CreateOpenSearchPayload { @@ -329,6 +343,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 2162424d4..523e270af 100644 --- a/internal/persistence/opensearch/models.go +++ b/internal/persistence/opensearch/models.go @@ -22,6 +22,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" @@ -68,6 +69,7 @@ type OpenSearch struct { ShardIndexingPressure OpenSearchShardIndexingPressure `json:"shardIndexingPressure"` Labels []*model.ResourceLabel `json:"labels"` Indices OpenSearchIndices `json:"indices"` + HTTP OpenSearchHTTP `json:"http"` TeamSlug slug.Slug `json:"-"` EnvironmentName string `json:"-"` WorkloadReference *workload.Reference `json:"-"` @@ -129,6 +131,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"` @@ -252,6 +262,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, @@ -273,6 +289,7 @@ func toOpenSearch(u *unstructured.Unstructured, envName string) (*OpenSearch, er Enforced: shardIndexingPressureEnforced, }, Indices: indices, + HTTP: http, }, nil } @@ -290,6 +307,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, @@ -302,6 +325,7 @@ func toOpenSearchFromNais(o *naiscrd.OpenSearch, envName string) (*OpenSearch, e StorageGB: StorageGB(o.Spec.StorageGB), ShardIndexingPressure: shardIndexingPressure, Indices: indices, + HTTP: http, }, nil } @@ -348,6 +372,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 { @@ -373,6 +398,15 @@ func (o *OpenSearchInput) ValidationErrors(ctx context.Context) *validate.Valida } } + 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 17207fd73..08fa39831 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) { @@ -227,6 +230,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 @@ -289,6 +302,7 @@ func Update(ctx context.Context, input UpdateOpenSearchInput) (*UpdateOpenSearch updateStorage, updateShardIndexingPressure, updateIndices, + updateHTTP, } for _, f := range updateFuncs { @@ -594,6 +608,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 456982869ec6b6b5041101d15664f5729a493840 Mon Sep 17 00:00:00 2001 From: Trong Huu Nguyen Date: Wed, 3 Jun 2026 12:10:26 +0200 Subject: [PATCH 17/21] 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/opensearch.resolvers.go | 2 +- internal/graph/schema/opensearch.graphqls | 76 +-- internal/graph/schema/valkey.graphqls | 22 +- internal/persistence/opensearch/models.go | 157 +++--- internal/persistence/opensearch/queries.go | 49 +- internal/persistence/valkey/models.go | 30 +- internal/persistence/valkey/queries.go | 14 +- 12 files changed, 390 insertions(+), 1078 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 ce7edef2b..d807efadd 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 21858b87a..96af8dbda 100644 --- a/internal/graph/gengql/opensearch.generated.go +++ b/internal/graph/gengql/opensearch.generated.go @@ -659,100 +659,96 @@ func (ec *executionContext) fieldContext_OpenSearch_labels(_ context.Context, fi return fc, nil } -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) { @@ -2137,98 +2133,6 @@ func (ec *executionContext) fieldContext_OpenSearchFacets_labels(_ context.Conte 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, @@ -2766,7 +2670,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 { @@ -2822,27 +2726,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 @@ -2980,66 +2891,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 { @@ -3077,43 +2928,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 { @@ -3125,7 +2939,7 @@ func (ec *executionContext) unmarshalInputUpdateOpenSearchInput(ctx context.Cont asMap[k] = v } - fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "version", "storageGB", "labels", "shardIndexingPressure", "indices", "http"} + fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "version", "storageGB", "labels", "shardIndexingPressureEnabled", "shardIndexingPressureEnforced", "indicesQueryBoolMaxClauseCount", "httpMaxContentLength"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -3188,27 +3002,34 @@ func (ec *executionContext) unmarshalInputUpdateOpenSearchInput(ctx context.Cont return it, err } it.Labels = 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 @@ -3595,21 +3416,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 @@ -4531,122 +4351,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 { @@ -5183,14 +4887,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) @@ -5231,10 +4927,6 @@ func (ec *executionContext) marshalNOpenSearchPermission2githubᚗ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) @@ -5388,22 +5080,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 @@ -5412,14 +5088,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 ac45ebe02..5ea9e1287 100644 --- a/internal/graph/gengql/root_.generated.go +++ b/internal/graph/gengql/root_.generated.go @@ -1641,26 +1641,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 - 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 - Labels func(childComplexity int) 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 + 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 + Labels func(childComplexity int) 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 { @@ -1748,14 +1749,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 @@ -1788,11 +1781,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 @@ -3483,7 +3471,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 @@ -3609,10 +3597,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 @@ -10327,12 +10311,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.OpenSearch.Cost(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 { @@ -10341,12 +10325,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 { @@ -10388,12 +10372,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 { @@ -10794,20 +10785,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 @@ -10932,20 +10909,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 @@ -18448,12 +18411,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 { @@ -18964,13 +18927,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 @@ -19805,10 +19761,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, @@ -19867,7 +19820,6 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputValkeyAccessOrder, ec.unmarshalInputValkeyFilter, ec.unmarshalInputValkeyOrder, - ec.unmarshalInputValkeyPersistenceInput, ec.unmarshalInputViewSecretValuesInput, ec.unmarshalInputVulnerabilitySummaryOrder, ec.unmarshalInputWorkloadLogSubscriptionFilter, @@ -25571,12 +25523,14 @@ type OpenSearch implements Persistence & Node { storageGB: Int! "User-defined labels attached to the instance." labels: [ResourceLabel!]! - "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." @@ -25619,40 +25573,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! @@ -25797,12 +25717,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 { @@ -25827,12 +25749,14 @@ input UpdateOpenSearchInput { storageGB: Int! "User-defined labels for the instance. When provided, replaces all existing user-defined labels. When omitted, labels are left unchanged." labels: [ResourceLabelInput!] - "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 { @@ -31721,8 +31645,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." @@ -31753,16 +31677,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! @@ -31936,8 +31850,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 { @@ -31964,8 +31878,8 @@ input UpdateValkeyInput { databases: Int "User-defined labels for the instance. When provided, replaces all existing user-defined labels. When omitted, labels are left unchanged." labels: [ResourceLabelInput!] - "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 { @@ -35813,12 +35727,14 @@ func (ec *executionContext) childFields_OpenSearch(ctx context.Context, field gr return ec.fieldContext_OpenSearch_storageGB(ctx, field) case "labels": return ec.fieldContext_OpenSearch_labels(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": @@ -35933,22 +35849,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": @@ -35995,16 +35895,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": @@ -38207,8 +38097,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": @@ -38369,14 +38259,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 388d35c22..2fd7ac7b6 100644 --- a/internal/graph/gengql/valkey.generated.go +++ b/internal/graph/gengql/valkey.generated.go @@ -727,36 +727,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) { @@ -2141,29 +2132,6 @@ func (ec *executionContext) fieldContext_ValkeyFacets_labels(_ context.Context, 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, @@ -2600,7 +2568,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 { @@ -2663,13 +2631,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 @@ -2730,7 +2698,7 @@ func (ec *executionContext) unmarshalInputUpdateValkeyInput(ctx context.Context, asMap[k] = v } - fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "maxMemoryPolicy", "notifyKeyspaceEvents", "databases", "labels", "persistence"} + fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "tier", "memory", "maxMemoryPolicy", "notifyKeyspaceEvents", "databases", "labels", "persistenceDisabled"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -2800,13 +2768,13 @@ func (ec *executionContext) unmarshalInputUpdateValkeyInput(ctx context.Context, return it, err } it.Labels = 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 @@ -2937,36 +2905,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 *************************** @@ -3394,8 +3332,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) } @@ -4320,45 +4258,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 { @@ -4839,10 +4738,6 @@ func (ec *executionContext) marshalNValkeyPermission2githubᚗ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) @@ -4976,14 +4871,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/opensearch.resolvers.go b/internal/graph/opensearch.resolvers.go index 06e8dd9ac..9b110eef2 100644 --- a/internal/graph/opensearch.resolvers.go +++ b/internal/graph/opensearch.resolvers.go @@ -124,7 +124,7 @@ func (r *teamEnvironmentResolver) OpenSearch(ctx context.Context, obj *team.Team func (r *teamInventoryCountsResolver) OpenSearches(ctx context.Context, obj *team.TeamInventoryCounts) (*opensearch.TeamInventoryCountOpenSearches, error) { return &opensearch.TeamInventoryCountOpenSearches{ - Total: len(opensearch.ListAllForTeam(ctx, obj.TeamSlug, nil)), + Total: len(opensearch.ListAllForTeam(ctx, obj.TeamSlug)), }, nil } diff --git a/internal/graph/schema/opensearch.graphqls b/internal/graph/schema/opensearch.graphqls index b3cd9304e..357cc93eb 100644 --- a/internal/graph/schema/opensearch.graphqls +++ b/internal/graph/schema/opensearch.graphqls @@ -83,12 +83,14 @@ type OpenSearch implements Persistence & Node { storageGB: Int! "User-defined labels attached to the instance." labels: [ResourceLabel!]! - "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." @@ -131,40 +133,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! @@ -309,12 +277,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 { @@ -339,12 +309,14 @@ input UpdateOpenSearchInput { storageGB: Int! "User-defined labels for the instance. When provided, replaces all existing user-defined labels. When omitted, labels are left unchanged." labels: [ResourceLabelInput!] - "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 4b468b88c..dd8f1cd46 100644 --- a/internal/graph/schema/valkey.graphqls +++ b/internal/graph/schema/valkey.graphqls @@ -87,8 +87,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." @@ -119,16 +119,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! @@ -302,8 +292,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 { @@ -330,8 +320,8 @@ input UpdateValkeyInput { databases: Int "User-defined labels for the instance. When provided, replaces all existing user-defined labels. When omitted, labels are left unchanged." labels: [ResourceLabelInput!] - "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 523e270af..ddab5958a 100644 --- a/internal/persistence/opensearch/models.go +++ b/internal/persistence/opensearch/models.go @@ -60,20 +60,23 @@ 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"` - Labels []*model.ResourceLabel `json:"labels"` - 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"` + Labels []*model.ResourceLabel `json:"labels"` + + 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() {} @@ -113,32 +116,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"` @@ -256,16 +233,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{ @@ -277,55 +254,54 @@ 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, - Labels: model.UserLabels(obj.GetLabels()), - 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, + Labels: model.UserLabels(obj.GetLabels()), + 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 } @@ -366,13 +342,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 { @@ -392,18 +369,18 @@ func (o *OpenSearchInput) ValidationErrors(ctx context.Context) *validate.Valida 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 08fa39831..824f1ba08 100644 --- a/internal/persistence/opensearch/queries.go +++ b/internal/persistence/opensearch/queries.go @@ -25,6 +25,8 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/utils/ptr" ) var ( @@ -217,21 +219,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 } @@ -541,7 +543,7 @@ func formatUserLabels(labels []*model.ResourceLabel) string { } func updateShardIndexingPressure(openSearch *naiscrd.OpenSearch, input UpdateOpenSearchInput) ([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, error) { - if input.ShardIndexingPressure == nil { + if input.ShardIndexingPressureEnabled == nil && input.ShardIndexingPressureEnforced == nil { return nil, nil } @@ -551,20 +553,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)), }) } @@ -573,15 +578,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 } @@ -589,7 +594,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 @@ -601,7 +606,7 @@ func updateIndices(openSearch *naiscrd.OpenSearch, input UpdateOpenSearchInput) return []*OpenSearchUpdatedActivityLogEntryDataUpdatedField{ { - Field: "indices.queryBoolMaxClauseCount", + Field: "indicesQueryBoolMaxClauseCount", OldValue: intPtrToStringPtr(oldCount), NewValue: intPtrToStringPtr(newCount), }, @@ -609,11 +614,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 } @@ -634,7 +639,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 b5869bcf1..e0f429741 100644 --- a/internal/persistence/valkey/models.go +++ b/internal/persistence/valkey/models.go @@ -64,8 +64,8 @@ type Valkey struct { MaxMemoryPolicy ValkeyMaxMemoryPolicy `json:"maxMemoryPolicy,omitempty"` NotifyKeyspaceEvents string `json:"notifyKeyspaceEvents,omitempty"` Databases int `json:"databases"` - Persistence ValkeyPersistence `json:"persistence"` Labels []*model.ResourceLabel `json:"labels"` + PersistenceDisabled bool `json:"persistenceDisabled"` TeamSlug slug.Slug `json:"-"` EnvironmentName string `json:"-"` WorkloadReference *workload.Reference `json:"-"` @@ -108,14 +108,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"` @@ -247,7 +239,7 @@ func toValkey(u *unstructured.Unstructured, envName string) (*Valkey, error) { NotifyKeyspaceEvents: notifyKeyspaceEvents, Databases: int(numberOfDatabases), Labels: model.UserLabels(obj.GetLabels()), - Persistence: ValkeyPersistence{Disabled: persistenceDisabled}, + PersistenceDisabled: persistenceDisabled, }, nil } @@ -266,9 +258,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{ @@ -282,7 +274,7 @@ func toValkeyFromNais(v *naiscrd.Valkey, envName string) (*Valkey, error) { NotifyKeyspaceEvents: v.Spec.NotifyKeyspaceEvents, MaxMemoryPolicy: mmp, Databases: databases, - Persistence: persistence, + PersistenceDisabled: persistenceDisabled, }, nil } @@ -323,12 +315,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 31f92939e..cee6cf683 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, } } @@ -563,7 +563,7 @@ func formatUserLabels(labels []*model.ResourceLabel) string { } func updatePersistence(valkey *naiscrd.Valkey, input UpdateValkeyInput) ([]*ValkeyUpdatedActivityLogEntryDataUpdatedField, error) { - if input.Persistence == nil { + if input.PersistenceDisabled == nil { return nil, nil } @@ -572,20 +572,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 } From 89e85a1b6afcf0fdaad4f9ff9a44e83bf271a995 Mon Sep 17 00:00:00 2001 From: Christer Edvartsen Date: Tue, 1 Sep 2026 10:54:42 +0200 Subject: [PATCH 18/21] fix: remove unnecessary conversion --- internal/persistence/valkey/queries.go | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/internal/persistence/valkey/queries.go b/internal/persistence/valkey/queries.go index cee6cf683..79630a84b 100644 --- a/internal/persistence/valkey/queries.go +++ b/internal/persistence/valkey/queries.go @@ -269,14 +269,8 @@ func Update(ctx context.Context, input UpdateValkeyInput) (*UpdateValkeyPayload, changes = append(changes, updateLabels(valkey, input)...) - // 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 + concreteValkey.Spec.Databases = input.Databases } if len(changes) == 0 { @@ -299,12 +293,6 @@ 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{}) From b17870b7d2b818f71dfc4e13c5160fdcfeaeedf3 Mon Sep 17 00:00:00 2001 From: Christer Edvartsen Date: Tue, 1 Sep 2026 10:55:23 +0200 Subject: [PATCH 19/21] build: bump nais/tester --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 0da8dcfdb..67376bd24 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 github.com/grafana/loki/v3 v3.7.1 - github.com/jackc/pgx/v5 v5.9.2 + github.com/jackc/pgx/v5 v5.10.0 github.com/joho/godotenv v1.5.1 github.com/lestrrat-go/httprc/v3 v3.0.0-beta2 github.com/lestrrat-go/jwx/v3 v3.0.1 @@ -42,7 +42,7 @@ require ( 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-20260528100930-7dcc162c09d6 - github.com/nais/tester v0.1.1 + github.com/nais/tester v0.2.0 github.com/nais/unleasherator v0.0.0-20251216221129-efebc54203fe github.com/nais/v13s/pkg/api v0.0.0-20260826091953-1b518b13ca28 github.com/patrickmn/go-cache v2.1.0+incompatible @@ -61,7 +61,7 @@ require ( github.com/testcontainers/testcontainers-go/modules/postgres v0.35.0 github.com/vektah/gqlparser/v2 v2.5.33 github.com/vikstrous/dataloadgen v0.0.6 - github.com/yuin/gopher-lua v1.1.1 + github.com/yuin/gopher-lua v1.1.2 github.com/zitadel/oidc/v3 v3.33.1 github.com/zitadel/zitadel-go/v3 v3.4.3 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 diff --git a/go.sum b/go.sum index 65eaa4c3f..ac958d244 100644 --- a/go.sum +++ b/go.sum @@ -633,8 +633,8 @@ github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= -github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= -github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= +github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jaegertracing/jaeger-idl v0.6.0 h1:LOVQfVby9ywdMPI9n3hMwKbyLVV3BL1XH2QqsP5KTMk= @@ -809,8 +809,8 @@ github.com/nais/liberator v0.0.0-20260216142648-ee49a9372bc4 h1:i7jBukqLtNpQIBhy github.com/nais/liberator v0.0.0-20260216142648-ee49a9372bc4/go.mod h1:jmMoQtUMhvv7j1C2gz89Gxc4hxc73GXtTR3mEXn7cvU= 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/tester v0.2.0 h1:lcTkDP52ddw9l5s3KC4snUP+GlUpZMUtJ3XR2vF4G0w= +github.com/nais/tester v0.2.0/go.mod h1:Pp7CtcVk/NZI3z0MW7V4j8CPMG5lhKZeAV4IaJKcslo= github.com/nais/unleasherator v0.0.0-20251216221129-efebc54203fe h1:CdRVopOihru4tXVwKZjhg6C8SbPLCQYOhJKpjBZYhjg= github.com/nais/unleasherator v0.0.0-20251216221129-efebc54203fe/go.mod h1:Tiz/1If3WgcfvNhmsO5DiQC+L+1XhBG3KWbIfbjx4EU= github.com/nais/v13s/pkg/api v0.0.0-20260826091953-1b518b13ca28 h1:qrtcAJlM/usxn+8pWdqKGgbQ79cAx0SGxtn8UVjn0mE= @@ -1101,8 +1101,8 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= -github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +github.com/yuin/gopher-lua v1.1.2 h1:yF/FjE3hD65tBbt0VXLE13HWS9h34fdzJmrWRXwobGA= +github.com/yuin/gopher-lua v1.1.2/go.mod h1:7aRmXIWl37SqRf0koeyylBEzJ+aPt8A+mmkQ4f1ntR8= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= From 579a5c4fce935803090b07219c5975774fa87ee9 Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Tue, 1 Sep 2026 11:23:10 +0200 Subject: [PATCH 20/21] Fix label updates and expose Valkey labels - Include label updates in shared update function pipeline for Valkey and OpenSearch - Switch label update helpers to typed CRDs and return errors consistently - Fix Valkey database comparison/update logic to avoid nil/pointer issues - Return labels in Valkey model mapping - Update Valkey integration test to check `nais.io/v1` and correct resource name --- integration_tests/valkey_crud.lua | 6 +++--- internal/persistence/opensearch/queries.go | 12 +++++------- internal/persistence/valkey/models.go | 1 + internal/persistence/valkey/queries.go | 19 ++++++++----------- 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/integration_tests/valkey_crud.lua b/integration_tests/valkey_crud.lua index d807efadd..fda8a679c 100644 --- a/integration_tests/valkey_crud.lua +++ b/integration_tests/valkey_crud.lua @@ -1107,10 +1107,10 @@ Test.gql( end) Test.k8s("Validate Valkey labels after update", function(t) - local resourceName = string.format("valkey-%s-labels-valkey", mainTeam:slug()) + local resourceName = "labels-valkey" - t.check("aiven.io/v1alpha1", "valkeys", "dev", mainTeam:slug(), resourceName, { - apiVersion = "aiven.io/v1alpha1", + t.check("nais.io/v1", "valkeys", "dev", mainTeam:slug(), resourceName, { + apiVersion = "nais.io/v1", kind = "Valkey", metadata = { name = resourceName, diff --git a/internal/persistence/opensearch/queries.go b/internal/persistence/opensearch/queries.go index 824f1ba08..8cf80e76c 100644 --- a/internal/persistence/opensearch/queries.go +++ b/internal/persistence/opensearch/queries.go @@ -25,7 +25,6 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/utils/ptr" ) @@ -305,6 +304,7 @@ func Update(ctx context.Context, input UpdateOpenSearchInput) (*UpdateOpenSearch updateShardIndexingPressure, updateIndices, updateHTTP, + updateLabels, } for _, f := range updateFuncs { @@ -315,8 +315,6 @@ func Update(ctx context.Context, input UpdateOpenSearchInput) (*UpdateOpenSearch changes = append(changes, res...) } - changes = append(changes, updateLabels(openSearch, input)...) - if len(changes) == 0 { os, err := toOpenSearch(openSearch, input.EnvironmentName) if err != nil { @@ -515,9 +513,9 @@ func updateStorage(openSearch *naiscrd.OpenSearch, input UpdateOpenSearchInput) return changes, nil } -func updateLabels(openSearch *unstructured.Unstructured, input UpdateOpenSearchInput) []*OpenSearchUpdatedActivityLogEntryDataUpdatedField { +func updateLabels(openSearch *naiscrd.OpenSearch, input UpdateOpenSearchInput) ([]*OpenSearchUpdatedActivityLogEntryDataUpdatedField, error) { if input.Labels == nil { - return nil + return nil, nil } existing := openSearch.GetLabels() @@ -525,13 +523,13 @@ func updateLabels(openSearch *unstructured.Unstructured, input UpdateOpenSearchI merged := model.MergeUserLabels(existing, input.Labels) newValue := formatUserLabels(model.UserLabels(merged)) if oldValue == newValue { - return nil + return nil, nil } openSearch.SetLabels(merged) return []*OpenSearchUpdatedActivityLogEntryDataUpdatedField{ {Field: "labels", OldValue: &oldValue, NewValue: &newValue}, - } + }, nil } func formatUserLabels(labels []*model.ResourceLabel) string { diff --git a/internal/persistence/valkey/models.go b/internal/persistence/valkey/models.go index e0f429741..eec5e33dc 100644 --- a/internal/persistence/valkey/models.go +++ b/internal/persistence/valkey/models.go @@ -275,6 +275,7 @@ func toValkeyFromNais(v *naiscrd.Valkey, envName string) (*Valkey, error) { MaxMemoryPolicy: mmp, Databases: databases, PersistenceDisabled: persistenceDisabled, + Labels: model.UserLabels(v.GetLabels()), }, nil } diff --git a/internal/persistence/valkey/queries.go b/internal/persistence/valkey/queries.go index 79630a84b..7bf7bc3ed 100644 --- a/internal/persistence/valkey/queries.go +++ b/internal/persistence/valkey/queries.go @@ -257,6 +257,7 @@ func Update(ctx context.Context, input UpdateValkeyInput) (*UpdateValkeyPayload, updateNotifyKeyspaceEvents, updateDatabases, updatePersistence, + updateLabels, } for _, f := range updateFuncs { @@ -267,12 +268,6 @@ func Update(ctx context.Context, input UpdateValkeyInput) (*UpdateValkeyPayload, changes = append(changes, res...) } - changes = append(changes, updateLabels(valkey, input)...) - - if input.Databases != nil { - concreteValkey.Spec.Databases = input.Databases - } - if len(changes) == 0 { v, err := kubernetes.ToConcrete[naiscrd.Valkey](valkey) if err != nil { @@ -498,7 +493,7 @@ func updateDatabases(valkey *naiscrd.Valkey, input UpdateValkeyInput) ([]*Valkey } oldValue := valkey.Spec.Databases - if oldValue == input.Databases { + if oldValue != nil && *oldValue == *input.Databases { return changes, nil } @@ -513,12 +508,14 @@ func updateDatabases(valkey *naiscrd.Valkey, input UpdateValkeyInput) ([]*Valkey NewValue: new(strconv.Itoa(*input.Databases)), }) + valkey.Spec.Databases = input.Databases + return changes, nil } -func updateLabels(valkey *unstructured.Unstructured, input UpdateValkeyInput) []*ValkeyUpdatedActivityLogEntryDataUpdatedField { +func updateLabels(valkey *naiscrd.Valkey, input UpdateValkeyInput) ([]*ValkeyUpdatedActivityLogEntryDataUpdatedField, error) { if input.Labels == nil { - return nil + return nil, nil } existing := valkey.GetLabels() @@ -528,7 +525,7 @@ func updateLabels(valkey *unstructured.Unstructured, input UpdateValkeyInput) [] newValue := formatUserLabels(model.UserLabels(merged)) if oldValue == newValue { - return nil + return nil, nil } valkey.SetLabels(merged) @@ -539,7 +536,7 @@ func updateLabels(valkey *unstructured.Unstructured, input UpdateValkeyInput) [] OldValue: &oldValue, NewValue: &newValue, }, - } + }, nil } func formatUserLabels(labels []*model.ResourceLabel) string { From 8c27be919dd9778101b8f3228fd63b313a493748 Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Tue, 1 Sep 2026 12:26:14 +0200 Subject: [PATCH 21/21] Some fixes --- internal/persistence/opensearch/models.go | 1 + internal/persistence/opensearch/queries.go | 2 +- internal/persistence/valkey/queries.go | 14 ++++++++++++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/internal/persistence/opensearch/models.go b/internal/persistence/opensearch/models.go index ddab5958a..95fb84d99 100644 --- a/internal/persistence/opensearch/models.go +++ b/internal/persistence/opensearch/models.go @@ -298,6 +298,7 @@ func toOpenSearchFromNais(o *naiscrd.OpenSearch, envName string) (*OpenSearch, e Memory: fromMapperatorMemory(o.Spec.Memory), MajorVersion: majorVersion, StorageGB: StorageGB(o.Spec.StorageGB), + Labels: model.UserLabels(o.GetLabels()), ShardIndexingPressureEnabled: shardIndexingPressureEnabled, ShardIndexingPressureEnforced: shardIndexingPressureEnforced, IndicesQueryBoolMaxClauseCount: queryBoolMaxClauseCount, diff --git a/internal/persistence/opensearch/queries.go b/internal/persistence/opensearch/queries.go index 8cf80e76c..b45d40f13 100644 --- a/internal/persistence/opensearch/queries.go +++ b/internal/persistence/opensearch/queries.go @@ -316,7 +316,7 @@ func Update(ctx context.Context, input UpdateOpenSearchInput) (*UpdateOpenSearch } if len(changes) == 0 { - os, err := toOpenSearch(openSearch, input.EnvironmentName) + os, err := toOpenSearchFromNais(concreteOpenSearch, input.EnvironmentName) if err != nil { return nil, err } diff --git a/internal/persistence/valkey/queries.go b/internal/persistence/valkey/queries.go index 7bf7bc3ed..7c1f6098d 100644 --- a/internal/persistence/valkey/queries.go +++ b/internal/persistence/valkey/queries.go @@ -107,11 +107,21 @@ 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 := ListAllForTeam(ctx, teamSlug) + all := fromContext(ctx).watcher.GetByNamespace( + teamSlug.String(), + watcher.InCluster(environmentName), + watcher.WithoutDeleted(), + ) + allNais := fromContext(ctx).naisWatcher.GetByNamespace( + teamSlug.String(), + watcher.InCluster(environmentName), + watcher.WithoutDeleted(), + ) + all = append(all, allNais...) ret := make([]*Valkey, 0) for _, ref := range references { - for _, d := range all { + for _, d := range watcher.Objects(all) { if d.FullyQualifiedName() == instanceNamer(teamSlug, ref.Instance) || d.FullyQualifiedName() == ref.Instance { ret = append(ret, d) }