diff --git a/charts/console-backend-rbac/templates/rbac.yaml b/charts/console-backend-rbac/templates/rbac.yaml index 18244f802..0d6b55a16 100644 --- a/charts/console-backend-rbac/templates/rbac.yaml +++ b/charts/console-backend-rbac/templates/rbac.yaml @@ -119,6 +119,16 @@ rules: - update - patch - delete + - apiGroups: + - "nais.io" + resources: + - postgres + verbs: + - get + - list + - watch + - create + - update - apiGroups: - "nais.io" resources: @@ -126,6 +136,7 @@ rules: verbs: - get - create + - delete - apiGroups: - "nais.io" resources: @@ -302,6 +313,7 @@ rules: verbs: - get - create + - delete - apiGroups: - "nais.io" resources: diff --git a/integration_tests/create_postgres_access.lua b/integration_tests/create_postgres_access.lua index b4e042cdc..c8952a564 100644 --- a/integration_tests/create_postgres_access.lua +++ b/integration_tests/create_postgres_access.lua @@ -1,10 +1,14 @@ local user = User.new("user", "user@usersen.com") local otherMemberUser = User.new("othermember", "othermember@usersen.com") +local creatorUser = User.new("creator", "creator@usersen.com") +local staleUser = User.new("stale", "stale@usersen.com") local nonMemberUser = User.new("nonmember", "other@user.com") local mainTeam = Team.new("someteamname", "purpose", "#slack_channel") mainTeam:addMember(user) mainTeam:addMember(otherMemberUser) +mainTeam:addMember(creatorUser) +mainTeam:addMember(staleUser) Helper.readK8sResources("k8s_resources/create_postgres_access") @@ -151,7 +155,7 @@ Test.gql("Create personal postgres access rejects an unavailable instance", func end) Test.gql("Create personal postgres access", function(t) - t.addHeader("x-user-email", user:email()) + t.addHeader("x-user-email", creatorUser:email()) t.query [[ mutation CreatePostgresAccess { createPostgresAccess(input: { @@ -179,7 +183,7 @@ Test.gql("Create personal postgres access", function(t) end) Test.gql("Personal postgres access is audited as a self-grant", function(t) - t.addHeader("x-user-email", user:email()) + t.addHeader("x-user-email", creatorUser:email()) t.query [[ { team(slug: "someteamname") { @@ -206,9 +210,9 @@ Test.gql("Personal postgres access is audited as a self-grant", function(t) activityLog = { nodes = { { - message = Contains("Requested READWRITE personal Postgres access for user@usersen.com"), + message = Contains("Requested READWRITE personal Postgres access for creator@usersen.com"), data = { - username = "user@usersen.com", + username = "creator@usersen.com", accessLevel = "READWRITE", expiresAt = NotNull(), reason = "Testing personal database access", @@ -221,6 +225,38 @@ Test.gql("Personal postgres access is audited as a self-grant", function(t) } end) +Test.gql("Requesting access again returns the live access instead of creating another", function(t) + t.addHeader("x-user-email", creatorUser:email()) + local request = [[ + mutation { createPostgresAccess(input: { + postgres: "foobar", branch: "main", environmentName: "dev", teamSlug: "someteamname", + accessLevel: READWRITE, reason: "Testing personal database access" + }) { name expiresAt } } + ]] + t.query(request) + t.check { data = { createPostgresAccess = { name = NotNull(), expiresAt = NotNull() } } } + t.query(request) + t.check { data = { createPostgresAccess = { name = NotNull(), expiresAt = NotNull() } } } + + -- A reused access is not a new request, so it must not be audited again. + t.query [[{ team(slug: "someteamname") { activityLog(filter: { activityTypes: [POSTGRES_PERSONAL_ACCESS_CREATED] }) { nodes { message } } } }]] + t.check { data = { team = { activityLog = { nodes = { { message = Contains("creator@usersen.com") } } } } } } +end) + +Test.gql("Requesting a different access level while one is live is rejected", function(t) + t.addHeader("x-user-email", creatorUser:email()) + t.query [[ + mutation { createPostgresAccess(input: { + postgres: "foobar", branch: "main", environmentName: "dev", teamSlug: "someteamname", + accessLevel: READ, reason: "Testing personal database access" + }) { name } } + ]] + t.check { + errors = { { locations = NotNull(), path = { "createPostgresAccess" }, message = Contains("already have readwrite access") } }, + data = Null, + } +end) + Test.gql("Personal access targets a physical instance, even when its name differs from logical Postgres", function(t) t.addHeader("x-user-email", user:email()) t.query [[ @@ -340,24 +376,31 @@ Test.gql("PostgresAccess credentials cannot be read through generic Secret eleva end end) -Test.gql("PostgresAccess connection rejects expired, unready, and missing-secret access", function(t) +Test.gql("PostgresAccess connection is null without error until the access is ready", function(t) t.addHeader("x-user-email", user:email()) for _, test in ipairs({ - { name = "expired-access", state = "EXPIRED", message = "has expired" }, - { name = "pending-access", state = "PENDING", message = "is not ready" }, - { name = "failed-access", state = "FAILED", message = "is not ready" }, - { name = "missing-secret-access", state = "READY", message = "secrets for PostgresAccess is not available" }, + { name = "expired-access", state = "EXPIRED" }, + { name = "pending-access", state = "PENDING" }, + { name = "failed-access", state = "FAILED" }, }) do t.query(string.format( [[query { team(slug: "someteamname") { environment(name: "dev") { postgresAccess(name: "%s") { state connection { password } } } } }]], test.name)) t.check { - errors = { { locations = NotNull(), path = { "team", "environment", "postgresAccess", "connection" }, message = Contains(test.message) } }, data = { team = { environment = { postgresAccess = { state = test.state, connection = Null } } } }, } end end) +Test.gql("PostgresAccess connection fails when a ready access has no secrets", function(t) + t.addHeader("x-user-email", user:email()) + t.query [[query { team(slug: "someteamname") { environment(name: "dev") { postgresAccess(name: "missing-secret-access") { state connection { password } } } } }]] + t.check { + errors = { { locations = NotNull(), path = { "team", "environment", "postgresAccess", "connection" }, message = Contains("secrets for PostgresAccess is not available") } }, + data = { team = { environment = { postgresAccess = { state = "READY", connection = Null } } } }, + } +end) + Test.gql("Personal postgres connection retrieval is audited", function(t) t.addHeader("x-user-email", user:email()) t.query [[ @@ -374,3 +417,21 @@ Test.gql("Personal postgres connection retrieval is audited", function(t) } } } }, } end) + +Test.gql("An expired access is cleaned up, after which a new one can be requested", function(t) + t.addHeader("x-user-email", staleUser:email()) + local request = [[ + mutation { createPostgresAccess(input: { + postgres: "foobar", branch: "main", environmentName: "dev", teamSlug: "someteamname", + accessLevel: READ, reason: "Testing personal database access" + }) { name } } + ]] + t.query(request) + t.check { + errors = { { locations = NotNull(), path = { "createPostgresAccess" }, message = Contains("being cleaned up") } }, + data = Null, + } + + t.query(request) + t.check { data = { createPostgresAccess = { name = "postgres-access-9269571e872b7c9d" } } } +end) diff --git a/integration_tests/k8s_resources/create_postgres_access/dev/someteamname/accesses.yaml b/integration_tests/k8s_resources/create_postgres_access/dev/someteamname/accesses.yaml index 394e2bae9..54d692020 100644 --- a/integration_tests/k8s_resources/create_postgres_access/dev/someteamname/accesses.yaml +++ b/integration_tests/k8s_resources/create_postgres_access/dev/someteamname/accesses.yaml @@ -104,3 +104,15 @@ status: conditions: - type: Ready status: "True" +--- +apiVersion: nais.io/v1 +kind: PostgresAccess +metadata: + name: postgres-access-9269571e872b7c9d + namespace: someteamname + uid: 33333333-3333-4333-8333-333333333333 +spec: + postgresBranch: foobar-main-a4f04c0c + username: stale@usersen.com + accessLevel: read + expiresAt: "2000-01-01T00:00:00Z" diff --git a/integration_tests/k8s_resources/postgres_crud/dev/pg-crud-team/postgres.yaml b/integration_tests/k8s_resources/postgres_crud/dev/pg-crud-team/postgres.yaml new file mode 100644 index 000000000..fc445a3e4 --- /dev/null +++ b/integration_tests/k8s_resources/postgres_crud/dev/pg-crud-team/postgres.yaml @@ -0,0 +1,12 @@ +apiVersion: nais.io/v1 +kind: Postgres +metadata: + name: existing + namespace: pg-crud-team +spec: + majorVersion: "17" + highAvailability: false + resources: + cpu: 100m + memory: 512Mi + diskSize: 10Gi diff --git a/integration_tests/postgres_crud.lua b/integration_tests/postgres_crud.lua new file mode 100644 index 000000000..587f73635 --- /dev/null +++ b/integration_tests/postgres_crud.lua @@ -0,0 +1,177 @@ +local member = User.new("postgres-crud-user", "postgres-crud-user@usersen.com") +local outsider = User.new("postgres-crud-outsider", "postgres-crud-outsider@usersen.com") +local team = Team.new("pg-crud-team", "Testing Postgres create and update", "#postgres-crud") +team:addMember(member) +Helper.readK8sResources("k8s_resources/postgres_crud") + +local function create(name, extra) + return string.format( + [[mutation { createPostgres(input: { + name: "%s", environmentName: "dev", teamSlug: "pg-crud-team", %s + }) { postgres { name majorVersion highAvailability resources { cpu memory diskSize } } } }]], + name, + extra + ) +end + +Test.gql("Non-members cannot create Postgres", function(t) + t.addHeader("x-user-email", outsider:email()) + t.query(create("denied", 'majorVersion: "18"')) + t.check { + errors = { { locations = NotNull(), path = { "createPostgres" }, message = Contains("You are authenticated") } }, + data = Null, + } +end) + +Test.gql("Team members can create Postgres with defaults left to the platform", function(t) + t.addHeader("x-user-email", member:email()) + t.query(create("fresh", 'majorVersion: "18"')) + t.check { + data = { + createPostgres = { + postgres = { + name = "fresh", + majorVersion = "18", + highAvailability = false, + resources = { cpu = Null, memory = Null, diskSize = Null }, + }, + }, + }, + } +end) + +Test.gql("Team members can create Postgres with explicit resources", function(t) + t.addHeader("x-user-email", member:email()) + t.query(create("sized", 'majorVersion: "18", highAvailability: true, cpu: "250m", memory: "1Gi", diskSize: "20Gi"')) + t.check { + data = { + createPostgres = { + postgres = { + name = "sized", + majorVersion = "18", + highAvailability = true, + resources = { cpu = "250m", memory = "1Gi", diskSize = "20Gi" }, + }, + }, + }, + } +end) + +Test.gql("Creating an existing Postgres fails", function(t) + t.addHeader("x-user-email", member:email()) + t.query(create("existing", 'majorVersion: "18"')) + t.check { + errors = { { locations = NotNull(), path = { "createPostgres" }, message = NotNull() } }, + data = Null, + } +end) + +Test.gql("Unsupported major version is rejected", function(t) + t.addHeader("x-user-email", member:email()) + t.query(create("badversion", 'majorVersion: "9"')) + t.check { + errors = { { extensions = { field = "majorVersion" }, path = { "createPostgres" }, message = Contains("Major version must be one of") } }, + data = Null, + } +end) + +Test.gql("Invalid resource quantity is rejected", function(t) + t.addHeader("x-user-email", member:email()) + t.query(create("badquantity", 'majorVersion: "18", cpu: "lots"')) + t.check { + errors = { { extensions = { field = "cpu" }, path = { "createPostgres" }, message = Contains("is not a valid quantity") } }, + data = Null, + } +end) + +Test.gql("Team members can update only the fields they provide", function(t) + t.addHeader("x-user-email", member:email()) + t.query [[mutation { updatePostgres(input: { + name: "existing", environmentName: "dev", teamSlug: "pg-crud-team", highAvailability: true, diskSize: "30Gi" + }) { postgres { majorVersion highAvailability resources { cpu memory diskSize } } } }]] + t.check { + data = { + updatePostgres = { + postgres = { + majorVersion = "17", + highAvailability = true, + resources = { cpu = "100m", memory = "512Mi", diskSize = "30Gi" }, + }, + }, + }, + } +end) + +Test.gql("Non-members cannot update Postgres", function(t) + t.addHeader("x-user-email", outsider:email()) + t.query [[mutation { updatePostgres(input: { + name: "existing", environmentName: "dev", teamSlug: "pg-crud-team", highAvailability: true + }) { postgres { name } } }]] + t.check { + errors = { { locations = NotNull(), path = { "updatePostgres" }, message = Contains("You are authenticated") } }, + data = Null, + } +end) + +Test.gql("Updating a missing Postgres fails", function(t) + t.addHeader("x-user-email", member:email()) + t.query [[mutation { updatePostgres(input: { + name: "missing", environmentName: "dev", teamSlug: "pg-crud-team", highAvailability: true + }) { postgres { name } } }]] + t.check { + errors = { { locations = NotNull(), path = { "updatePostgres" }, message = Contains("not found") } }, + data = Null, + } +end) + +Test.gql("Names that Kubernetes would reject are rejected as field errors", function(t) + t.addHeader("x-user-email", member:email()) + for _, name in ipairs({ "Upper", "has/slash", "-leading" }) do + t.query(create(name, 'majorVersion: "18"')) + t.check { + errors = { { extensions = { field = "name" }, path = { "createPostgres" }, message = Contains("lowercase letters") } }, + data = Null, + } + end + t.query [[mutation { updatePostgres(input: { + name: "Upper", environmentName: "dev", teamSlug: "pg-crud-team", highAvailability: true + }) { postgres { name } } }]] + t.check { + errors = { { extensions = { field = "name" }, path = { "updatePostgres" }, message = Contains("lowercase letters") } }, + data = Null, + } +end) + +Test.gql("Zero and negative resource quantities are rejected", function(t) + t.addHeader("x-user-email", member:email()) + for _, field in ipairs({ "cpu", "memory", "diskSize" }) do + t.query(create("badquantity", string.format('majorVersion: "18", %s: "-1"', field))) + t.check { + errors = { { extensions = { field = field }, path = { "createPostgres" }, message = Contains("must be greater than zero") } }, + data = Null, + } + end +end) + +Test.gql("Creating and updating Postgres is recorded in the activity log", function(t) + t.addHeader("x-user-email", member:email()) + t.query [[{ team(slug: "pg-crud-team") { + activityLog(first: 50, filter: { activityTypes: [POSTGRES_CREATED, POSTGRES_UPDATED] }) { + nodes { __typename message actor resourceType resourceName environmentName teamSlug + ... on PostgresUpdatedActivityLogEntry { data { updatedFields { field oldValue newValue } } } + } + } + } }]] + t.check { + data = { team = { activityLog = { nodes = { + { __typename = "PostgresUpdatedActivityLogEntry", message = "Updated Postgres", actor = member:email(), resourceType = "POSTGRES", resourceName = "existing", environmentName = "dev", teamSlug = "pg-crud-team", + data = { updatedFields = { + { field = "highAvailability", oldValue = "false", newValue = "true" }, + { field = "diskSize", oldValue = "10Gi", newValue = "30Gi" }, + } }, + }, + { __typename = "PostgresCreatedActivityLogEntry", message = "Created Postgres", actor = member:email(), resourceType = "POSTGRES", resourceName = Ignore(), environmentName = "dev", teamSlug = "pg-crud-team" }, + { __typename = "PostgresCreatedActivityLogEntry", message = "Created Postgres", actor = member:email(), resourceType = "POSTGRES", resourceName = Ignore(), environmentName = "dev", teamSlug = "pg-crud-team" }, + } } } }, + } +end) diff --git a/internal/auth/authz/queries.go b/internal/auth/authz/queries.go index b62feb0b2..745d4bcca 100644 --- a/internal/auth/authz/queries.go +++ b/internal/auth/authz/queries.go @@ -320,6 +320,14 @@ func CanGrantPostgresAccess(ctx context.Context, teamSlug slug.Slug) error { return requireStrictTeamAuthorization(ctx, teamSlug, "postgres:access:grant") } +func CanCreatePostgres(ctx context.Context, teamSlug slug.Slug) error { + return requireTeamAuthorization(ctx, teamSlug, "postgres:create") +} + +func CanUpdatePostgres(ctx context.Context, teamSlug slug.Slug) error { + return requireTeamAuthorization(ctx, teamSlug, "postgres:update") +} + func CanDeletePostgres(ctx context.Context, teamSlug slug.Slug) error { return requireTeamAuthorization(ctx, teamSlug, "postgres:delete") } diff --git a/internal/database/migrations/0077_add_postgres_create_update_authorizations.sql b/internal/database/migrations/0077_add_postgres_create_update_authorizations.sql new file mode 100644 index 000000000..f8245c806 --- /dev/null +++ b/internal/database/migrations/0077_add_postgres_create_update_authorizations.sql @@ -0,0 +1,33 @@ +-- +goose Up +INSERT INTO + authorizations (name, description) +VALUES + ( + 'postgres:create', + 'Permission to create Postgres instances.' + ), + ( + 'postgres:update', + 'Permission to update Postgres instances.' + ) +; + +INSERT INTO + role_authorizations (role_name, authorization_name) +VALUES + ('Team member', 'postgres:create'), + ('Team member', 'postgres:update'), + ('Team owner', 'postgres:create'), + ('Team owner', 'postgres:update') +; + +-- +goose Down +DELETE FROM role_authorizations +WHERE + authorization_name IN ('postgres:create', 'postgres:update') +; + +DELETE FROM authorizations +WHERE + name IN ('postgres:create', 'postgres:update') +; diff --git a/internal/graph/gengql/activitylog.generated.go b/internal/graph/gengql/activitylog.generated.go index dbdf65c8c..a79520cce 100644 --- a/internal/graph/gengql/activitylog.generated.go +++ b/internal/graph/gengql/activitylog.generated.go @@ -789,6 +789,13 @@ func (ec *executionContext) _ActivityLogEntry(ctx context.Context, sel ast.Selec return graphql.Null } return ec._ReconcilerConfiguredActivityLogEntry(ctx, sel, obj) + case postgres.PostgresUpdatedActivityLogEntry: + return ec._PostgresUpdatedActivityLogEntry(ctx, sel, &obj) + case *postgres.PostgresUpdatedActivityLogEntry: + if obj == nil { + return graphql.Null + } + return ec._PostgresUpdatedActivityLogEntry(ctx, sel, obj) case postgres.PostgresPersonalAccessCreatedActivityLogEntry: return ec._PostgresPersonalAccessCreatedActivityLogEntry(ctx, sel, &obj) case *postgres.PostgresPersonalAccessCreatedActivityLogEntry: @@ -817,6 +824,13 @@ func (ec *executionContext) _ActivityLogEntry(ctx context.Context, sel ast.Selec return graphql.Null } return ec._PostgresDeletedActivityLogEntry(ctx, sel, obj) + case postgres.PostgresCreatedActivityLogEntry: + return ec._PostgresCreatedActivityLogEntry(ctx, sel, &obj) + case *postgres.PostgresCreatedActivityLogEntry: + if obj == nil { + return graphql.Null + } + return ec._PostgresCreatedActivityLogEntry(ctx, sel, obj) case opensearch.OpenSearchUpdatedActivityLogEntry: return ec._OpenSearchUpdatedActivityLogEntry(ctx, sel, &obj) case *opensearch.OpenSearchUpdatedActivityLogEntry: diff --git a/internal/graph/gengql/postgres.generated.go b/internal/graph/gengql/postgres.generated.go index 880e62c65..a43724831 100644 --- a/internal/graph/gengql/postgres.generated.go +++ b/internal/graph/gengql/postgres.generated.go @@ -203,6 +203,38 @@ func (ec *executionContext) fieldContext_CreatePostgresAccessPayload_expiresAt(_ return graphql.NewScalarFieldContext("CreatePostgresAccessPayload", field, false, false, errors.New("field of type Time does not have child fields")) } +func (ec *executionContext) _CreatePostgresPayload_postgres(ctx context.Context, field graphql.CollectedField, obj *postgres.CreatePostgresPayload) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_CreatePostgresPayload_postgres(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Postgres, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *postgres.Postgres) graphql.Marshaler { + return ec.marshalNPostgres2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋpostgresᚐPostgres(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_CreatePostgresPayload_postgres(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "CreatePostgresPayload", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_Postgres(ctx, field) + }, + } + return fc, nil +} + func (ec *executionContext) _DeletePostgresBranchPayload_postgresBranchDeleted(ctx context.Context, field graphql.CollectedField, obj *postgres.DeletePostgresBranchPayload) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -1518,6 +1550,222 @@ func (ec *executionContext) fieldContext_PostgresBranchStateFacetItem_count(_ co return graphql.NewScalarFieldContext("PostgresBranchStateFacetItem", field, false, false, errors.New("field of type Int does not have child fields")) } +func (ec *executionContext) _PostgresCreatedActivityLogEntry_id(ctx context.Context, field graphql.CollectedField, obj *postgres.PostgresCreatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_PostgresCreatedActivityLogEntry_id(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.ID(), nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v ident.Ident) graphql.Marshaler { + return ec.marshalNID2githubᚗcomᚋnaisᚋapiᚋinternalᚋgraphᚋidentᚐIdent(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_PostgresCreatedActivityLogEntry_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("PostgresCreatedActivityLogEntry", field, true, false, errors.New("field of type ID does not have child fields")) +} + +func (ec *executionContext) _PostgresCreatedActivityLogEntry_actor(ctx context.Context, field graphql.CollectedField, obj *postgres.PostgresCreatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_PostgresCreatedActivityLogEntry_actor(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Actor, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler { + return ec.marshalNString2string(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_PostgresCreatedActivityLogEntry_actor(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("PostgresCreatedActivityLogEntry", field, false, false, errors.New("field of type String does not have child fields")) +} + +func (ec *executionContext) _PostgresCreatedActivityLogEntry_gitHubActorClaims(ctx context.Context, field graphql.CollectedField, obj *postgres.PostgresCreatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_PostgresCreatedActivityLogEntry_gitHubActorClaims(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.GitHubActorClaims, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *github.GitHubActorClaims) graphql.Marshaler { + return ec.marshalOGitHubActorClaims2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋauthᚋmiddlewareᚋgithubᚐGitHubActorClaims(ctx, selections, v) + }, + true, + false, + ) +} +func (ec *executionContext) fieldContext_PostgresCreatedActivityLogEntry_gitHubActorClaims(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PostgresCreatedActivityLogEntry", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_GitHubActorClaims(ctx, field) + }, + } + return fc, nil +} + +func (ec *executionContext) _PostgresCreatedActivityLogEntry_createdAt(ctx context.Context, field graphql.CollectedField, obj *postgres.PostgresCreatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_PostgresCreatedActivityLogEntry_createdAt(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.CreatedAt, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v time.Time) graphql.Marshaler { + return ec.marshalNTime2timeᚐTime(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_PostgresCreatedActivityLogEntry_createdAt(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("PostgresCreatedActivityLogEntry", field, false, false, errors.New("field of type Time does not have child fields")) +} + +func (ec *executionContext) _PostgresCreatedActivityLogEntry_message(ctx context.Context, field graphql.CollectedField, obj *postgres.PostgresCreatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_PostgresCreatedActivityLogEntry_message(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Message, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler { + return ec.marshalNString2string(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_PostgresCreatedActivityLogEntry_message(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("PostgresCreatedActivityLogEntry", field, false, false, errors.New("field of type String does not have child fields")) +} + +func (ec *executionContext) _PostgresCreatedActivityLogEntry_resourceType(ctx context.Context, field graphql.CollectedField, obj *postgres.PostgresCreatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_PostgresCreatedActivityLogEntry_resourceType(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.ResourceType, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v activitylog.ActivityLogEntryResourceType) graphql.Marshaler { + return ec.marshalNActivityLogEntryResourceType2githubᚗcomᚋnaisᚋapiᚋinternalᚋactivitylogᚐActivityLogEntryResourceType(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_PostgresCreatedActivityLogEntry_resourceType(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("PostgresCreatedActivityLogEntry", field, false, false, errors.New("field of type ActivityLogEntryResourceType does not have child fields")) +} + +func (ec *executionContext) _PostgresCreatedActivityLogEntry_resourceName(ctx context.Context, field graphql.CollectedField, obj *postgres.PostgresCreatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_PostgresCreatedActivityLogEntry_resourceName(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.ResourceName, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler { + return ec.marshalNString2string(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_PostgresCreatedActivityLogEntry_resourceName(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("PostgresCreatedActivityLogEntry", field, false, false, errors.New("field of type String does not have child fields")) +} + +func (ec *executionContext) _PostgresCreatedActivityLogEntry_teamSlug(ctx context.Context, field graphql.CollectedField, obj *postgres.PostgresCreatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_PostgresCreatedActivityLogEntry_teamSlug(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.TeamSlug, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *slug.Slug) graphql.Marshaler { + return ec.marshalNSlug2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋslugᚐSlug(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_PostgresCreatedActivityLogEntry_teamSlug(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("PostgresCreatedActivityLogEntry", field, false, false, errors.New("field of type Slug does not have child fields")) +} + +func (ec *executionContext) _PostgresCreatedActivityLogEntry_environmentName(ctx context.Context, field graphql.CollectedField, obj *postgres.PostgresCreatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_PostgresCreatedActivityLogEntry_environmentName(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.EnvironmentName, 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_PostgresCreatedActivityLogEntry_environmentName(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("PostgresCreatedActivityLogEntry", field, false, false, errors.New("field of type String does not have child fields")) +} + func (ec *executionContext) _PostgresDeletedActivityLogEntry_id(ctx context.Context, field graphql.CollectedField, obj *postgres.PostgresDeletedActivityLogEntry) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -2653,51 +2901,432 @@ func (ec *executionContext) fieldContext_PostgresResources_diskSize(_ context.Co return graphql.NewScalarFieldContext("PostgresResources", field, false, false, errors.New("field of type String does not have child fields")) } -func (ec *executionContext) _TeamInventoryCountPostgresBranches_total(ctx context.Context, field graphql.CollectedField, obj *postgres.TeamInventoryCountPostgresBranches) (ret graphql.Marshaler) { +func (ec *executionContext) _PostgresUpdatedActivityLogEntry_id(ctx context.Context, field graphql.CollectedField, obj *postgres.PostgresUpdatedActivityLogEntry) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_TeamInventoryCountPostgresBranches_total(ctx, field) + return ec.fieldContext_PostgresUpdatedActivityLogEntry_id(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.Total, nil + return obj.ID(), nil }, nil, - func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { - return ec.marshalNInt2int(ctx, selections, v) + func(ctx context.Context, selections ast.SelectionSet, v ident.Ident) graphql.Marshaler { + return ec.marshalNID2githubᚗcomᚋnaisᚋapiᚋinternalᚋgraphᚋidentᚐIdent(ctx, selections, v) }, true, true, ) } -func (ec *executionContext) fieldContext_TeamInventoryCountPostgresBranches_total(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("TeamInventoryCountPostgresBranches", field, false, false, errors.New("field of type Int does not have child fields")) +func (ec *executionContext) fieldContext_PostgresUpdatedActivityLogEntry_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("PostgresUpdatedActivityLogEntry", field, true, false, errors.New("field of type ID does not have child fields")) } -// endregion **************************** field.gotpl ***************************** - -// region **************************** input.gotpl ***************************** - -func (ec *executionContext) unmarshalInputCreatePostgresAccessInput(ctx context.Context, obj any) (postgres.CreatePostgresAccessInput, error) { - var it postgres.CreatePostgresAccessInput - if obj == nil { - return it, nil - } - - asMap := map[string]any{} - for k, v := range obj.(map[string]any) { - asMap[k] = v - } +func (ec *executionContext) _PostgresUpdatedActivityLogEntry_actor(ctx context.Context, field graphql.CollectedField, obj *postgres.PostgresUpdatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_PostgresUpdatedActivityLogEntry_actor(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Actor, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler { + return ec.marshalNString2string(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_PostgresUpdatedActivityLogEntry_actor(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("PostgresUpdatedActivityLogEntry", field, false, false, errors.New("field of type String does not have child fields")) +} - fieldsInOrder := [...]string{"postgres", "branch", "teamSlug", "environmentName", "accessLevel", "reason", "ttl"} - for _, k := range fieldsInOrder { - v, ok := asMap[k] - if !ok { - continue - } - switch k { +func (ec *executionContext) _PostgresUpdatedActivityLogEntry_gitHubActorClaims(ctx context.Context, field graphql.CollectedField, obj *postgres.PostgresUpdatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_PostgresUpdatedActivityLogEntry_gitHubActorClaims(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.GitHubActorClaims, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *github.GitHubActorClaims) graphql.Marshaler { + return ec.marshalOGitHubActorClaims2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋauthᚋmiddlewareᚋgithubᚐGitHubActorClaims(ctx, selections, v) + }, + true, + false, + ) +} +func (ec *executionContext) fieldContext_PostgresUpdatedActivityLogEntry_gitHubActorClaims(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PostgresUpdatedActivityLogEntry", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_GitHubActorClaims(ctx, field) + }, + } + return fc, nil +} + +func (ec *executionContext) _PostgresUpdatedActivityLogEntry_createdAt(ctx context.Context, field graphql.CollectedField, obj *postgres.PostgresUpdatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_PostgresUpdatedActivityLogEntry_createdAt(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.CreatedAt, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v time.Time) graphql.Marshaler { + return ec.marshalNTime2timeᚐTime(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_PostgresUpdatedActivityLogEntry_createdAt(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("PostgresUpdatedActivityLogEntry", field, false, false, errors.New("field of type Time does not have child fields")) +} + +func (ec *executionContext) _PostgresUpdatedActivityLogEntry_message(ctx context.Context, field graphql.CollectedField, obj *postgres.PostgresUpdatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_PostgresUpdatedActivityLogEntry_message(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Message, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler { + return ec.marshalNString2string(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_PostgresUpdatedActivityLogEntry_message(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("PostgresUpdatedActivityLogEntry", field, false, false, errors.New("field of type String does not have child fields")) +} + +func (ec *executionContext) _PostgresUpdatedActivityLogEntry_resourceType(ctx context.Context, field graphql.CollectedField, obj *postgres.PostgresUpdatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_PostgresUpdatedActivityLogEntry_resourceType(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.ResourceType, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v activitylog.ActivityLogEntryResourceType) graphql.Marshaler { + return ec.marshalNActivityLogEntryResourceType2githubᚗcomᚋnaisᚋapiᚋinternalᚋactivitylogᚐActivityLogEntryResourceType(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_PostgresUpdatedActivityLogEntry_resourceType(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("PostgresUpdatedActivityLogEntry", field, false, false, errors.New("field of type ActivityLogEntryResourceType does not have child fields")) +} + +func (ec *executionContext) _PostgresUpdatedActivityLogEntry_resourceName(ctx context.Context, field graphql.CollectedField, obj *postgres.PostgresUpdatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_PostgresUpdatedActivityLogEntry_resourceName(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.ResourceName, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler { + return ec.marshalNString2string(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_PostgresUpdatedActivityLogEntry_resourceName(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("PostgresUpdatedActivityLogEntry", field, false, false, errors.New("field of type String does not have child fields")) +} + +func (ec *executionContext) _PostgresUpdatedActivityLogEntry_teamSlug(ctx context.Context, field graphql.CollectedField, obj *postgres.PostgresUpdatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_PostgresUpdatedActivityLogEntry_teamSlug(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.TeamSlug, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *slug.Slug) graphql.Marshaler { + return ec.marshalNSlug2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋslugᚐSlug(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_PostgresUpdatedActivityLogEntry_teamSlug(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("PostgresUpdatedActivityLogEntry", field, false, false, errors.New("field of type Slug does not have child fields")) +} + +func (ec *executionContext) _PostgresUpdatedActivityLogEntry_environmentName(ctx context.Context, field graphql.CollectedField, obj *postgres.PostgresUpdatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_PostgresUpdatedActivityLogEntry_environmentName(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.EnvironmentName, 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_PostgresUpdatedActivityLogEntry_environmentName(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("PostgresUpdatedActivityLogEntry", field, false, false, errors.New("field of type String does not have child fields")) +} + +func (ec *executionContext) _PostgresUpdatedActivityLogEntry_data(ctx context.Context, field graphql.CollectedField, obj *postgres.PostgresUpdatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_PostgresUpdatedActivityLogEntry_data(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Data, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *postgres.PostgresUpdatedActivityLogEntryData) graphql.Marshaler { + return ec.marshalNPostgresUpdatedActivityLogEntryData2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋpostgresᚐPostgresUpdatedActivityLogEntryData(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_PostgresUpdatedActivityLogEntry_data(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PostgresUpdatedActivityLogEntry", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_PostgresUpdatedActivityLogEntryData(ctx, field) + }, + } + return fc, nil +} + +func (ec *executionContext) _PostgresUpdatedActivityLogEntryData_updatedFields(ctx context.Context, field graphql.CollectedField, obj *postgres.PostgresUpdatedActivityLogEntryData) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_PostgresUpdatedActivityLogEntryData_updatedFields(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.UpdatedFields, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v []*postgres.PostgresUpdatedActivityLogEntryDataUpdatedField) graphql.Marshaler { + return ec.marshalNPostgresUpdatedActivityLogEntryDataUpdatedField2ᚕᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋpostgresᚐPostgresUpdatedActivityLogEntryDataUpdatedFieldᚄ(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_PostgresUpdatedActivityLogEntryData_updatedFields(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PostgresUpdatedActivityLogEntryData", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_PostgresUpdatedActivityLogEntryDataUpdatedField(ctx, field) + }, + } + return fc, nil +} + +func (ec *executionContext) _PostgresUpdatedActivityLogEntryDataUpdatedField_field(ctx context.Context, field graphql.CollectedField, obj *postgres.PostgresUpdatedActivityLogEntryDataUpdatedField) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_PostgresUpdatedActivityLogEntryDataUpdatedField_field(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Field, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler { + return ec.marshalNString2string(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_PostgresUpdatedActivityLogEntryDataUpdatedField_field(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("PostgresUpdatedActivityLogEntryDataUpdatedField", field, false, false, errors.New("field of type String does not have child fields")) +} + +func (ec *executionContext) _PostgresUpdatedActivityLogEntryDataUpdatedField_oldValue(ctx context.Context, field graphql.CollectedField, obj *postgres.PostgresUpdatedActivityLogEntryDataUpdatedField) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_PostgresUpdatedActivityLogEntryDataUpdatedField_oldValue(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.OldValue, 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_PostgresUpdatedActivityLogEntryDataUpdatedField_oldValue(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("PostgresUpdatedActivityLogEntryDataUpdatedField", field, false, false, errors.New("field of type String does not have child fields")) +} + +func (ec *executionContext) _PostgresUpdatedActivityLogEntryDataUpdatedField_newValue(ctx context.Context, field graphql.CollectedField, obj *postgres.PostgresUpdatedActivityLogEntryDataUpdatedField) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_PostgresUpdatedActivityLogEntryDataUpdatedField_newValue(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.NewValue, 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_PostgresUpdatedActivityLogEntryDataUpdatedField_newValue(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("PostgresUpdatedActivityLogEntryDataUpdatedField", field, false, false, errors.New("field of type String does not have child fields")) +} + +func (ec *executionContext) _TeamInventoryCountPostgresBranches_total(ctx context.Context, field graphql.CollectedField, obj *postgres.TeamInventoryCountPostgresBranches) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_TeamInventoryCountPostgresBranches_total(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Total, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { + return ec.marshalNInt2int(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_TeamInventoryCountPostgresBranches_total(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("TeamInventoryCountPostgresBranches", field, false, false, errors.New("field of type Int does not have child fields")) +} + +func (ec *executionContext) _UpdatePostgresPayload_postgres(ctx context.Context, field graphql.CollectedField, obj *postgres.UpdatePostgresPayload) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_UpdatePostgresPayload_postgres(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Postgres, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *postgres.Postgres) graphql.Marshaler { + return ec.marshalNPostgres2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋpostgresᚐPostgres(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_UpdatePostgresPayload_postgres(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "UpdatePostgresPayload", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_Postgres(ctx, field) + }, + } + return fc, nil +} + +// endregion **************************** field.gotpl ***************************** + +// region **************************** input.gotpl ***************************** + +func (ec *executionContext) unmarshalInputCreatePostgresAccessInput(ctx context.Context, obj any) (postgres.CreatePostgresAccessInput, error) { + var it postgres.CreatePostgresAccessInput + if obj == nil { + return it, nil + } + + asMap := map[string]any{} + for k, v := range obj.(map[string]any) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"postgres", "branch", "teamSlug", "environmentName", "accessLevel", "reason", "ttl"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { case "postgres": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("postgres")) data, err := ec.unmarshalNString2string(ctx, v) @@ -2752,6 +3381,85 @@ func (ec *executionContext) unmarshalInputCreatePostgresAccessInput(ctx context. return it, nil } +func (ec *executionContext) unmarshalInputCreatePostgresInput(ctx context.Context, obj any) (postgres.CreatePostgresInput, error) { + var it postgres.CreatePostgresInput + if obj == nil { + return it, nil + } + + asMap := map[string]any{} + for k, v := range obj.(map[string]any) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "majorVersion", "highAvailability", "cpu", "memory", "diskSize"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "name": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("name")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.Name = data + case "environmentName": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("environmentName")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.EnvironmentName = data + case "teamSlug": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("teamSlug")) + data, err := ec.unmarshalNSlug2githubᚗcomᚋnaisᚋapiᚋinternalᚋslugᚐSlug(ctx, v) + if err != nil { + return it, err + } + it.TeamSlug = data + case "majorVersion": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("majorVersion")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.MajorVersion = data + case "highAvailability": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("highAvailability")) + data, err := ec.unmarshalOBoolean2ᚖbool(ctx, v) + if err != nil { + return it, err + } + it.HighAvailability = data + case "cpu": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("cpu")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.CPU = data + case "memory": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("memory")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.Memory = data + case "diskSize": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("diskSize")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.DiskSize = data + } + } + return it, nil +} + func (ec *executionContext) unmarshalInputDeletePostgresBranchInput(ctx context.Context, obj any) (postgres.DeletePostgresBranchInput, error) { var it postgres.DeletePostgresBranchInput if obj == nil { @@ -2891,6 +3599,78 @@ func (ec *executionContext) unmarshalInputPostgresBranchOrder(ctx context.Contex return it, nil } +func (ec *executionContext) unmarshalInputUpdatePostgresInput(ctx context.Context, obj any) (postgres.UpdatePostgresInput, error) { + var it postgres.UpdatePostgresInput + if obj == nil { + return it, nil + } + + asMap := map[string]any{} + for k, v := range obj.(map[string]any) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"name", "environmentName", "teamSlug", "highAvailability", "cpu", "memory", "diskSize"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "name": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("name")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.Name = data + case "environmentName": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("environmentName")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.EnvironmentName = data + case "teamSlug": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("teamSlug")) + data, err := ec.unmarshalNSlug2githubᚗcomᚋnaisᚋapiᚋinternalᚋslugᚐSlug(ctx, v) + if err != nil { + return it, err + } + it.TeamSlug = data + case "highAvailability": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("highAvailability")) + data, err := ec.unmarshalOBoolean2ᚖbool(ctx, v) + if err != nil { + return it, err + } + it.HighAvailability = data + case "cpu": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("cpu")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.CPU = data + case "memory": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("memory")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.Memory = data + case "diskSize": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("diskSize")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.DiskSize = data + } + } + return it, nil +} + // endregion **************************** input.gotpl ***************************** // region ************************** interface.gotpl *************************** @@ -2943,6 +3723,45 @@ func (ec *executionContext) _CreatePostgresAccessPayload(ctx context.Context, se return out } +var createPostgresPayloadImplementors = []string{"CreatePostgresPayload"} + +func (ec *executionContext) _CreatePostgresPayload(ctx context.Context, sel ast.SelectionSet, obj *postgres.CreatePostgresPayload) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, createPostgresPayloadImplementors) + + 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("CreatePostgresPayload") + case "postgres": + out.Values[i] = ec._CreatePostgresPayload_postgres(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 deletePostgresBranchPayloadImplementors = []string{"DeletePostgresBranchPayload"} func (ec *executionContext) _DeletePostgresBranchPayload(ctx context.Context, sel ast.SelectionSet, obj *postgres.DeletePostgresBranchPayload) graphql.Marshaler { @@ -3862,8 +4681,52 @@ func (ec *executionContext) _PostgresBranchFacets(ctx context.Context, sel ast.S out.Values[i] = graphql.Null continue } - - out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + 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 postgresBranchStateFacetItemImplementors = []string{"PostgresBranchStateFacetItem"} + +func (ec *executionContext) _PostgresBranchStateFacetItem(ctx context.Context, sel ast.SelectionSet, obj *postgres.PostgresBranchStateFacetItem) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, postgresBranchStateFacetItemImplementors) + + 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("PostgresBranchStateFacetItem") + case "state": + out.Values[i] = ec._PostgresBranchStateFacetItem_state(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "count": + out.Values[i] = ec._PostgresBranchStateFacetItem_count(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -3887,27 +4750,56 @@ func (ec *executionContext) _PostgresBranchFacets(ctx context.Context, sel ast.S return out } -var postgresBranchStateFacetItemImplementors = []string{"PostgresBranchStateFacetItem"} +var postgresCreatedActivityLogEntryImplementors = []string{"PostgresCreatedActivityLogEntry", "ActivityLogEntry", "Node"} -func (ec *executionContext) _PostgresBranchStateFacetItem(ctx context.Context, sel ast.SelectionSet, obj *postgres.PostgresBranchStateFacetItem) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, postgresBranchStateFacetItemImplementors) +func (ec *executionContext) _PostgresCreatedActivityLogEntry(ctx context.Context, sel ast.SelectionSet, obj *postgres.PostgresCreatedActivityLogEntry) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, postgresCreatedActivityLogEntryImplementors) 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("PostgresBranchStateFacetItem") - case "state": - out.Values[i] = ec._PostgresBranchStateFacetItem_state(ctx, field, obj) + out.Values[i] = graphql.MarshalString("PostgresCreatedActivityLogEntry") + case "id": + out.Values[i] = ec._PostgresCreatedActivityLogEntry_id(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } - case "count": - out.Values[i] = ec._PostgresBranchStateFacetItem_count(ctx, field, obj) + case "actor": + out.Values[i] = ec._PostgresCreatedActivityLogEntry_actor(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "gitHubActorClaims": + out.Values[i] = ec._PostgresCreatedActivityLogEntry_gitHubActorClaims(ctx, field, obj) + case "createdAt": + out.Values[i] = ec._PostgresCreatedActivityLogEntry_createdAt(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "message": + out.Values[i] = ec._PostgresCreatedActivityLogEntry_message(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "resourceType": + out.Values[i] = ec._PostgresCreatedActivityLogEntry_resourceType(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "resourceName": + out.Values[i] = ec._PostgresCreatedActivityLogEntry_resourceName(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "teamSlug": + out.Values[i] = ec._PostgresCreatedActivityLogEntry_teamSlug(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } + case "environmentName": + out.Values[i] = ec._PostgresCreatedActivityLogEntry_environmentName(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -4368,6 +5260,166 @@ func (ec *executionContext) _PostgresResources(ctx context.Context, sel ast.Sele return out } +var postgresUpdatedActivityLogEntryImplementors = []string{"PostgresUpdatedActivityLogEntry", "ActivityLogEntry", "Node"} + +func (ec *executionContext) _PostgresUpdatedActivityLogEntry(ctx context.Context, sel ast.SelectionSet, obj *postgres.PostgresUpdatedActivityLogEntry) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, postgresUpdatedActivityLogEntryImplementors) + + 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("PostgresUpdatedActivityLogEntry") + case "id": + out.Values[i] = ec._PostgresUpdatedActivityLogEntry_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "actor": + out.Values[i] = ec._PostgresUpdatedActivityLogEntry_actor(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "gitHubActorClaims": + out.Values[i] = ec._PostgresUpdatedActivityLogEntry_gitHubActorClaims(ctx, field, obj) + case "createdAt": + out.Values[i] = ec._PostgresUpdatedActivityLogEntry_createdAt(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "message": + out.Values[i] = ec._PostgresUpdatedActivityLogEntry_message(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "resourceType": + out.Values[i] = ec._PostgresUpdatedActivityLogEntry_resourceType(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "resourceName": + out.Values[i] = ec._PostgresUpdatedActivityLogEntry_resourceName(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "teamSlug": + out.Values[i] = ec._PostgresUpdatedActivityLogEntry_teamSlug(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "environmentName": + out.Values[i] = ec._PostgresUpdatedActivityLogEntry_environmentName(ctx, field, obj) + case "data": + out.Values[i] = ec._PostgresUpdatedActivityLogEntry_data(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 postgresUpdatedActivityLogEntryDataImplementors = []string{"PostgresUpdatedActivityLogEntryData"} + +func (ec *executionContext) _PostgresUpdatedActivityLogEntryData(ctx context.Context, sel ast.SelectionSet, obj *postgres.PostgresUpdatedActivityLogEntryData) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, postgresUpdatedActivityLogEntryDataImplementors) + + 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("PostgresUpdatedActivityLogEntryData") + case "updatedFields": + out.Values[i] = ec._PostgresUpdatedActivityLogEntryData_updatedFields(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 postgresUpdatedActivityLogEntryDataUpdatedFieldImplementors = []string{"PostgresUpdatedActivityLogEntryDataUpdatedField"} + +func (ec *executionContext) _PostgresUpdatedActivityLogEntryDataUpdatedField(ctx context.Context, sel ast.SelectionSet, obj *postgres.PostgresUpdatedActivityLogEntryDataUpdatedField) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, postgresUpdatedActivityLogEntryDataUpdatedFieldImplementors) + + 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("PostgresUpdatedActivityLogEntryDataUpdatedField") + case "field": + out.Values[i] = ec._PostgresUpdatedActivityLogEntryDataUpdatedField_field(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "oldValue": + out.Values[i] = ec._PostgresUpdatedActivityLogEntryDataUpdatedField_oldValue(ctx, field, obj) + case "newValue": + out.Values[i] = ec._PostgresUpdatedActivityLogEntryDataUpdatedField_newValue(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 teamInventoryCountPostgresBranchesImplementors = []string{"TeamInventoryCountPostgresBranches"} func (ec *executionContext) _TeamInventoryCountPostgresBranches(ctx context.Context, sel ast.SelectionSet, obj *postgres.TeamInventoryCountPostgresBranches) graphql.Marshaler { @@ -4407,6 +5459,45 @@ func (ec *executionContext) _TeamInventoryCountPostgresBranches(ctx context.Cont return out } +var updatePostgresPayloadImplementors = []string{"UpdatePostgresPayload"} + +func (ec *executionContext) _UpdatePostgresPayload(ctx context.Context, sel ast.SelectionSet, obj *postgres.UpdatePostgresPayload) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, updatePostgresPayloadImplementors) + + 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("UpdatePostgresPayload") + case "postgres": + out.Values[i] = ec._UpdatePostgresPayload_postgres(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 +} + // endregion **************************** object.gotpl **************************** // region ***************************** type.gotpl ***************************** @@ -4430,6 +5521,25 @@ func (ec *executionContext) marshalNCreatePostgresAccessPayload2ᚖgithubᚗcom return ec._CreatePostgresAccessPayload(ctx, sel, v) } +func (ec *executionContext) unmarshalNCreatePostgresInput2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋpostgresᚐCreatePostgresInput(ctx context.Context, v any) (postgres.CreatePostgresInput, error) { + res, err := ec.unmarshalInputCreatePostgresInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNCreatePostgresPayload2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋpostgresᚐCreatePostgresPayload(ctx context.Context, sel ast.SelectionSet, v postgres.CreatePostgresPayload) graphql.Marshaler { + return ec._CreatePostgresPayload(ctx, sel, &v) +} + +func (ec *executionContext) marshalNCreatePostgresPayload2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋpostgresᚐCreatePostgresPayload(ctx context.Context, sel ast.SelectionSet, v *postgres.CreatePostgresPayload) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._CreatePostgresPayload(ctx, sel, v) +} + func (ec *executionContext) unmarshalNDeletePostgresBranchInput2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋpostgresᚐDeletePostgresBranchInput(ctx context.Context, v any) (postgres.DeletePostgresBranchInput, error) { res, err := ec.unmarshalInputDeletePostgresBranchInput(ctx, v) return res, graphql.ErrorOnPath(ctx, err) @@ -4625,6 +5735,42 @@ func (ec *executionContext) marshalNPostgresResources2githubᚗcomᚋnaisᚋapi return ec._PostgresResources(ctx, sel, &v) } +func (ec *executionContext) marshalNPostgresUpdatedActivityLogEntryData2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋpostgresᚐPostgresUpdatedActivityLogEntryData(ctx context.Context, sel ast.SelectionSet, v *postgres.PostgresUpdatedActivityLogEntryData) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._PostgresUpdatedActivityLogEntryData(ctx, sel, v) +} + +func (ec *executionContext) marshalNPostgresUpdatedActivityLogEntryDataUpdatedField2ᚕᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋpostgresᚐPostgresUpdatedActivityLogEntryDataUpdatedFieldᚄ(ctx context.Context, sel ast.SelectionSet, v []*postgres.PostgresUpdatedActivityLogEntryDataUpdatedField) graphql.Marshaler { + ret := graphql.MarshalSliceConcurrently(ctx, len(v), 0, false, func(ctx context.Context, i int) graphql.Marshaler { + fc := graphql.GetFieldContext(ctx) + fc.Result = &v[i] + return ec.marshalNPostgresUpdatedActivityLogEntryDataUpdatedField2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋpostgresᚐPostgresUpdatedActivityLogEntryDataUpdatedField(ctx, sel, v[i]) + }) + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalNPostgresUpdatedActivityLogEntryDataUpdatedField2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋpostgresᚐPostgresUpdatedActivityLogEntryDataUpdatedField(ctx context.Context, sel ast.SelectionSet, v *postgres.PostgresUpdatedActivityLogEntryDataUpdatedField) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._PostgresUpdatedActivityLogEntryDataUpdatedField(ctx, sel, v) +} + func (ec *executionContext) marshalNTeamInventoryCountPostgresBranches2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋpostgresᚐTeamInventoryCountPostgresBranches(ctx context.Context, sel ast.SelectionSet, v postgres.TeamInventoryCountPostgresBranches) graphql.Marshaler { return ec._TeamInventoryCountPostgresBranches(ctx, sel, &v) } @@ -4639,6 +5785,25 @@ func (ec *executionContext) marshalNTeamInventoryCountPostgresBranches2ᚖgithub return ec._TeamInventoryCountPostgresBranches(ctx, sel, v) } +func (ec *executionContext) unmarshalNUpdatePostgresInput2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋpostgresᚐUpdatePostgresInput(ctx context.Context, v any) (postgres.UpdatePostgresInput, error) { + res, err := ec.unmarshalInputUpdatePostgresInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNUpdatePostgresPayload2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋpostgresᚐUpdatePostgresPayload(ctx context.Context, sel ast.SelectionSet, v postgres.UpdatePostgresPayload) graphql.Marshaler { + return ec._UpdatePostgresPayload(ctx, sel, &v) +} + +func (ec *executionContext) marshalNUpdatePostgresPayload2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋpostgresᚐUpdatePostgresPayload(ctx context.Context, sel ast.SelectionSet, v *postgres.UpdatePostgresPayload) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._UpdatePostgresPayload(ctx, sel, v) +} + func (ec *executionContext) marshalOPostgresAccessConnectionDetails2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋpostgresᚐPostgresAccessConnectionDetails(ctx context.Context, sel ast.SelectionSet, v *postgres.PostgresAccessConnectionDetails) graphql.Marshaler { if v == nil { return graphql.Null diff --git a/internal/graph/gengql/root_.generated.go b/internal/graph/gengql/root_.generated.go index 950ae0e89..2c89a7c48 100644 --- a/internal/graph/gengql/root_.generated.go +++ b/internal/graph/gengql/root_.generated.go @@ -737,6 +737,10 @@ type ComplexityRoot struct { Name func(childComplexity int) int } + CreatePostgresPayload struct { + Postgres func(childComplexity int) int + } + CreateSecretPayload struct { Secret func(childComplexity int) int } @@ -1622,6 +1626,7 @@ type ComplexityRoot struct { CreateKafkaCredentials func(childComplexity int, input kafkatopic.CreateKafkaCredentialsInput) int CreateOpenSearch func(childComplexity int, input opensearch.CreateOpenSearchInput) int CreateOpenSearchCredentials func(childComplexity int, input opensearch.CreateOpenSearchCredentialsInput) int + CreatePostgres func(childComplexity int, input postgres.CreatePostgresInput) int CreatePostgresAccess func(childComplexity int, input postgres.CreatePostgresAccessInput) int CreateSecret func(childComplexity int, input secret.CreateSecretInput) int CreateServiceAccount func(childComplexity int, input serviceaccount.CreateServiceAccountInput) int @@ -1665,6 +1670,7 @@ type ComplexityRoot struct { UpdateJob func(childComplexity int, input job.UpdateJobInput) int UpdateKafkaTopic func(childComplexity int, input kafkatopic.UpdateKafkaTopicInput) int UpdateOpenSearch func(childComplexity int, input opensearch.UpdateOpenSearchInput) int + UpdatePostgres func(childComplexity int, input postgres.UpdatePostgresInput) int UpdateSecret func(childComplexity int, input secret.UpdateSecretInput) int UpdateSecretValue func(childComplexity int, input secret.UpdateSecretValueInput) int UpdateServiceAccount func(childComplexity int, input serviceaccount.UpdateServiceAccountInput) int @@ -1959,6 +1965,18 @@ type ComplexityRoot struct { State func(childComplexity int) int } + PostgresCreatedActivityLogEntry struct { + Actor func(childComplexity int) int + CreatedAt func(childComplexity int) int + EnvironmentName func(childComplexity int) int + GitHubActorClaims func(childComplexity int) int + ID func(childComplexity int) int + Message func(childComplexity int) int + ResourceName func(childComplexity int) int + ResourceType func(childComplexity int) int + TeamSlug func(childComplexity int) int + } + PostgresDeletedActivityLogEntry struct { Actor func(childComplexity int) int CreatedAt func(childComplexity int) int @@ -2027,6 +2045,29 @@ type ComplexityRoot struct { Memory func(childComplexity int) int } + PostgresUpdatedActivityLogEntry struct { + Actor func(childComplexity int) int + CreatedAt func(childComplexity int) int + Data func(childComplexity int) int + EnvironmentName func(childComplexity int) int + GitHubActorClaims func(childComplexity int) int + ID func(childComplexity int) int + Message func(childComplexity int) int + ResourceName func(childComplexity int) int + ResourceType func(childComplexity int) int + TeamSlug func(childComplexity int) int + } + + PostgresUpdatedActivityLogEntryData struct { + UpdatedFields func(childComplexity int) int + } + + PostgresUpdatedActivityLogEntryDataUpdatedField struct { + Field func(childComplexity int) int + NewValue func(childComplexity int) int + OldValue func(childComplexity int) int + } + Price struct { Value func(childComplexity int) int } @@ -3514,6 +3555,10 @@ type ComplexityRoot struct { OpenSearch func(childComplexity int) int } + UpdatePostgresPayload struct { + Postgres func(childComplexity int) int + } + UpdateSecretPayload struct { Secret func(childComplexity int) int } @@ -6382,6 +6427,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.CreatePostgresAccessPayload.Name(childComplexity), true + case "CreatePostgresPayload.postgres": + if e.ComplexityRoot.CreatePostgresPayload.Postgres == nil { + break + } + + return e.ComplexityRoot.CreatePostgresPayload.Postgres(childComplexity), true + case "CreateSecretPayload.secret": if e.ComplexityRoot.CreateSecretPayload.Secret == nil { break @@ -10073,6 +10125,18 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.Mutation.CreateOpenSearchCredentials(childComplexity, args["input"].(opensearch.CreateOpenSearchCredentialsInput)), true + case "Mutation.createPostgres": + if e.ComplexityRoot.Mutation.CreatePostgres == nil { + break + } + + args, err := ec.field_Mutation_createPostgres_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.ComplexityRoot.Mutation.CreatePostgres(childComplexity, args["input"].(postgres.CreatePostgresInput)), true + case "Mutation.createPostgresAccess": if e.ComplexityRoot.Mutation.CreatePostgresAccess == nil { break @@ -10589,6 +10653,18 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.Mutation.UpdateOpenSearch(childComplexity, args["input"].(opensearch.UpdateOpenSearchInput)), true + case "Mutation.updatePostgres": + if e.ComplexityRoot.Mutation.UpdatePostgres == nil { + break + } + + args, err := ec.field_Mutation_updatePostgres_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.ComplexityRoot.Mutation.UpdatePostgres(childComplexity, args["input"].(postgres.UpdatePostgresInput)), true + case "Mutation.updateSecret": if e.ComplexityRoot.Mutation.UpdateSecret == nil { break @@ -11957,6 +12033,69 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.PostgresBranchStateFacetItem.State(childComplexity), true + case "PostgresCreatedActivityLogEntry.actor": + if e.ComplexityRoot.PostgresCreatedActivityLogEntry.Actor == nil { + break + } + + return e.ComplexityRoot.PostgresCreatedActivityLogEntry.Actor(childComplexity), true + + case "PostgresCreatedActivityLogEntry.createdAt": + if e.ComplexityRoot.PostgresCreatedActivityLogEntry.CreatedAt == nil { + break + } + + return e.ComplexityRoot.PostgresCreatedActivityLogEntry.CreatedAt(childComplexity), true + + case "PostgresCreatedActivityLogEntry.environmentName": + if e.ComplexityRoot.PostgresCreatedActivityLogEntry.EnvironmentName == nil { + break + } + + return e.ComplexityRoot.PostgresCreatedActivityLogEntry.EnvironmentName(childComplexity), true + + case "PostgresCreatedActivityLogEntry.gitHubActorClaims": + if e.ComplexityRoot.PostgresCreatedActivityLogEntry.GitHubActorClaims == nil { + break + } + + return e.ComplexityRoot.PostgresCreatedActivityLogEntry.GitHubActorClaims(childComplexity), true + + case "PostgresCreatedActivityLogEntry.id": + if e.ComplexityRoot.PostgresCreatedActivityLogEntry.ID == nil { + break + } + + return e.ComplexityRoot.PostgresCreatedActivityLogEntry.ID(childComplexity), true + + case "PostgresCreatedActivityLogEntry.message": + if e.ComplexityRoot.PostgresCreatedActivityLogEntry.Message == nil { + break + } + + return e.ComplexityRoot.PostgresCreatedActivityLogEntry.Message(childComplexity), true + + case "PostgresCreatedActivityLogEntry.resourceName": + if e.ComplexityRoot.PostgresCreatedActivityLogEntry.ResourceName == nil { + break + } + + return e.ComplexityRoot.PostgresCreatedActivityLogEntry.ResourceName(childComplexity), true + + case "PostgresCreatedActivityLogEntry.resourceType": + if e.ComplexityRoot.PostgresCreatedActivityLogEntry.ResourceType == nil { + break + } + + return e.ComplexityRoot.PostgresCreatedActivityLogEntry.ResourceType(childComplexity), true + + case "PostgresCreatedActivityLogEntry.teamSlug": + if e.ComplexityRoot.PostgresCreatedActivityLogEntry.TeamSlug == nil { + break + } + + return e.ComplexityRoot.PostgresCreatedActivityLogEntry.TeamSlug(childComplexity), true + case "PostgresDeletedActivityLogEntry.actor": if e.ComplexityRoot.PostgresDeletedActivityLogEntry.Actor == nil { break @@ -12286,6 +12425,104 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.PostgresResources.Memory(childComplexity), true + case "PostgresUpdatedActivityLogEntry.actor": + if e.ComplexityRoot.PostgresUpdatedActivityLogEntry.Actor == nil { + break + } + + return e.ComplexityRoot.PostgresUpdatedActivityLogEntry.Actor(childComplexity), true + + case "PostgresUpdatedActivityLogEntry.createdAt": + if e.ComplexityRoot.PostgresUpdatedActivityLogEntry.CreatedAt == nil { + break + } + + return e.ComplexityRoot.PostgresUpdatedActivityLogEntry.CreatedAt(childComplexity), true + + case "PostgresUpdatedActivityLogEntry.data": + if e.ComplexityRoot.PostgresUpdatedActivityLogEntry.Data == nil { + break + } + + return e.ComplexityRoot.PostgresUpdatedActivityLogEntry.Data(childComplexity), true + + case "PostgresUpdatedActivityLogEntry.environmentName": + if e.ComplexityRoot.PostgresUpdatedActivityLogEntry.EnvironmentName == nil { + break + } + + return e.ComplexityRoot.PostgresUpdatedActivityLogEntry.EnvironmentName(childComplexity), true + + case "PostgresUpdatedActivityLogEntry.gitHubActorClaims": + if e.ComplexityRoot.PostgresUpdatedActivityLogEntry.GitHubActorClaims == nil { + break + } + + return e.ComplexityRoot.PostgresUpdatedActivityLogEntry.GitHubActorClaims(childComplexity), true + + case "PostgresUpdatedActivityLogEntry.id": + if e.ComplexityRoot.PostgresUpdatedActivityLogEntry.ID == nil { + break + } + + return e.ComplexityRoot.PostgresUpdatedActivityLogEntry.ID(childComplexity), true + + case "PostgresUpdatedActivityLogEntry.message": + if e.ComplexityRoot.PostgresUpdatedActivityLogEntry.Message == nil { + break + } + + return e.ComplexityRoot.PostgresUpdatedActivityLogEntry.Message(childComplexity), true + + case "PostgresUpdatedActivityLogEntry.resourceName": + if e.ComplexityRoot.PostgresUpdatedActivityLogEntry.ResourceName == nil { + break + } + + return e.ComplexityRoot.PostgresUpdatedActivityLogEntry.ResourceName(childComplexity), true + + case "PostgresUpdatedActivityLogEntry.resourceType": + if e.ComplexityRoot.PostgresUpdatedActivityLogEntry.ResourceType == nil { + break + } + + return e.ComplexityRoot.PostgresUpdatedActivityLogEntry.ResourceType(childComplexity), true + + case "PostgresUpdatedActivityLogEntry.teamSlug": + if e.ComplexityRoot.PostgresUpdatedActivityLogEntry.TeamSlug == nil { + break + } + + return e.ComplexityRoot.PostgresUpdatedActivityLogEntry.TeamSlug(childComplexity), true + + case "PostgresUpdatedActivityLogEntryData.updatedFields": + if e.ComplexityRoot.PostgresUpdatedActivityLogEntryData.UpdatedFields == nil { + break + } + + return e.ComplexityRoot.PostgresUpdatedActivityLogEntryData.UpdatedFields(childComplexity), true + + case "PostgresUpdatedActivityLogEntryDataUpdatedField.field": + if e.ComplexityRoot.PostgresUpdatedActivityLogEntryDataUpdatedField.Field == nil { + break + } + + return e.ComplexityRoot.PostgresUpdatedActivityLogEntryDataUpdatedField.Field(childComplexity), true + + case "PostgresUpdatedActivityLogEntryDataUpdatedField.newValue": + if e.ComplexityRoot.PostgresUpdatedActivityLogEntryDataUpdatedField.NewValue == nil { + break + } + + return e.ComplexityRoot.PostgresUpdatedActivityLogEntryDataUpdatedField.NewValue(childComplexity), true + + case "PostgresUpdatedActivityLogEntryDataUpdatedField.oldValue": + if e.ComplexityRoot.PostgresUpdatedActivityLogEntryDataUpdatedField.OldValue == nil { + break + } + + return e.ComplexityRoot.PostgresUpdatedActivityLogEntryDataUpdatedField.OldValue(childComplexity), true + case "Price.value": if e.ComplexityRoot.Price.Value == nil { break @@ -19068,6 +19305,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.UpdateOpenSearchPayload.OpenSearch(childComplexity), true + case "UpdatePostgresPayload.postgres": + if e.ComplexityRoot.UpdatePostgresPayload.Postgres == nil { + break + } + + return e.ComplexityRoot.UpdatePostgresPayload.Postgres(childComplexity), true + case "UpdateSecretPayload.secret": if e.ComplexityRoot.UpdateSecretPayload.Secret == nil { break @@ -20879,6 +21123,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputCreateOpenSearchCredentialsInput, ec.unmarshalInputCreateOpenSearchInput, ec.unmarshalInputCreatePostgresAccessInput, + ec.unmarshalInputCreatePostgresInput, ec.unmarshalInputCreateSecretInput, ec.unmarshalInputCreateServiceAccountInput, ec.unmarshalInputCreateServiceAccountTokenInput, @@ -20968,6 +21213,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputUpdateJobInput, ec.unmarshalInputUpdateKafkaTopicInput, ec.unmarshalInputUpdateOpenSearchInput, + ec.unmarshalInputUpdatePostgresInput, ec.unmarshalInputUpdateSecretInput, ec.unmarshalInputUpdateSecretValueInput, ec.unmarshalInputUpdateServiceAccountInput, @@ -27735,7 +27981,96 @@ type PostgresDeletedActivityLogEntry implements ActivityLogEntry & Node { environmentName: String } +"Activity log entry for a Postgres that was created." +type PostgresCreatedActivityLogEntry implements ActivityLogEntry & Node { + "ID of the entry." + id: ID! + + "The identity of the actor who performed the action. The value is either the name of a service account, or the email address of a user." + actor: String! + + "GitHub Actions OIDC claims when authenticated by a GitHub repository." + gitHubActorClaims: GitHubActorClaims + + "Creation time of the entry." + createdAt: Time! + + "Message that summarizes the entry." + message: String! + + "Type of the resource that was affected by the action." + resourceType: ActivityLogEntryResourceType! + + "Name of the resource that was affected by the action." + resourceName: String! + + "The team slug that the entry belongs to." + teamSlug: Slug! + + "The environment name that the entry belongs to." + environmentName: String +} + +"Activity log entry for a Postgres that was updated." +type PostgresUpdatedActivityLogEntry implements ActivityLogEntry & Node { + "ID of the entry." + id: ID! + + "The identity of the actor who performed the action. The value is either the name of a service account, or the email address of a user." + actor: String! + + "GitHub Actions OIDC claims when authenticated by a GitHub repository." + gitHubActorClaims: GitHubActorClaims + + "Creation time of the entry." + createdAt: Time! + + "Message that summarizes the entry." + message: String! + + "Type of the resource that was affected by the action." + resourceType: ActivityLogEntryResourceType! + + "Name of the resource that was affected by the action." + resourceName: String! + + "The team slug that the entry belongs to." + teamSlug: Slug! + + "The environment name that the entry belongs to." + environmentName: String + + "Data associated with the entry." + data: PostgresUpdatedActivityLogEntryData! +} + +"Data associated with a Postgres update." +type PostgresUpdatedActivityLogEntryData { + "The fields that were changed." + updatedFields: [PostgresUpdatedActivityLogEntryDataUpdatedField!]! +} + +"A single field that was changed by a Postgres update." +type PostgresUpdatedActivityLogEntryDataUpdatedField { + "The name of the field." + field: String! + + "The old value of the field, if it had one." + oldValue: String + + "The new value of the field." + newValue: String +} + extend enum ActivityLogActivityType { + """ + A Postgres was created + """ + POSTGRES_CREATED + """ + A Postgres was updated + """ + POSTGRES_UPDATED """ A user was granted access to a Postgres cluster """ @@ -27761,6 +28096,10 @@ extend type Mutation { When the access is ready, retrieve its connection materials through PostgresAccess.connection. """ createPostgresAccess(input: CreatePostgresAccessInput!): CreatePostgresAccessPayload! + "Create a new Postgres. Its first branch, main, is created and activated by the platform." + createPostgres(input: CreatePostgresInput!): CreatePostgresPayload! + "Update an existing Postgres. The PostgreSQL major version cannot be changed." + updatePostgres(input: UpdatePostgresInput!): UpdatePostgresPayload! "Delete a PostgresBranch that is not active on its Postgres." deletePostgresBranch(input: DeletePostgresBranchInput!): DeletePostgresBranchPayload! } @@ -27884,6 +28223,56 @@ type PostgresAccessConnectionDetails { "Owner-only bearer token for this access; never log it." relayToken: String! } + +"Input for creating a Postgres." +input CreatePostgresInput { + "Name of the Postgres." + name: String! + "The environment name that the Postgres belongs to." + environmentName: String! + "The team that owns the Postgres." + teamSlug: Slug! + "PostgreSQL major version." + majorVersion: String! + "Add a third instance and enable synchronous replication. Defaults to false." + highAvailability: Boolean + "Requested CPU, for example '100m'. If omitted, the platform default is used." + cpu: String + "Requested memory, for example '512Mi'. If omitted, the platform default is used." + memory: String + "Requested disk size, for example '10Gi'. If omitted, the platform default is used." + diskSize: String +} + +"Result of creating a Postgres." +type CreatePostgresPayload { + "The Postgres that was created." + postgres: Postgres! +} + +"Input for updating a Postgres. Omitted fields are left unchanged." +input UpdatePostgresInput { + "Name of the Postgres." + name: String! + "The environment name that the Postgres belongs to." + environmentName: String! + "The team that owns the Postgres." + teamSlug: Slug! + "Add a third instance and enable synchronous replication." + highAvailability: Boolean + "Requested CPU, for example '100m'." + cpu: String + "Requested memory, for example '512Mi'." + memory: String + "Requested disk size, for example '10Gi'." + diskSize: String +} + +"Result of updating a Postgres." +type UpdatePostgresPayload { + "The Postgres that was updated." + postgres: Postgres! +} `, BuiltIn: false}, {Name: "../schema/price.graphqls", Input: `extend type Query { """ @@ -36112,6 +36501,14 @@ func (ec *executionContext) childFields_CreatePostgresAccessPayload(ctx context. return nil, fmt.Errorf("no field named %q was found under type CreatePostgresAccessPayload", field.Name) } +func (ec *executionContext) childFields_CreatePostgresPayload(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "postgres": + return ec.fieldContext_CreatePostgresPayload_postgres(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type CreatePostgresPayload", field.Name) +} + func (ec *executionContext) childFields_CreateSecretPayload(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "secret": @@ -37868,6 +38265,26 @@ func (ec *executionContext) childFields_PostgresResources(ctx context.Context, f return nil, fmt.Errorf("no field named %q was found under type PostgresResources", field.Name) } +func (ec *executionContext) childFields_PostgresUpdatedActivityLogEntryData(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "updatedFields": + return ec.fieldContext_PostgresUpdatedActivityLogEntryData_updatedFields(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type PostgresUpdatedActivityLogEntryData", field.Name) +} + +func (ec *executionContext) childFields_PostgresUpdatedActivityLogEntryDataUpdatedField(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "field": + return ec.fieldContext_PostgresUpdatedActivityLogEntryDataUpdatedField_field(ctx, field) + case "oldValue": + return ec.fieldContext_PostgresUpdatedActivityLogEntryDataUpdatedField_oldValue(ctx, field) + case "newValue": + return ec.fieldContext_PostgresUpdatedActivityLogEntryDataUpdatedField_newValue(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type PostgresUpdatedActivityLogEntryDataUpdatedField", field.Name) +} + func (ec *executionContext) childFields_Price(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "value": @@ -39714,6 +40131,14 @@ func (ec *executionContext) childFields_UpdateOpenSearchPayload(ctx context.Cont return nil, fmt.Errorf("no field named %q was found under type UpdateOpenSearchPayload", field.Name) } +func (ec *executionContext) childFields_UpdatePostgresPayload(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "postgres": + return ec.fieldContext_UpdatePostgresPayload_postgres(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type UpdatePostgresPayload", field.Name) +} + func (ec *executionContext) childFields_UpdateSecretPayload(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "secret": diff --git a/internal/graph/gengql/schema.generated.go b/internal/graph/gengql/schema.generated.go index 6c8b9e196..d16ba505a 100644 --- a/internal/graph/gengql/schema.generated.go +++ b/internal/graph/gengql/schema.generated.go @@ -85,6 +85,8 @@ type MutationResolver interface { DeleteOpenSearch(ctx context.Context, input opensearch.DeleteOpenSearchInput) (*opensearch.DeleteOpenSearchPayload, error) CreateOpenSearchCredentials(ctx context.Context, input opensearch.CreateOpenSearchCredentialsInput) (*opensearch.CreateOpenSearchCredentialsPayload, error) CreatePostgresAccess(ctx context.Context, input postgres.CreatePostgresAccessInput) (*postgres.CreatePostgresAccessPayload, error) + CreatePostgres(ctx context.Context, input postgres.CreatePostgresInput) (*postgres.CreatePostgresPayload, error) + UpdatePostgres(ctx context.Context, input postgres.UpdatePostgresInput) (*postgres.UpdatePostgresPayload, error) DeletePostgresBranch(ctx context.Context, input postgres.DeletePostgresBranchInput) (*postgres.DeletePostgresBranchPayload, error) EnableReconciler(ctx context.Context, input reconciler.EnableReconcilerInput) (*reconciler.Reconciler, error) DisableReconciler(ctx context.Context, input reconciler.DisableReconcilerInput) (*reconciler.Reconciler, error) @@ -378,6 +380,20 @@ func (ec *executionContext) field_Mutation_createPostgresAccess_args(ctx context return args, nil } +func (ec *executionContext) field_Mutation_createPostgres_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "input", + func(ctx context.Context, v any) (postgres.CreatePostgresInput, error) { + return ec.unmarshalNCreatePostgresInput2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋpostgresᚐCreatePostgresInput(ctx, v) + }) + if err != nil { + return nil, err + } + args["input"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_createSecret_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -966,6 +982,20 @@ func (ec *executionContext) field_Mutation_updateOpenSearch_args(ctx context.Con return args, nil } +func (ec *executionContext) field_Mutation_updatePostgres_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "input", + func(ctx context.Context, v any) (postgres.UpdatePostgresInput, error) { + return ec.unmarshalNUpdatePostgresInput2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋpostgresᚐUpdatePostgresInput(ctx, v) + }) + if err != nil { + return nil, err + } + args["input"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_updateSecretValue_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -2680,6 +2710,94 @@ func (ec *executionContext) fieldContext_Mutation_createPostgresAccess(ctx conte return fc, nil } +func (ec *executionContext) _Mutation_createPostgres(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_Mutation_createPostgres(ctx, field) + }, + func(ctx context.Context) (any, error) { + fc := graphql.GetFieldContext(ctx) + return ec.Resolvers.Mutation().CreatePostgres(ctx, fc.Args["input"].(postgres.CreatePostgresInput)) + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *postgres.CreatePostgresPayload) graphql.Marshaler { + return ec.marshalNCreatePostgresPayload2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋpostgresᚐCreatePostgresPayload(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_Mutation_createPostgres(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_CreatePostgresPayload(ctx, field) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Mutation_createPostgres_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Mutation_updatePostgres(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_Mutation_updatePostgres(ctx, field) + }, + func(ctx context.Context) (any, error) { + fc := graphql.GetFieldContext(ctx) + return ec.Resolvers.Mutation().UpdatePostgres(ctx, fc.Args["input"].(postgres.UpdatePostgresInput)) + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *postgres.UpdatePostgresPayload) graphql.Marshaler { + return ec.marshalNUpdatePostgresPayload2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋpostgresᚐUpdatePostgresPayload(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_Mutation_updatePostgres(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_UpdatePostgresPayload(ctx, field) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Mutation_updatePostgres_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _Mutation_deletePostgresBranch(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -6525,6 +6643,13 @@ func (ec *executionContext) _Node(ctx context.Context, sel ast.SelectionSet, obj return graphql.Null } return ec._PrometheusAlert(ctx, sel, obj) + case postgres.PostgresUpdatedActivityLogEntry: + return ec._PostgresUpdatedActivityLogEntry(ctx, sel, &obj) + case *postgres.PostgresUpdatedActivityLogEntry: + if obj == nil { + return graphql.Null + } + return ec._PostgresUpdatedActivityLogEntry(ctx, sel, obj) case postgres.PostgresPersonalAccessCreatedActivityLogEntry: return ec._PostgresPersonalAccessCreatedActivityLogEntry(ctx, sel, &obj) case *postgres.PostgresPersonalAccessCreatedActivityLogEntry: @@ -6553,6 +6678,13 @@ func (ec *executionContext) _Node(ctx context.Context, sel ast.SelectionSet, obj return graphql.Null } return ec._PostgresDeletedActivityLogEntry(ctx, sel, obj) + case postgres.PostgresCreatedActivityLogEntry: + return ec._PostgresCreatedActivityLogEntry(ctx, sel, &obj) + case *postgres.PostgresCreatedActivityLogEntry: + if obj == nil { + return graphql.Null + } + return ec._PostgresCreatedActivityLogEntry(ctx, sel, obj) case postgres.PostgresBranch: return ec._PostgresBranch(ctx, sel, &obj) case *postgres.PostgresBranch: @@ -7269,6 +7401,20 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { out.Invalids++ } + case "createPostgres": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_createPostgres(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "updatePostgres": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_updatePostgres(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } case "deletePostgresBranch": out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { return ec._Mutation_deletePostgresBranch(ctx, field) diff --git a/internal/graph/postgres.resolvers.go b/internal/graph/postgres.resolvers.go index 381052242..ea2b456aa 100644 --- a/internal/graph/postgres.resolvers.go +++ b/internal/graph/postgres.resolvers.go @@ -43,6 +43,20 @@ func (r *mutationResolver) CreatePostgresAccess(ctx context.Context, input postg return postgres.CreatePostgresAccess(ctx, input) } +func (r *mutationResolver) CreatePostgres(ctx context.Context, input postgres.CreatePostgresInput) (*postgres.CreatePostgresPayload, error) { + if err := authz.CanCreatePostgres(ctx, input.TeamSlug); err != nil { + return nil, err + } + return postgres.Create(ctx, input) +} + +func (r *mutationResolver) UpdatePostgres(ctx context.Context, input postgres.UpdatePostgresInput) (*postgres.UpdatePostgresPayload, error) { + if err := authz.CanUpdatePostgres(ctx, input.TeamSlug); err != nil { + return nil, err + } + return postgres.Update(ctx, input) +} + func (r *mutationResolver) DeletePostgresBranch(ctx context.Context, input postgres.DeletePostgresBranchInput) (*postgres.DeletePostgresBranchPayload, error) { if err := authz.CanDeletePostgres(ctx, input.TeamSlug); err != nil { return nil, err diff --git a/internal/graph/schema/postgres.graphqls b/internal/graph/schema/postgres.graphqls index 9d636eb16..a55398f9b 100644 --- a/internal/graph/schema/postgres.graphqls +++ b/internal/graph/schema/postgres.graphqls @@ -341,7 +341,96 @@ type PostgresDeletedActivityLogEntry implements ActivityLogEntry & Node { environmentName: String } +"Activity log entry for a Postgres that was created." +type PostgresCreatedActivityLogEntry implements ActivityLogEntry & Node { + "ID of the entry." + id: ID! + + "The identity of the actor who performed the action. The value is either the name of a service account, or the email address of a user." + actor: String! + + "GitHub Actions OIDC claims when authenticated by a GitHub repository." + gitHubActorClaims: GitHubActorClaims + + "Creation time of the entry." + createdAt: Time! + + "Message that summarizes the entry." + message: String! + + "Type of the resource that was affected by the action." + resourceType: ActivityLogEntryResourceType! + + "Name of the resource that was affected by the action." + resourceName: String! + + "The team slug that the entry belongs to." + teamSlug: Slug! + + "The environment name that the entry belongs to." + environmentName: String +} + +"Activity log entry for a Postgres that was updated." +type PostgresUpdatedActivityLogEntry implements ActivityLogEntry & Node { + "ID of the entry." + id: ID! + + "The identity of the actor who performed the action. The value is either the name of a service account, or the email address of a user." + actor: String! + + "GitHub Actions OIDC claims when authenticated by a GitHub repository." + gitHubActorClaims: GitHubActorClaims + + "Creation time of the entry." + createdAt: Time! + + "Message that summarizes the entry." + message: String! + + "Type of the resource that was affected by the action." + resourceType: ActivityLogEntryResourceType! + + "Name of the resource that was affected by the action." + resourceName: String! + + "The team slug that the entry belongs to." + teamSlug: Slug! + + "The environment name that the entry belongs to." + environmentName: String + + "Data associated with the entry." + data: PostgresUpdatedActivityLogEntryData! +} + +"Data associated with a Postgres update." +type PostgresUpdatedActivityLogEntryData { + "The fields that were changed." + updatedFields: [PostgresUpdatedActivityLogEntryDataUpdatedField!]! +} + +"A single field that was changed by a Postgres update." +type PostgresUpdatedActivityLogEntryDataUpdatedField { + "The name of the field." + field: String! + + "The old value of the field, if it had one." + oldValue: String + + "The new value of the field." + newValue: String +} + extend enum ActivityLogActivityType { + """ + A Postgres was created + """ + POSTGRES_CREATED + """ + A Postgres was updated + """ + POSTGRES_UPDATED """ A user was granted access to a Postgres cluster """ @@ -367,6 +456,10 @@ extend type Mutation { When the access is ready, retrieve its connection materials through PostgresAccess.connection. """ createPostgresAccess(input: CreatePostgresAccessInput!): CreatePostgresAccessPayload! + "Create a new Postgres. Its first branch, main, is created and activated by the platform." + createPostgres(input: CreatePostgresInput!): CreatePostgresPayload! + "Update an existing Postgres. The PostgreSQL major version cannot be changed." + updatePostgres(input: UpdatePostgresInput!): UpdatePostgresPayload! "Delete a PostgresBranch that is not active on its Postgres." deletePostgresBranch(input: DeletePostgresBranchInput!): DeletePostgresBranchPayload! } @@ -490,3 +583,53 @@ type PostgresAccessConnectionDetails { "Owner-only bearer token for this access; never log it." relayToken: String! } + +"Input for creating a Postgres." +input CreatePostgresInput { + "Name of the Postgres." + name: String! + "The environment name that the Postgres belongs to." + environmentName: String! + "The team that owns the Postgres." + teamSlug: Slug! + "PostgreSQL major version." + majorVersion: String! + "Add a third instance and enable synchronous replication. Defaults to false." + highAvailability: Boolean + "Requested CPU, for example '100m'. If omitted, the platform default is used." + cpu: String + "Requested memory, for example '512Mi'. If omitted, the platform default is used." + memory: String + "Requested disk size, for example '10Gi'. If omitted, the platform default is used." + diskSize: String +} + +"Result of creating a Postgres." +type CreatePostgresPayload { + "The Postgres that was created." + postgres: Postgres! +} + +"Input for updating a Postgres. Omitted fields are left unchanged." +input UpdatePostgresInput { + "Name of the Postgres." + name: String! + "The environment name that the Postgres belongs to." + environmentName: String! + "The team that owns the Postgres." + teamSlug: Slug! + "Add a third instance and enable synchronous replication." + highAvailability: Boolean + "Requested CPU, for example '100m'." + cpu: String + "Requested memory, for example '512Mi'." + memory: String + "Requested disk size, for example '10Gi'." + diskSize: String +} + +"Result of updating a Postgres." +type UpdatePostgresPayload { + "The Postgres that was updated." + postgres: Postgres! +} diff --git a/internal/persistence/postgres/activitylog.go b/internal/persistence/postgres/activitylog.go index 1c6999c74..e0b533e1e 100644 --- a/internal/persistence/postgres/activitylog.go +++ b/internal/persistence/postgres/activitylog.go @@ -18,6 +18,19 @@ const ( func init() { activitylog.RegisterTransformer(activityLogEntryResourceTypePostgres, func(entry activitylog.GenericActivityLogEntry) (activitylog.ActivityLogEntry, error) { switch entry.Action { + case activitylog.ActivityLogEntryActionCreated: + return PostgresCreatedActivityLogEntry{ + GenericActivityLogEntry: entry.WithMessage("Created Postgres"), + }, nil + case activitylog.ActivityLogEntryActionUpdated: + data, err := activitylog.UnmarshalData[PostgresUpdatedActivityLogEntryData](entry) + if err != nil { + return nil, fmt.Errorf("transforming postgres updated activity log entry data: %w", err) + } + return PostgresUpdatedActivityLogEntry{ + GenericActivityLogEntry: entry.WithMessage("Updated Postgres"), + Data: data, + }, nil case activitylog.ActivityLogEntryActionDeleted: return PostgresDeletedActivityLogEntry{ GenericActivityLogEntry: entry.WithMessage("Deleted Postgres"), @@ -62,6 +75,8 @@ func init() { activitylog.RegisterFilter("POSTGRES_GRANT_ACCESS", activityLogEntryActionGrantAccess, activityLogEntryResourceTypePostgres) activitylog.RegisterFilter("POSTGRES_PERSONAL_ACCESS_CREATED", activityLogEntryActionCreatePersonalAccess, activityLogEntryResourceTypePostgres) activitylog.RegisterFilter("POSTGRES_PERSONAL_ACCESS_CONNECTION", activityLogEntryActionGetPersonalAccessConnection, activityLogEntryResourceTypePostgres) + activitylog.RegisterFilter("POSTGRES_CREATED", activitylog.ActivityLogEntryActionCreated, activityLogEntryResourceTypePostgres) + activitylog.RegisterFilter("POSTGRES_UPDATED", activitylog.ActivityLogEntryActionUpdated, activityLogEntryResourceTypePostgres) activitylog.RegisterFilter("POSTGRES_DELETED", activitylog.ActivityLogEntryActionDeleted, activityLogEntryResourceTypePostgres) } @@ -69,6 +84,25 @@ type PostgresDeletedActivityLogEntry struct { activitylog.GenericActivityLogEntry } +type PostgresCreatedActivityLogEntry struct { + activitylog.GenericActivityLogEntry +} + +type PostgresUpdatedActivityLogEntry struct { + activitylog.GenericActivityLogEntry + Data *PostgresUpdatedActivityLogEntryData `json:"data"` +} + +type PostgresUpdatedActivityLogEntryData struct { + UpdatedFields []*PostgresUpdatedActivityLogEntryDataUpdatedField `json:"updatedFields"` +} + +type PostgresUpdatedActivityLogEntryDataUpdatedField struct { + Field string `json:"field"` + OldValue *string `json:"oldValue,omitempty"` + NewValue *string `json:"newValue,omitempty"` +} + type PostgresGrantAccessActivityLogEntry struct { activitylog.GenericActivityLogEntry diff --git a/internal/persistence/postgres/connection.go b/internal/persistence/postgres/connection.go index ef508c3f4..d23f585c5 100644 --- a/internal/persistence/postgres/connection.go +++ b/internal/persistence/postgres/connection.go @@ -30,7 +30,7 @@ func loadPostgresAccessConnection(ctx context.Context, access *unstructured.Unst if err != nil { return err } - // The broker creates short names (postgres-access-), so this is the + // The broker creates short names (postgres-access-), so this is the // pgrator-owned credential Secret for this access, not a client-supplied name. credential, err := getAccessResource(ctx, input.EnvironmentName, namespace, access.GetName()+"-credentials", schema.GroupVersionResource{Version: "v1", Resource: "secrets"}) if err != nil { diff --git a/internal/persistence/postgres/models.go b/internal/persistence/postgres/models.go index 76cc0a761..32f5b9099 100644 --- a/internal/persistence/postgres/models.go +++ b/internal/persistence/postgres/models.go @@ -24,6 +24,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation" ) type PostgresBranchEdge = pagination.Edge[*PostgresBranch] @@ -563,3 +564,111 @@ type PostgresAccessConnectionDetails struct { RelayAccess string `json:"relayAccess"` RelayToken string `json:"relayToken"` } + +// supportedMajorVersions are the versions the API accepts for new Postgres. The CRD enum +// also allows older versions, but those are not offered to users. +var supportedMajorVersions = []string{"18"} + +type CreatePostgresInput struct { + Name string `json:"name"` + EnvironmentName string `json:"environmentName"` + TeamSlug slug.Slug `json:"teamSlug"` + MajorVersion string `json:"majorVersion"` + HighAvailability *bool `json:"highAvailability,omitempty"` + CPU *string `json:"cpu,omitempty"` + Memory *string `json:"memory,omitempty"` + DiskSize *string `json:"diskSize,omitempty"` +} + +type CreatePostgresPayload struct { + Postgres *Postgres `json:"postgres"` +} + +type UpdatePostgresInput struct { + Name string `json:"name"` + EnvironmentName string `json:"environmentName"` + TeamSlug slug.Slug `json:"teamSlug"` + HighAvailability *bool `json:"highAvailability,omitempty"` + CPU *string `json:"cpu,omitempty"` + Memory *string `json:"memory,omitempty"` + DiskSize *string `json:"diskSize,omitempty"` +} + +type UpdatePostgresPayload struct { + Postgres *Postgres `json:"postgres"` +} + +func (i *CreatePostgresInput) Validate(ctx context.Context) error { + return i.ValidationErrors(ctx).NilIfEmpty() +} + +func (i *CreatePostgresInput) ValidationErrors(_ context.Context) *validate.ValidationErrors { + verr := validate.New() + i.Name = strings.TrimSpace(i.Name) + i.EnvironmentName = strings.TrimSpace(i.EnvironmentName) + i.MajorVersion = strings.TrimSpace(i.MajorVersion) + + if i.Name == "" { + verr.Add("name", "Name must not be empty.") + } else if errs := validation.IsDNS1123Subdomain(i.Name); len(errs) > 0 { + verr.Add("name", "Name must consist of lowercase letters, numbers, and hyphens only. It cannot start or end with a hyphen.") + } + if i.EnvironmentName == "" { + verr.Add("environmentName", "Environment name must not be empty.") + } + if i.TeamSlug == "" { + verr.Add("teamSlug", "Team slug must not be empty.") + } + if !contains(supportedMajorVersions, i.MajorVersion) { + verr.Add("majorVersion", "Major version must be one of: %s.", strings.Join(supportedMajorVersions, ", ")) + } + validateQuantities(verr, i.CPU, i.Memory, i.DiskSize) + return verr +} + +func (i *UpdatePostgresInput) Validate(ctx context.Context) error { + return i.ValidationErrors(ctx).NilIfEmpty() +} + +func (i *UpdatePostgresInput) ValidationErrors(_ context.Context) *validate.ValidationErrors { + verr := validate.New() + i.Name = strings.TrimSpace(i.Name) + i.EnvironmentName = strings.TrimSpace(i.EnvironmentName) + + if i.Name == "" { + verr.Add("name", "Name must not be empty.") + } else if errs := validation.IsDNS1123Subdomain(i.Name); len(errs) > 0 { + verr.Add("name", "Name must consist of lowercase letters, numbers, and hyphens only. It cannot start or end with a hyphen.") + } + if i.EnvironmentName == "" { + verr.Add("environmentName", "Environment name must not be empty.") + } + if i.TeamSlug == "" { + verr.Add("teamSlug", "Team slug must not be empty.") + } + validateQuantities(verr, i.CPU, i.Memory, i.DiskSize) + return verr +} + +func validateQuantities(verr *validate.ValidationErrors, cpu, memory, diskSize *string) { + for field, value := range map[string]*string{"cpu": cpu, "memory": memory, "diskSize": diskSize} { + if value == nil { + continue + } + quantity, err := resource.ParseQuantity(*value) + if err != nil { + verr.Add(field, "%q is not a valid quantity.", *value) + } else if quantity.Sign() <= 0 { + verr.Add(field, "%q must be greater than zero.", *value) + } + } +} + +func contains(values []string, value string) bool { + for _, v := range values { + if v == value { + return true + } + } + return false +} diff --git a/internal/persistence/postgres/queries.go b/internal/persistence/postgres/queries.go index 80fdc7874..439f206de 100644 --- a/internal/persistence/postgres/queries.go +++ b/internal/persistence/postgres/queries.go @@ -3,13 +3,15 @@ package postgres import ( "cmp" "context" + "crypto/sha256" + "encoding/hex" "errors" "fmt" "slices" + "strconv" "strings" "time" - "github.com/google/uuid" "github.com/nais/api/internal/activitylog" "github.com/nais/api/internal/auth/authz" "github.com/nais/api/internal/graph/apierror" @@ -25,9 +27,11 @@ import ( liberatorv1 "github.com/nais/liberator/pkg/apis/nais.io/v1" nais_io_v1 "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" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" "k8s.io/utils/ptr" ) @@ -235,6 +239,12 @@ func GetPostgresAccessConnection(ctx context.Context, input PostgresAccessConnec return nil, authz.ErrUnauthorized } + // Status lives in PostgresAccess.state. Until the access is ready there are no + // connection materials, which is not an error for a caller polling for readiness. + if state, _ := postgresAccessState(resource, resource.Spec.ExpiresAt.Time); state != PostgresAccessStateReady || access.GetDeletionTimestamp() != nil { + return nil, nil + } + connection, credentialSecretName, err := postgresAccessConnectionDetails(access, time.Now()) if err != nil { return nil, err @@ -481,10 +491,38 @@ func CreatePostgresAccess(ctx context.Context, input CreatePostgresAccessInput) } expiresAt := time.Now().Add(accessTTL) - name := fmt.Sprintf("postgres-access-%s", uuid.NewString()[:8]) - res := newPostgresAccessResource(input, authz.ActorFromContext(ctx).User.Identity(), name, expiresAt) - - if _, err := client.Namespace(input.TeamSlug.String()).Create(ctx, res, metav1.CreateOptions{}); err != nil { + username := authz.ActorFromContext(ctx).User.Identity() + branchObjectName := nais_io_v1.PostgresBranchObjectName(input.Postgres, input.Branch) + + // One access per user and branch: pgrator derives a single personal database role from + // them, so a second access could never become ready. The name is derived from the pair, so + // the API server enforces this atomically through AlreadyExists. + name := postgresAccessName(username, branchObjectName) + accesses := client.Namespace(input.TeamSlug.String()) + + res := newPostgresAccessResource(input, username, name, expiresAt) + _, err = accesses.Create(ctx, res, metav1.CreateOptions{}) + if k8serrors.IsAlreadyExists(err) { + existing, getErr := accesses.Get(ctx, name, metav1.GetOptions{}) + if getErr != nil { + return nil, getErr + } + if existing.GetDeletionTimestamp() == nil && existingExpiry(existing).After(time.Now()) { + level, _, _ := unstructured.NestedString(existing.Object, "spec", "accessLevel") + if level != input.AccessLevel.CRDValue() { + return nil, apierror.Errorf("You already have %s access to this branch until %s. Wait for it to expire before requesting a different access level.", level, existingExpiry(existing).Format(time.RFC3339)) + } + return &CreatePostgresAccessPayload{Name: name, ExpiresAt: existingExpiry(existing)}, nil + } + // The spec is immutable and nothing removes expired accesses, so clear it out of the way. + // Preconditions make sure we only delete the object we inspected. + uid := existing.GetUID() + if err := accesses.Delete(ctx, name, metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &uid}}); err != nil && !k8serrors.IsNotFound(err) { + return nil, err + } + return nil, apierror.Errorf("Your previous access to this branch is being cleaned up. Try again in a few seconds.") + } + if err != nil { return nil, err } @@ -508,6 +546,20 @@ func CreatePostgresAccess(ctx context.Context, input CreatePostgresAccessInput) return &CreatePostgresAccessPayload{Name: name, ExpiresAt: expiresAt}, nil } +func existingExpiry(access *unstructured.Unstructured) time.Time { + value, _, _ := unstructured.NestedString(access.Object, "spec", "expiresAt") + t, _ := time.Parse(time.RFC3339, value) + return t +} + +// postgresAccessName derives the access name from user and branch, so the Kubernetes API +// server rejects a second concurrent access for the same pair. The NUL separator keeps +// different (user, branch) pairs from producing the same input. +func postgresAccessName(username, branchObjectName string) string { + sum := sha256.Sum256([]byte(username + "\x00" + branchObjectName)) + return "postgres-access-" + hex.EncodeToString(sum[:])[:16] +} + func (i CreatePostgresAccessInput) accessTTL() (time.Duration, error) { if i.TTL == "" { return defaultPostgresAccessTTL, nil @@ -576,3 +628,235 @@ func WorkloadsForInstance(ctx context.Context, teamSlug slug.Slug, environmentNa return ret } + +func postgresGVR() schema.GroupVersionResource { + return schema.GroupVersionResource{Group: "nais.io", Version: "v1", Resource: "postgres"} +} + +// postgresClient returns a namespaced client for Postgres that writes with the API's own +// identity. Users never write the CRD directly; authorization is decided by the resolver. +func postgresClient(ctx context.Context, environmentName string, teamSlug slug.Slug) (dynamic.ResourceInterface, error) { + client, err := fromContext(ctx).postgresBranchWatcher.SystemAuthenticatedClient(ctx, environmentName, watcher.WithImpersonatedClientGVR(postgresGVR())) + if err != nil { + return nil, err + } + return client.Namespace(teamSlug.String()), nil +} + +func Create(ctx context.Context, input CreatePostgresInput) (*CreatePostgresPayload, error) { + if err := input.Validate(ctx); err != nil { + return nil, err + } + + client, err := postgresClient(ctx, input.EnvironmentName, input.TeamSlug) + if err != nil { + return nil, err + } + + pg := &nais_io_v1.Postgres{ + TypeMeta: metav1.TypeMeta{Kind: "Postgres", APIVersion: "nais.io/v1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: input.Name, + Namespace: input.TeamSlug.String(), + }, + Spec: nais_io_v1.PostgresSpec{MajorVersion: input.MajorVersion}, + } + pg.SetAnnotations(kubernetes.WithCommonAnnotations(nil, authz.ActorFromContext(ctx).User.Identity())) + kubernetes.SetManagedByConsoleLabel(pg) + if input.HighAvailability != nil { + pg.Spec.HighAvailability = *input.HighAvailability + } + if err := applyResources(&pg.Spec.Resources, input.CPU, input.Memory, input.DiskSize); err != nil { + return nil, err + } + + obj, err := toUnstructuredWithoutZeroResources(pg) + if err != nil { + return nil, err + } + + created, err := client.Create(ctx, obj, metav1.CreateOptions{}) + if err != nil { + if k8serrors.IsAlreadyExists(err) { + return nil, apierror.ErrAlreadyExists + } + return nil, err + } + + if err := logPostgresChange(ctx, activitylog.ActivityLogEntryActionCreated, input.Name, input.EnvironmentName, input.TeamSlug, nil); err != nil { + return nil, err + } + + ret, err := toPostgres(created, input.EnvironmentName) + if err != nil { + return nil, err + } + return &CreatePostgresPayload{Postgres: ret}, nil +} + +func Update(ctx context.Context, input UpdatePostgresInput) (*UpdatePostgresPayload, error) { + if err := input.Validate(ctx); err != nil { + return nil, err + } + + client, err := postgresClient(ctx, input.EnvironmentName, input.TeamSlug) + if err != nil { + return nil, err + } + + existing, err := client.Get(ctx, input.Name, metav1.GetOptions{}) + if k8serrors.IsNotFound(err) { + return nil, apierror.Errorf("Postgres %q not found.", input.Name) + } + if err != nil { + return nil, err + } + + pg, err := kubernetes.ToConcrete[nais_io_v1.Postgres](existing) + if err != nil { + return nil, err + } + + var changes []*PostgresUpdatedActivityLogEntryDataUpdatedField + for _, f := range []func(*nais_io_v1.Postgres, UpdatePostgresInput) ([]*PostgresUpdatedActivityLogEntryDataUpdatedField, error){ + updateHighAvailability, + updateResources, + } { + res, err := f(pg, input) + if err != nil { + return nil, err + } + changes = append(changes, res...) + } + + if len(changes) == 0 { + ret, err := toPostgres(existing, input.EnvironmentName) + if err != nil { + return nil, err + } + return &UpdatePostgresPayload{Postgres: ret}, nil + } + + obj, err := toUnstructuredWithoutZeroResources(pg) + if err != nil { + return nil, err + } + obj.SetAnnotations(kubernetes.WithCommonAnnotations(obj.GetAnnotations(), authz.ActorFromContext(ctx).User.Identity())) + + updated, err := client.Update(ctx, obj, metav1.UpdateOptions{}) + if err != nil { + return nil, err + } + + if err := logPostgresChange(ctx, activitylog.ActivityLogEntryActionUpdated, input.Name, input.EnvironmentName, input.TeamSlug, PostgresUpdatedActivityLogEntryData{UpdatedFields: changes}); err != nil { + return nil, err + } + + ret, err := toPostgres(updated, input.EnvironmentName) + if err != nil { + return nil, err + } + return &UpdatePostgresPayload{Postgres: ret}, nil +} + +// applyResources sets the quantities that were provided. Omitted values are left as they are, +// so the CRD defaults apply on create and existing values are kept on update. +func applyResources(r *nais_io_v1.PostgresResources, cpu, memory, diskSize *string) error { + set := func(field string, value *string, target *resource.Quantity) error { + if value == nil { + return nil + } + q, err := resource.ParseQuantity(*value) + if err != nil { + return fmt.Errorf("parsing %s: %w", field, err) + } + *target = q + return nil + } + if err := set("cpu", cpu, &r.Cpu); err != nil { + return err + } + if err := set("memory", memory, &r.Memory); err != nil { + return err + } + return set("diskSize", diskSize, &r.DiskSize) +} + +// toUnstructuredWithoutZeroResources converts pg and drops resource quantities that were never +// set. resource.Quantity is a struct, so omitempty does not skip it and it would otherwise be +// sent as "0", which stops the CRD from applying its defaults. +func toUnstructuredWithoutZeroResources(pg *nais_io_v1.Postgres) (*unstructured.Unstructured, error) { + obj, err := kubernetes.ToUnstructured(pg) + if err != nil { + return nil, err + } + for field, q := range map[string]resource.Quantity{ + "cpu": pg.Spec.Resources.Cpu, "memory": pg.Spec.Resources.Memory, "diskSize": pg.Spec.Resources.DiskSize, + } { + if q.IsZero() { + unstructured.RemoveNestedField(obj.Object, "spec", "resources", field) + } + } + if m, found, _ := unstructured.NestedMap(obj.Object, "spec", "resources"); found && len(m) == 0 { + unstructured.RemoveNestedField(obj.Object, "spec", "resources") + } + return obj, nil +} + +func logPostgresChange(ctx context.Context, action activitylog.ActivityLogEntryAction, name, environmentName string, teamSlug slug.Slug, data any) error { + return activitylog.Create(ctx, activitylog.CreateInput{ + Action: action, + Actor: authz.ActorFromContext(ctx).User, + ResourceType: activityLogEntryResourceTypePostgres, + ResourceName: name, + EnvironmentName: new(environmentName), + TeamSlug: new(teamSlug), + Data: data, + }) +} + +func updateHighAvailability(pg *nais_io_v1.Postgres, input UpdatePostgresInput) ([]*PostgresUpdatedActivityLogEntryDataUpdatedField, error) { + if input.HighAvailability == nil || pg.Spec.HighAvailability == *input.HighAvailability { + return nil, nil + } + old := pg.Spec.HighAvailability + pg.Spec.HighAvailability = *input.HighAvailability + return []*PostgresUpdatedActivityLogEntryDataUpdatedField{{ + Field: "highAvailability", + OldValue: new(strconv.FormatBool(old)), + NewValue: new(strconv.FormatBool(pg.Spec.HighAvailability)), + }}, nil +} + +// updateResources applies the quantities that were provided. Omitted values are left as they +// are, and a value that is semantically unchanged (1024Mi vs 1Gi) is not reported as a change. +func updateResources(pg *nais_io_v1.Postgres, input UpdatePostgresInput) ([]*PostgresUpdatedActivityLogEntryDataUpdatedField, error) { + var changes []*PostgresUpdatedActivityLogEntryDataUpdatedField + for _, f := range []struct { + name string + value *string + target *resource.Quantity + }{ + {"cpu", input.CPU, &pg.Spec.Resources.Cpu}, + {"memory", input.Memory, &pg.Spec.Resources.Memory}, + {"diskSize", input.DiskSize, &pg.Spec.Resources.DiskSize}, + } { + if f.value == nil { + continue + } + q, err := resource.ParseQuantity(*f.value) + if err != nil { + return nil, fmt.Errorf("parsing %s: %w", f.name, err) + } + if f.target.Cmp(q) == 0 { + continue + } + change := &PostgresUpdatedActivityLogEntryDataUpdatedField{Field: f.name, NewValue: new(q.String())} + if !f.target.IsZero() { + change.OldValue = new(f.target.String()) + } + *f.target = q + changes = append(changes, change) + } + return changes, nil +} diff --git a/internal/persistence/postgres/queries_test.go b/internal/persistence/postgres/queries_test.go index f7077fe9c..5df4e4751 100644 --- a/internal/persistence/postgres/queries_test.go +++ b/internal/persistence/postgres/queries_test.go @@ -11,6 +11,7 @@ import ( nais_io_v1 "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/util/validation" ) func TestNewPostgresAccessResource(t *testing.T) { @@ -210,3 +211,54 @@ func TestPostgresAccessConnectionDetails(t *testing.T) { }) } } + +func TestPostgresAccessNameIsStablePerUserAndBranch(t *testing.T) { + name := postgresAccessName("user@example.com", "orders-main-abc") + if name != postgresAccessName("user@example.com", "orders-main-abc") { + t.Error("name must be stable, so a second request for the same pair conflicts") + } + for _, other := range []string{ + postgresAccessName("other@example.com", "orders-main-abc"), + postgresAccessName("user@example.com", "orders-recovered-abc"), + // Shifting the boundary between user and branch must not collide. + postgresAccessName("user@example.comorders", "-main-abc"), + } { + if other == name { + t.Errorf("different user/branch produced the same name %q", name) + } + } + if errs := validation.IsDNS1123Subdomain(name); len(errs) > 0 { + t.Errorf("name %q is not a valid resource name: %v", name, errs) + } +} + +func TestOmittedResourcesAreNotSent(t *testing.T) { + cpu := "500m" + pg := &nais_io_v1.Postgres{} + if err := applyResources(&pg.Spec.Resources, &cpu, nil, nil); err != nil { + t.Fatal(err) + } + + obj, err := toUnstructuredWithoutZeroResources(pg) + if err != nil { + t.Fatal(err) + } + + resources, _, _ := unstructured.NestedMap(obj.Object, "spec", "resources") + if got := resources["cpu"]; got != "500m" { + t.Errorf("cpu = %v, want 500m", got) + } + for _, field := range []string{"memory", "diskSize"} { + if v, found := resources[field]; found { + t.Errorf("%s = %v was sent, but must be left out so the CRD default applies", field, v) + } + } + + empty, err := toUnstructuredWithoutZeroResources(&nais_io_v1.Postgres{}) + if err != nil { + t.Fatal(err) + } + if _, found, _ := unstructured.NestedMap(empty.Object, "spec", "resources"); found { + t.Error("spec.resources must be left out entirely when nothing is set") + } +}