Add support for team-managed configs (ConfigMaps) via GraphQL API#364
Add support for team-managed configs (ConfigMaps) via GraphQL API#364frodesundby merged 8 commits intomainfrom
Conversation
frodesundby
commented
Mar 17, 2026
- Introduce configmap domain with models, queries, and activity log types
- Add GraphQL schema for configs, including CRUD mutations and filtering
- Implement authorization for config operations
- Add config listing, value management, and usage tracking for teams, applications, and jobs
- Update workload interfaces and models to support GetConfigs
- Register configmap watcher and loader
- Add database migration for config authorizations and role grants
- Generate GraphQL code for new config types and connections
- Introduce configmap domain with models, queries, and activity log types - Add GraphQL schema for configs, including CRUD mutations and filtering - Implement authorization for config operations - Add config listing, value management, and usage tracking for teams, applications, and jobs - Update workload interfaces and models to support GetConfigs - Register configmap watcher and loader - Add database migration for config authorizations and role grants - Generate GraphQL code for new config types and connections
jhrv
left a comment
There was a problem hiding this comment.
Review
Bra PR. Følger secret-mønsteret og forenkler der det gir mening. Noen ting:
Sort etter slice i Workloads
configmap.resolvers.go — sorteringen skjer etter pagination.Slice, så du sorterer bare innenfor én side, ikke hele listen. Flytt sorteringen opp.
Applications() og Jobs() krysser environments
En Config tilhører ett environment, men begge resolverene bruker ListAllForTeam som går på tvers. En config i dev kan da vise apper fra prod som tilfeldigvis har en config med samme navn. Workloads() gjør det riktig med ListAllForTeamInEnvironment. Samme problem i InUse-filteret i sortfilter.go.
Delete sjekker ikke configIsManagedByConsole
De andre mutasjonene gjør det. Fin anledning til å ta #329 i samme slengen.
GetSecrets() mangler tom-streng guard
GetConfigs() har if v.ConfigMap != "" — GetSecrets() har det ikke. Fiks gjerne begge veier.
Tester
Secret har tre integrasjonstester (secrets.lua, secrets_cross_team.lua, workload_secrets.lua) som dekker CRUD, cross-team authz og workload-relasjoner. Tilsvarende gir mening her:
configs.lua— CRUD-livssyklus, feiltilfeller (duplikater, unmanaged), authz for non-membersconfigs_cross_team.lua— Andre teams kan ikke mutere, men kan se verdier (ikke sensitive)workload_configs.lua— Bidireksjonale relasjoner via envFrom/filesFrom fixtures
|
Mtp at denne er endret, kanskje bare starte med å støtte det fra starten? |
|
Ikke negativ til det. Er det noen ulemper med å ta det separat men rett etterpå - og for både secret og config? |
|
Ingen problem med å ta det som oppfølger nei, tenkte bare om det kanskje kan være litt mindre jobb dersom det blir med nå :) |
Includes tests for config CRUD, cross-team access, workload usage, and activity log entries. Adds test K8s resources for configs and applications. Updates config activity log schema and related code.
Secrets use SystemAuthenticatedClient for all mutations too - ImpersonatedClient is only used for reading secret values (which requires temporary RBAC). ConfigMaps don't have sensitive values, so SystemAuthenticatedClient is correct.
ConfigMaps are not sensitive, and developers already have full RBAC access to configmaps via nais:developer ClusterRole. Using ImpersonatedClient gives us Kubernetes audit log entries tied to the actual user, not the nais-api service account.
# Conflicts: # internal/graph/gengql/activitylog.generated.go
There was a problem hiding this comment.
Pull request overview
Adds a new “configmap/config” domain to the API, exposing team-managed Kubernetes ConfigMaps via GraphQL (listing, lookup, CRUD/value mutations), wiring it into workload usage tracking, activity log, authz, watchers/loaders, and integration tests.
Changes:
- Extend the Workload model and workload implementations (Application/Job) to report referenced ConfigMaps.
- Introduce
internal/workload/configmapwith watcher-backed listing/querying, mutations, filtering/sorting, and activity log types. - Add GraphQL schema + generated bindings/resolvers, authz checks, watcher/loader registration, DB migration, and integration test coverage.
Reviewed changes
Copilot reviewed 46 out of 48 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| internal/workload/models.go | Extends Workload interface with GetConfigs() for configmap usage tracking. |
| internal/workload/job/models.go | Implements GetConfigs() for jobs; tightens GetSecrets() to skip empty refs. |
| internal/workload/application/models.go | Implements GetConfigs() for applications; tightens GetSecrets() to skip empty refs. |
| internal/workload/configmap/sortfilter.go | Adds sort/filter logic for configs, including “in use” filtering via workload references. |
| internal/workload/configmap/queries.go | Implements listing, lookup, CRUD, and value mutations against ConfigMaps via dynamic client + activity logging. |
| internal/workload/configmap/node.go | Registers config nodes with the global ident system (CFG). |
| internal/workload/configmap/models.go | Defines Config models, GraphQL order/filter types, and conversion from K8s objects. |
| internal/workload/configmap/errors.go | Adds GraphQL-friendly domain errors (unmanaged, already exists). |
| internal/workload/configmap/dataloader.go | Registers watcher + loader context for configmaps. |
| internal/workload/configmap/activitylog.go | Registers activity log transformers/filters for config events. |
| internal/kubernetes/watchers/watchers.go | Registers/configures the new ConfigMap watcher in the watcher set. |
| internal/kubernetes/clientset.go | Exports common “last modified” annotation constants used by configmap conversion. |
| internal/graph/schema/configmap.graphqls | Adds GraphQL schema for configs (queries, mutations, connections, activity log types). |
| internal/graph/gengql/valkey.generated.go | Regenerated GraphQL bindings referencing Team.configs / TeamEnvironment.config. |
| internal/graph/gengql/utilization.generated.go | Regenerated GraphQL bindings referencing Team.configs / TeamEnvironment.config. |
| internal/graph/gengql/teams.generated.go | Regenerated bindings for Team/TeamEnvironment/InventoryCounts config fields and resolvers. |
| internal/graph/gengql/sqlinstance.generated.go | Regenerated bindings referencing Team.configs / TeamEnvironment.config. |
| internal/graph/gengql/serviceaccounts.generated.go | Regenerated bindings referencing Team.configs. |
| internal/graph/gengql/secret.generated.go | Regenerated bindings referencing Team.configs / TeamEnvironment.config. |
| internal/graph/gengql/schema.generated.go | Regenerated root schema bindings for config mutations and node unions. |
| internal/graph/gengql/repository.generated.go | Regenerated bindings referencing Team.configs. |
| internal/graph/gengql/reconcilers.generated.go | Regenerated bindings referencing Team.configs. |
| internal/graph/gengql/postgres.generated.go | Regenerated bindings referencing Team.configs / TeamEnvironment.config. |
| internal/graph/gengql/opensearch.generated.go | Regenerated bindings referencing Team.configs / TeamEnvironment.config. |
| internal/graph/gengql/netpol.generated.go | Regenerated bindings referencing Team.configs. |
| internal/graph/gengql/kafka.generated.go | Regenerated bindings referencing Team.configs / TeamEnvironment.config. |
| internal/graph/gengql/jobs.generated.go | Regenerated bindings adding Job.configs field plumbing. |
| internal/graph/gengql/issues.generated.go | Regenerated bindings referencing TeamEnvironment.config and workload configs fields. |
| internal/graph/gengql/complexity.go | Adds GraphQL complexity accounting for config-related connection fields. |
| internal/graph/gengql/bucket.generated.go | Regenerated bindings referencing Team.configs / TeamEnvironment.config. |
| internal/graph/gengql/bigquery.generated.go | Regenerated bindings referencing Team.configs / TeamEnvironment.config. |
| internal/graph/gengql/applications.generated.go | Regenerated bindings adding Application.configs field plumbing. |
| internal/graph/gengql/alerts.generated.go | Regenerated bindings referencing Team.configs / TeamEnvironment.config. |
| internal/graph/gengql/activitylog.generated.go | Regenerated bindings to include config activity log entry types and Config as ActivityLogger. |
| internal/graph/configmap.resolvers.go | Adds resolvers for config fields/mutations and usage relationships (apps/jobs/workloads). |
| internal/database/migrations/0060_add_config_authorizations.sql | Adds DB authorizations + grants for config create/update/delete. |
| internal/cmd/api/http.go | Wires configmap loader context into GraphQL request context. |
| internal/auth/authz/queries.go | Adds authz helper functions for config create/update/delete. |
| integration_tests/workload_configs.lua | Adds integration tests for workload→configs resolution. |
| integration_tests/k8s_resources/configs/staging/myteam/configmaps.yaml | Adds configmap fixtures (managed/unmanaged) for staging. |
| integration_tests/k8s_resources/configs/staging/myteam/applications.yaml | Adds application fixtures using configs via filesFrom. |
| integration_tests/k8s_resources/configs/dev/myteam/configmaps.yaml | Adds configmap fixtures (managed/unmanaged) for dev. |
| integration_tests/k8s_resources/configs/dev/myteam/applications.yaml | Adds application fixtures using configs via envFrom. |
| integration_tests/configs_cross_team.lua | Adds cross-team access control tests for config read vs mutation authz. |
| integration_tests/configs.lua | Adds comprehensive config CRUD/value + activity log integration tests. |
| .configs/gqlgen.yaml | Adds configmap package to gqlgen autobind for schema/model mapping. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
IsEnvVarName rejects valid ConfigMap keys containing hyphens and dots (e.g. 'api-url', 'config.file'). IsConfigMapKey validates against actual Kubernetes ConfigMap key rules and handles length validation internally.