From ecdd6254f227aa769323f8a832187f60deec3f80 Mon Sep 17 00:00:00 2001 From: Matt Siwiec Date: Wed, 30 Aug 2023 09:13:58 -0600 Subject: [PATCH] Use new event hooks (#212) * new manual event hooks Signed-off-by: Matt Siwiec * update tests for auth-relationships Signed-off-by: Matt Siwiec * relationships Signed-off-by: Matt Siwiec * add location to manual hook Signed-off-by: Matt Siwiec * finesse manual hook Signed-off-by: Matt Siwiec --------- Signed-off-by: Matt Siwiec --- cmd/serve.go | 2 +- go.mod | 16 +- go.sum | 32 +- internal/ent/entc.go | 5 +- internal/ent/generated/pubsubhooks/pubsub.go | 565 ------------------- internal/ent/schema/loadbalancer.go | 6 +- internal/ent/schema/origin.go | 3 + internal/ent/schema/pool.go | 4 + internal/ent/schema/port.go | 7 +- internal/ent/schema/provider.go | 3 + internal/graphapi/auth_test.go | 22 +- internal/graphapi/loadbalancer_test.go | 24 + internal/graphapi/origin_test.go | 19 + internal/graphapi/owner_resolvers_test.go | 11 + internal/graphapi/pool_test.go | 22 +- internal/graphapi/port_test.go | 20 + internal/manualhooks/hooks.go | 535 ++++++++++-------- x/pubsubinfo/annotation.go | 45 +- x/pubsubinfo/copyright.go | 19 + x/pubsubinfo/doc.go | 18 +- x/pubsubinfo/generator.go | 53 +- x/pubsubinfo/gql_hooks.go | 96 ++++ x/pubsubinfo/json.go | 55 ++ x/pubsubinfo/key_directive.go | 41 ++ x/pubsubinfo/template.go | 26 +- x/pubsubinfo/template/event_hooks.tmpl | 284 ++++++++++ x/pubsubinfo/template/gql_federation.tmpl | 4 + x/pubsubinfo/template/pubsub.tmpl | 291 ---------- x/pubsubinfo/timestamps.go | 86 +++ 29 files changed, 1162 insertions(+), 1152 deletions(-) delete mode 100644 internal/ent/generated/pubsubhooks/pubsub.go create mode 100644 x/pubsubinfo/copyright.go create mode 100644 x/pubsubinfo/gql_hooks.go create mode 100644 x/pubsubinfo/json.go create mode 100644 x/pubsubinfo/key_directive.go create mode 100644 x/pubsubinfo/template/event_hooks.tmpl create mode 100644 x/pubsubinfo/template/gql_federation.tmpl delete mode 100644 x/pubsubinfo/template/pubsub.tmpl create mode 100644 x/pubsubinfo/timestamps.go diff --git a/cmd/serve.go b/cmd/serve.go index 569e0f935..46df6dc1c 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -138,7 +138,7 @@ func serve(ctx context.Context) error { defer client.Close() // TODO: fix generated pubsubhooks - // pubsubhooks.PubsubHooks(client) + // eventhooks.PubsubHooks(client) manualhooks.PubsubHooks(client) diff --git a/go.mod b/go.mod index 1c7ab2876..3e6beee75 100644 --- a/go.mod +++ b/go.mod @@ -22,10 +22,10 @@ require ( github.com/testcontainers/testcontainers-go/modules/postgres v0.21.0 github.com/vektah/gqlparser/v2 v2.5.6 github.com/wundergraph/graphql-go-tools v1.62.3 - go.infratographer.com/permissions-api v0.2.2 - go.infratographer.com/x v0.3.7-0.20230810153653-79f5c2c24800 + go.infratographer.com/permissions-api v0.2.4 + go.infratographer.com/x v0.3.7 go.uber.org/zap v1.25.0 - golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 + golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 ) require ( @@ -116,7 +116,7 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.0 // indirect - github.com/subosito/gotenv v1.4.2 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect github.com/vmihailenco/msgpack/v5 v5.0.0-beta.9 // indirect @@ -144,11 +144,11 @@ require ( golang.org/x/sys v0.11.0 // indirect golang.org/x/text v0.12.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.10.0 // indirect + golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230807174057-1744710a1577 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230807174057-1744710a1577 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577 // indirect + google.golang.org/genproto v0.0.0-20230815205213-6bfd019c3878 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230815205213-6bfd019c3878 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 // indirect google.golang.org/grpc v1.57.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 67031c3a4..e5206e4af 100644 --- a/go.sum +++ b/go.sum @@ -445,8 +445,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= -github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/testcontainers/testcontainers-go v0.21.0 h1:syePAxdeTzfkap+RrJaQZpJQ/s/fsUgn11xIvHrOE9U= github.com/testcontainers/testcontainers-go v0.21.0/go.mod h1:c1ez3WVRHq7T/Aj+X3TIipFBwkBaNT5iNCY8+1b83Ng= github.com/testcontainers/testcontainers-go/modules/postgres v0.21.0 h1:rFPyTR7pPMiHcDktXwd5iZ+mA1cHH/WRa+knxBcY8wU= @@ -479,10 +479,10 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t github.com/zclconf/go-cty v1.8.0 h1:s4AvqaeQzJIu3ndv4gVIhplVD0krU+bgrcLSVUnaWuA= github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -go.infratographer.com/permissions-api v0.2.2 h1:aqgK369fDa3vpSzbBKigHjtG6nNndxQKRyq7VtMp1Z8= -go.infratographer.com/permissions-api v0.2.2/go.mod h1:TupQNHKMcVUIGlLKpNtdwkaGohiL88IL49MMslIT58U= -go.infratographer.com/x v0.3.7-0.20230810153653-79f5c2c24800 h1:uIySwMbfu75kyKIRBsj3ICXRIf/Gmp5cAe73lDDmC9M= -go.infratographer.com/x v0.3.7-0.20230810153653-79f5c2c24800/go.mod h1:AMNcTkqb+yHLCbnZtiiHTC7QvN+4MOpzdOhqHXfKQUk= +go.infratographer.com/permissions-api v0.2.4 h1:s0cwfnygh49r1M7sQILsBtKJb/NQRTm817BRcm1nsLQ= +go.infratographer.com/permissions-api v0.2.4/go.mod h1:ljfVx75plqELqo0K9Y03LfiGzYdtnett5DEnqxoSSFU= +go.infratographer.com/x v0.3.7 h1:kkykoVtC8XrmvC4oZwHWa/15+dv9RhQHgSm8KoEb/Nc= +go.infratographer.com/x v0.3.7/go.mod h1:/zbDM9njbWzUDCA9pkbi1z/v4VZjGsVHx+SPycSgIhg= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -561,8 +561,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 h1:EDuYyU/MkFXllv9QF9819VlI9a4tzGuCbhG0ExK9o1U= -golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -782,8 +782,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= -golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -853,12 +853,12 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20230807174057-1744710a1577 h1:Tyk/35yqszRCvaragTn5NnkY6IiKk/XvHzEWepo71N0= -google.golang.org/genproto v0.0.0-20230807174057-1744710a1577/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= -google.golang.org/genproto/googleapis/api v0.0.0-20230807174057-1744710a1577 h1:xv8KoglAClYGkprUSmDTKaILtzfD8XzG9NYVXMprjKo= -google.golang.org/genproto/googleapis/api v0.0.0-20230807174057-1744710a1577/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577 h1:wukfNtZmZUurLN/atp2hiIeTKn7QJWIQdHzqmsOnAOk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/genproto v0.0.0-20230815205213-6bfd019c3878 h1:Iveh6tGCJkHAjJgEqUQYGDGgbwmhjoAOz8kO/ajxefY= +google.golang.org/genproto v0.0.0-20230815205213-6bfd019c3878/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= +google.golang.org/genproto/googleapis/api v0.0.0-20230815205213-6bfd019c3878 h1:WGq4lvB/mlicysM/dUT3SBvijH4D3sm/Ny1A4wmt2CI= +google.golang.org/genproto/googleapis/api v0.0.0-20230815205213-6bfd019c3878/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 h1:lv6/DhyiFFGsmzxbsUUTOkN29II+zeWHxvT8Lpdxsv0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= diff --git a/internal/ent/entc.go b/internal/ent/entc.go index a114a5d85..90b7e4d8a 100644 --- a/internal/ent/entc.go +++ b/internal/ent/entc.go @@ -17,13 +17,16 @@ import ( func main() { xExt, err := entx.NewExtension( entx.WithFederation(), + // entx.WithEventHooks(), // TODO: untangle additional subjects coupled to auth relationship entx.WithJSONScalar(), ) if err != nil { log.Fatalf("creating entx extension: %v", err) } - pubsubExt, err := pubsubinfo.NewExtension() + pubsubExt, err := pubsubinfo.NewExtension( + pubsubinfo.WithEventHooks(), + ) if err != nil { log.Fatalf("creating pubsubinfo extension: %v", err) } diff --git a/internal/ent/generated/pubsubhooks/pubsub.go b/internal/ent/generated/pubsubhooks/pubsub.go deleted file mode 100644 index 01f495c45..000000000 --- a/internal/ent/generated/pubsubhooks/pubsub.go +++ /dev/null @@ -1,565 +0,0 @@ -// Copyright 2023 The Infratographer Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Code generated by entc, DO NOT EDIT. - -package pubsubhooks - -import ( - "context" - "fmt" - "time" - - "entgo.io/ent" - "go.infratographer.com/load-balancer-api/internal/ent/generated" - "go.infratographer.com/load-balancer-api/internal/ent/generated/hook" - "go.infratographer.com/load-balancer-api/internal/ent/schema" - "go.infratographer.com/x/events" - "go.infratographer.com/x/gidx" - "golang.org/x/exp/slices" -) - -func LoadBalancerHooks() []ent.Hook { - return []ent.Hook{ - hook.On( - func(next ent.Mutator) ent.Mutator { - return hook.LoadBalancerFunc(func(ctx context.Context, m *generated.LoadBalancerMutation) (ent.Value, error) { - // complete the mutation before we process the event - retValue, err := next.Mutate(ctx, m) - if err != nil { - return retValue, err - } - - // queueName := "" - additionalSubjects := []gidx.PrefixedID{} - - objID, ok := m.ID() - if !ok { - return nil, fmt.Errorf("object doesn't have an id %s", objID) - } - - changeset := []events.FieldChange{} - cv_created_at := "" - created_at, ok := m.CreatedAt() - - if ok { - cv_created_at = created_at.Format(time.RFC3339) - pv_created_at := "" - if !m.Op().Is(ent.OpCreate) { - ov, err := m.OldCreatedAt(ctx) - if err != nil { - pv_created_at = "" - } else { - pv_created_at = ov.Format(time.RFC3339) - } - } - - changeset = append(changeset, events.FieldChange{ - Field: "created_at", - PreviousValue: pv_created_at, - CurrentValue: cv_created_at, - }) - } - - cv_updated_at := "" - updated_at, ok := m.UpdatedAt() - - if ok { - cv_updated_at = updated_at.Format(time.RFC3339) - pv_updated_at := "" - if !m.Op().Is(ent.OpCreate) { - ov, err := m.OldUpdatedAt(ctx) - if err != nil { - pv_updated_at = "" - } else { - pv_updated_at = ov.Format(time.RFC3339) - } - } - - changeset = append(changeset, events.FieldChange{ - Field: "updated_at", - PreviousValue: pv_updated_at, - CurrentValue: cv_updated_at, - }) - } - - cv_name := "" - name, ok := m.Name() - - if ok { - cv_name = fmt.Sprintf("%s", fmt.Sprint(name)) - pv_name := "" - if !m.Op().Is(ent.OpCreate) { - ov, err := m.OldName(ctx) - if err != nil { - pv_name = "" - } else { - pv_name = fmt.Sprintf("%s", fmt.Sprint(ov)) - } - } - - changeset = append(changeset, events.FieldChange{ - Field: "name", - PreviousValue: pv_name, - CurrentValue: cv_name, - }) - } - - cv_owner_id := "" - owner_id, ok := m.OwnerID() - if !ok && !m.Op().Is(ent.OpCreate) { - // since we are doing an update or delete and these fields didn't change, load the "old" value - owner_id, err = m.OldOwnerID(ctx) - if err != nil { - return nil, err - } - } - additionalSubjects = append(additionalSubjects, owner_id) - - if ok { - cv_owner_id = fmt.Sprintf("%s", fmt.Sprint(owner_id)) - pv_owner_id := "" - if !m.Op().Is(ent.OpCreate) { - ov, err := m.OldOwnerID(ctx) - if err != nil { - pv_owner_id = "" - } else { - pv_owner_id = fmt.Sprintf("%s", fmt.Sprint(ov)) - } - } - - changeset = append(changeset, events.FieldChange{ - Field: "owner_id", - PreviousValue: pv_owner_id, - CurrentValue: cv_owner_id, - }) - } - - cv_location_id := "" - location_id, ok := m.LocationID() - if !ok && !m.Op().Is(ent.OpCreate) { - // since we are doing an update or delete and these fields didn't change, load the "old" value - location_id, err = m.OldLocationID(ctx) - if err != nil { - return nil, err - } - } - additionalSubjects = append(additionalSubjects, location_id) - - if ok { - cv_location_id = fmt.Sprintf("%s", fmt.Sprint(location_id)) - pv_location_id := "" - if !m.Op().Is(ent.OpCreate) { - ov, err := m.OldLocationID(ctx) - if err != nil { - pv_location_id = "" - } else { - pv_location_id = fmt.Sprintf("%s", fmt.Sprint(ov)) - } - } - - changeset = append(changeset, events.FieldChange{ - Field: "location_id", - PreviousValue: pv_location_id, - CurrentValue: cv_location_id, - }) - } - - cv_provider_id := "" - provider_id, ok := m.ProviderID() - if !ok && !m.Op().Is(ent.OpCreate) { - // since we are doing an update or delete and these fields didn't change, load the "old" value - provider_id, err = m.OldProviderID(ctx) - if err != nil { - return nil, err - } - } - additionalSubjects = append(additionalSubjects, provider_id) - - if ok { - cv_provider_id = fmt.Sprintf("%s", fmt.Sprint(provider_id)) - pv_provider_id := "" - if !m.Op().Is(ent.OpCreate) { - ov, err := m.OldProviderID(ctx) - if err != nil { - pv_provider_id = "" - } else { - pv_provider_id = fmt.Sprintf("%s", fmt.Sprint(ov)) - } - } - - changeset = append(changeset, events.FieldChange{ - Field: "provider_id", - PreviousValue: pv_provider_id, - CurrentValue: cv_provider_id, - }) - } - - msg := events.ChangeMessage{ - EventType: eventType(m.Op()), - SubjectID: objID, - AdditionalSubjectIDs: additionalSubjects, - Timestamp: time.Now().UTC(), - FieldChanges: changeset, - } - - lb_lookup := getLocation(ctx, objID, additionalSubjects) - if lb_lookup != "" { - lb, err := m.Client().LoadBalancer.Get(ctx, lb_lookup) - if err != nil { - return nil, fmt.Errorf("unable to lookup location %s", lb_lookup) - } - - if !slices.Contains(additionalSubjects, lb.LocationID) { - additionalSubjects = append(additionalSubjects, lb.LocationID) - msg.AdditionalSubjectIDs = additionalSubjects - } - } - - m.EventsPublisher.PublishChange(ctx, eventSubject(objID), msg) - - return retValue, nil - }) - }, - ent.OpCreate|ent.OpUpdate|ent.OpUpdateOne, - ), - - // Delete Hook - hook.On( - func(next ent.Mutator) ent.Mutator { - return hook.LoadBalancerFunc(func(ctx context.Context, m *generated.LoadBalancerMutation) (ent.Value, error) { - // queueName := "" - additionalSubjects := []gidx.PrefixedID{} - - objID, ok := m.ID() - if !ok { - return nil, fmt.Errorf("object doesn't have an id %s", objID) - } - - dbObj, err := m.Client().LoadBalancer.Get(ctx, objID) - if err != nil { - return nil, fmt.Errorf("failed to load object to get values for pubsub, err %w", err) - } - - additionalSubjects = append(additionalSubjects, dbObj.OwnerID) - - additionalSubjects = append(additionalSubjects, dbObj.LocationID) - - additionalSubjects = append(additionalSubjects, dbObj.ProviderID) - - msg := events.ChangeMessage{ - EventType: eventType(m.Op()), - SubjectID: objID, - AdditionalSubjectIDs: additionalSubjects, - Timestamp: time.Now().UTC(), - } - - lb_lookup := getLocation(ctx, objID, additionalSubjects) - if lb_lookup != "" { - lb, err := m.Client().LoadBalancer.Get(ctx, lb_lookup) - if err != nil { - return nil, fmt.Errorf("unable to lookup location %s", lb_lookup) - } - - if !slices.Contains(additionalSubjects, lb.LocationID) { - additionalSubjects = append(additionalSubjects, lb.LocationID) - msg.AdditionalSubjectIDs = additionalSubjects - } - } - - // we have all the info we need, now complete the mutation before we process the event - retValue, err := next.Mutate(ctx, m) - if err != nil { - return retValue, err - } - - m.EventsPublisher.PublishChange(ctx, eventSubject(objID), msg) - - return retValue, nil - }) - }, - ent.OpDelete|ent.OpDeleteOne, - ), - } -} -func PortHooks() []ent.Hook { - return []ent.Hook{ - hook.On( - func(next ent.Mutator) ent.Mutator { - return hook.PortFunc(func(ctx context.Context, m *generated.PortMutation) (ent.Value, error) { - // complete the mutation before we process the event - retValue, err := next.Mutate(ctx, m) - if err != nil { - return retValue, err - } - - // queueName := "" - additionalSubjects := []gidx.PrefixedID{} - - objID, ok := m.ID() - if !ok { - return nil, fmt.Errorf("object doesn't have an id %s", objID) - } - - changeset := []events.FieldChange{} - cv_created_at := "" - created_at, ok := m.CreatedAt() - - if ok { - cv_created_at = created_at.Format(time.RFC3339) - pv_created_at := "" - if !m.Op().Is(ent.OpCreate) { - ov, err := m.OldCreatedAt(ctx) - if err != nil { - pv_created_at = "" - } else { - pv_created_at = ov.Format(time.RFC3339) - } - } - - changeset = append(changeset, events.FieldChange{ - Field: "created_at", - PreviousValue: pv_created_at, - CurrentValue: cv_created_at, - }) - } - - cv_updated_at := "" - updated_at, ok := m.UpdatedAt() - - if ok { - cv_updated_at = updated_at.Format(time.RFC3339) - pv_updated_at := "" - if !m.Op().Is(ent.OpCreate) { - ov, err := m.OldUpdatedAt(ctx) - if err != nil { - pv_updated_at = "" - } else { - pv_updated_at = ov.Format(time.RFC3339) - } - } - - changeset = append(changeset, events.FieldChange{ - Field: "updated_at", - PreviousValue: pv_updated_at, - CurrentValue: cv_updated_at, - }) - } - - cv_number := "" - number, ok := m.Number() - - if ok { - cv_number = fmt.Sprintf("%s", fmt.Sprint(number)) - pv_number := "" - if !m.Op().Is(ent.OpCreate) { - ov, err := m.OldNumber(ctx) - if err != nil { - pv_number = "" - } else { - pv_number = fmt.Sprintf("%s", fmt.Sprint(ov)) - } - } - - changeset = append(changeset, events.FieldChange{ - Field: "number", - PreviousValue: pv_number, - CurrentValue: cv_number, - }) - } - - cv_name := "" - name, ok := m.Name() - - if ok { - cv_name = fmt.Sprintf("%s", fmt.Sprint(name)) - pv_name := "" - if !m.Op().Is(ent.OpCreate) { - ov, err := m.OldName(ctx) - if err != nil { - pv_name = "" - } else { - pv_name = fmt.Sprintf("%s", fmt.Sprint(ov)) - } - } - - changeset = append(changeset, events.FieldChange{ - Field: "name", - PreviousValue: pv_name, - CurrentValue: cv_name, - }) - } - - cv_load_balancer_id := "" - load_balancer_id, ok := m.LoadBalancerID() - if !ok && !m.Op().Is(ent.OpCreate) { - // since we are doing an update or delete and these fields didn't change, load the "old" value - load_balancer_id, err = m.OldLoadBalancerID(ctx) - if err != nil { - return nil, err - } - } - additionalSubjects = append(additionalSubjects, load_balancer_id) - - if ok { - cv_load_balancer_id = fmt.Sprintf("%s", fmt.Sprint(load_balancer_id)) - pv_load_balancer_id := "" - if !m.Op().Is(ent.OpCreate) { - ov, err := m.OldLoadBalancerID(ctx) - if err != nil { - pv_load_balancer_id = "" - } else { - pv_load_balancer_id = fmt.Sprintf("%s", fmt.Sprint(ov)) - } - } - - changeset = append(changeset, events.FieldChange{ - Field: "load_balancer_id", - PreviousValue: pv_load_balancer_id, - CurrentValue: cv_load_balancer_id, - }) - } - - msg := events.ChangeMessage{ - EventType: eventType(m.Op()), - SubjectID: objID, - AdditionalSubjectIDs: additionalSubjects, - Timestamp: time.Now().UTC(), - FieldChanges: changeset, - } - - lb_lookup := getLocation(ctx, objID, additionalSubjects) - if lb_lookup != "" { - lb, err := m.Client().LoadBalancer.Get(ctx, lb_lookup) - if err != nil { - return nil, fmt.Errorf("unable to lookup location %s", lb_lookup) - } - - if !slices.Contains(additionalSubjects, lb.LocationID) { - additionalSubjects = append(additionalSubjects, lb.LocationID) - msg.AdditionalSubjectIDs = additionalSubjects - } - } - - m.EventsPublisher.PublishChange(ctx, eventSubject(objID), msg) - - return retValue, nil - }) - }, - ent.OpCreate|ent.OpUpdate|ent.OpUpdateOne, - ), - - // Delete Hook - hook.On( - func(next ent.Mutator) ent.Mutator { - return hook.PortFunc(func(ctx context.Context, m *generated.PortMutation) (ent.Value, error) { - // queueName := "" - additionalSubjects := []gidx.PrefixedID{} - - objID, ok := m.ID() - if !ok { - return nil, fmt.Errorf("object doesn't have an id %s", objID) - } - - dbObj, err := m.Client().Port.Get(ctx, objID) - if err != nil { - return nil, fmt.Errorf("failed to load object to get values for pubsub, err %w", err) - } - - additionalSubjects = append(additionalSubjects, dbObj.LoadBalancerID) - - msg := events.ChangeMessage{ - EventType: eventType(m.Op()), - SubjectID: objID, - AdditionalSubjectIDs: additionalSubjects, - Timestamp: time.Now().UTC(), - } - - lb_lookup := getLocation(ctx, objID, additionalSubjects) - if lb_lookup != "" { - lb, err := m.Client().LoadBalancer.Get(ctx, lb_lookup) - if err != nil { - return nil, fmt.Errorf("unable to lookup location %s", lb_lookup) - } - - if !slices.Contains(additionalSubjects, lb.LocationID) { - additionalSubjects = append(additionalSubjects, lb.LocationID) - msg.AdditionalSubjectIDs = additionalSubjects - } - } - - // we have all the info we need, now complete the mutation before we process the event - retValue, err := next.Mutate(ctx, m) - if err != nil { - return retValue, err - } - - m.EventsPublisher.PublishChange(ctx, eventSubject(objID), msg) - - return retValue, nil - }) - }, - ent.OpDelete|ent.OpDeleteOne, - ), - } -} - -func PubsubHooks(c *generated.Client) { - c.LoadBalancer.Use(LoadBalancerHooks()...) - - c.Port.Use(PortHooks()...) - -} - -func eventType(op ent.Op) string { - switch op { - case ent.OpCreate: - return "create" - case ent.OpUpdate, ent.OpUpdateOne: - return "update" - case ent.OpDelete, ent.OpDeleteOne: - return "delete" - default: - return "unknown" - } -} - -func eventSubject(objID gidx.PrefixedID) string { - switch objID.Prefix() { - case schema.LoadBalancerPrefix: - return "load-balancer" - case schema.PortPrefix: - return "load-balancer-port" - case schema.OriginPrefix: - return "load-balancer-origin" - case schema.PoolPrefix: - return "load-balancer-pool" - default: - return "unknown" - } -} - -func getLocation(ctx context.Context, id gidx.PrefixedID, addID []gidx.PrefixedID) gidx.PrefixedID { - if id.Prefix() == schema.LoadBalancerPrefix { - return id - } - - for _, id := range addID { - if id.Prefix() == schema.LoadBalancerPrefix { - return id - } - } - - return "" -} diff --git a/internal/ent/schema/loadbalancer.go b/internal/ent/schema/loadbalancer.go index bffa7031f..c9a11bbc3 100644 --- a/internal/ent/schema/loadbalancer.go +++ b/internal/ent/schema/loadbalancer.go @@ -56,7 +56,7 @@ func (LoadBalancer) Fields() []ent.Field { entgql.Type("ID"), entgql.Skip(entgql.SkipWhereInput, entgql.SkipMutationUpdateInput, entgql.SkipType), entgql.OrderField("OWNER"), - pubsubinfo.AdditionalSubject(), + pubsubinfo.EventsHookAdditionalSubject("owner"), ), field.String("location_id"). GoType(gidx.PrefixedID("")). @@ -66,7 +66,6 @@ func (LoadBalancer) Fields() []ent.Field { Annotations( entgql.Type("ID"), entgql.Skip(^entgql.SkipMutationCreateInput), - pubsubinfo.AdditionalSubject(), ), field.String("provider_id"). GoType(gidx.PrefixedID("")). @@ -76,7 +75,6 @@ func (LoadBalancer) Fields() []ent.Field { Annotations( entgql.Type("ID"), entgql.Skip(^entgql.SkipMutationCreateInput), - pubsubinfo.AdditionalSubject(), ), } } @@ -114,7 +112,7 @@ func (LoadBalancer) Indexes() []ent.Index { func (LoadBalancer) Annotations() []schema.Annotation { return []schema.Annotation{ entx.GraphKeyDirective("id"), - pubsubinfo.Annotation{}, + pubsubinfo.EventsHookSubjectName("load-balancer"), schema.Comment("Representation of a load balancer."), prefixIDDirective(LoadBalancerPrefix), entgql.Implements("IPAddressable"), diff --git a/internal/ent/schema/origin.go b/internal/ent/schema/origin.go index 9eab0f6de..66d38755c 100644 --- a/internal/ent/schema/origin.go +++ b/internal/ent/schema/origin.go @@ -12,6 +12,7 @@ import ( "go.infratographer.com/x/gidx" "go.infratographer.com/load-balancer-api/internal/ent/schema/validations" + "go.infratographer.com/load-balancer-api/x/pubsubinfo" ) // Origin holds the schema definition for the Origin entity. @@ -64,6 +65,7 @@ func (Origin) Fields() []ent.Field { Annotations( entgql.Type("ID"), entgql.Skip(entgql.SkipWhereInput, entgql.SkipMutationUpdateInput), + pubsubinfo.EventsHookAdditionalSubject("pool"), ), } } @@ -91,6 +93,7 @@ func (Origin) Indexes() []ent.Index { func (Origin) Annotations() []schema.Annotation { return []schema.Annotation{ entx.GraphKeyDirective("id"), + pubsubinfo.EventsHookSubjectName("load-balancer-origin"), entgql.Type("LoadBalancerOrigin"), prefixIDDirective(OriginPrefix), entgql.RelayConnection(), diff --git a/internal/ent/schema/pool.go b/internal/ent/schema/pool.go index 76c0cc19f..98432e28d 100644 --- a/internal/ent/schema/pool.go +++ b/internal/ent/schema/pool.go @@ -10,6 +10,8 @@ import ( "go.infratographer.com/x/entx" "go.infratographer.com/x/gidx" + + "go.infratographer.com/load-balancer-api/x/pubsubinfo" ) // Pool holds the schema definition for the Pool entity. @@ -49,6 +51,7 @@ func (Pool) Fields() []ent.Field { Annotations( entgql.Type("ID"), entgql.Skip(entgql.SkipWhereInput, entgql.SkipMutationUpdateInput), + pubsubinfo.EventsHookAdditionalSubject("owner"), ), } } @@ -76,6 +79,7 @@ func (Pool) Indexes() []ent.Index { func (Pool) Annotations() []schema.Annotation { return []schema.Annotation{ entx.GraphKeyDirective("id"), + pubsubinfo.EventsHookSubjectName("load-balancer-pool"), entgql.Type("LoadBalancerPool"), prefixIDDirective(PoolPrefix), entgql.RelayConnection(), diff --git a/internal/ent/schema/port.go b/internal/ent/schema/port.go index 288cdbefe..b13e64c2f 100644 --- a/internal/ent/schema/port.go +++ b/internal/ent/schema/port.go @@ -8,10 +8,10 @@ import ( "entgo.io/ent/schema/field" "entgo.io/ent/schema/index" - "go.infratographer.com/load-balancer-api/x/pubsubinfo" - "go.infratographer.com/x/entx" "go.infratographer.com/x/gidx" + + "go.infratographer.com/load-balancer-api/x/pubsubinfo" ) const ( @@ -57,7 +57,6 @@ func (Port) Fields() []ent.Field { Annotations( entgql.Type("ID"), entgql.Skip(entgql.SkipWhereInput, entgql.SkipMutationUpdateInput), - pubsubinfo.AdditionalSubject(), ), } } @@ -91,6 +90,6 @@ func (Port) Annotations() []schema.Annotation { prefixIDDirective(PortPrefix), entgql.RelayConnection(), entgql.Mutations(entgql.MutationCreate(), entgql.MutationUpdate()), - pubsubinfo.Annotation{}, + pubsubinfo.EventsHookSubjectName("load-balancer-port"), } } diff --git a/internal/ent/schema/provider.go b/internal/ent/schema/provider.go index a486b3bdb..2b2d9ce52 100644 --- a/internal/ent/schema/provider.go +++ b/internal/ent/schema/provider.go @@ -9,6 +9,8 @@ import ( "entgo.io/ent/schema/index" "go.infratographer.com/x/entx" "go.infratographer.com/x/gidx" + + "go.infratographer.com/load-balancer-api/x/pubsubinfo" ) // Provider holds the schema definition for the LoadBalancerProvider entity. @@ -51,6 +53,7 @@ func (Provider) Fields() []ent.Field { entgql.Type("ID"), entgql.Skip(entgql.SkipWhereInput, entgql.SkipMutationUpdateInput, entgql.SkipType), entgql.OrderField("OWNER"), + pubsubinfo.EventsHookAdditionalSubject("owner"), ), } } diff --git a/internal/graphapi/auth_test.go b/internal/graphapi/auth_test.go index 9dae15a2b..cae7e2125 100644 --- a/internal/graphapi/auth_test.go +++ b/internal/graphapi/auth_test.go @@ -6,8 +6,10 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go.infratographer.com/permissions-api/pkg/permissions" + "go.infratographer.com/permissions-api/pkg/permissions/mockpermissions" "go.infratographer.com/x/echojwtx" "go.infratographer.com/x/testing/auth" ) @@ -17,6 +19,15 @@ func TestJWTEnabledLoadbalancerGETWithAuthClient(t *testing.T) { oauthCLI, issuer, oAuthClose := auth.OAuthTestClient("urn:test:loadbalancer", "") defer oAuthClose() + ctx := context.Background() + perms := new(mockpermissions.MockPermissions) + perms.On("CreateAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + ctx = perms.ContextWithHandler(ctx) + + // Permit request + ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) + srv, err := newTestServer( withAuthConfig( &echojwtx.AuthConfig{ @@ -33,7 +44,6 @@ func TestJWTEnabledLoadbalancerGETWithAuthClient(t *testing.T) { defer srv.Close() - ctx := context.Background() lb1 := (&LoadBalancerBuilder{}).MustNew(ctx) resp, err := graphTestClient( @@ -50,6 +60,15 @@ func TestJWTENabledLoadbalancerGETWithDefaultClient(t *testing.T) { _, issuer, oAuthClose := auth.OAuthTestClient("urn:test:loadbalancer", "") defer oAuthClose() + ctx := context.Background() + perms := new(mockpermissions.MockPermissions) + perms.On("CreateAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + ctx = perms.ContextWithHandler(ctx) + + // Permit request + ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) + srv, err := newTestServer( withAuthConfig( &echojwtx.AuthConfig{ @@ -66,7 +85,6 @@ func TestJWTENabledLoadbalancerGETWithDefaultClient(t *testing.T) { defer srv.Close() - ctx := context.Background() lb1 := (&LoadBalancerBuilder{}).MustNew(ctx) resp, err := graphTestClient( diff --git a/internal/graphapi/loadbalancer_test.go b/internal/graphapi/loadbalancer_test.go index 4628169c4..e1dde2e9e 100644 --- a/internal/graphapi/loadbalancer_test.go +++ b/internal/graphapi/loadbalancer_test.go @@ -6,8 +6,10 @@ import ( "github.com/brianvoe/gofakeit/v6" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go.infratographer.com/permissions-api/pkg/permissions" + "go.infratographer.com/permissions-api/pkg/permissions/mockpermissions" "go.infratographer.com/x/gidx" ent "go.infratographer.com/load-balancer-api/internal/ent/generated" @@ -16,6 +18,10 @@ import ( func TestQuery_loadBalancer(t *testing.T) { ctx := context.Background() + perms := new(mockpermissions.MockPermissions) + perms.On("CreateAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + ctx = perms.ContextWithHandler(ctx) // Permit request ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) @@ -71,6 +77,10 @@ func TestQuery_loadBalancer(t *testing.T) { func TestCreate_loadBalancer(t *testing.T) { ctx := context.Background() + perms := new(mockpermissions.MockPermissions) + perms.On("CreateAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + ctx = perms.ContextWithHandler(ctx) // Permit request ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) @@ -144,6 +154,10 @@ func TestCreate_loadBalancer(t *testing.T) { func TestUpdate_loadBalancer(t *testing.T) { ctx := context.Background() + perms := new(mockpermissions.MockPermissions) + perms.On("CreateAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + ctx = perms.ContextWithHandler(ctx) // Permit request ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) @@ -200,6 +214,11 @@ func TestUpdate_loadBalancer(t *testing.T) { func TestDelete_loadBalancer(t *testing.T) { ctx := context.Background() + perms := new(mockpermissions.MockPermissions) + perms.On("CreateAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + perms.On("DeleteAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + ctx = perms.ContextWithHandler(ctx) // Permit request ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) @@ -253,6 +272,11 @@ func TestDelete_loadBalancer(t *testing.T) { func TestFullLoadBalancerLifecycle(t *testing.T) { ctx := context.Background() + perms := new(mockpermissions.MockPermissions) + perms.On("CreateAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + perms.On("DeleteAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + ctx = perms.ContextWithHandler(ctx) // Permit request ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) diff --git a/internal/graphapi/origin_test.go b/internal/graphapi/origin_test.go index 56508886b..c9984f724 100644 --- a/internal/graphapi/origin_test.go +++ b/internal/graphapi/origin_test.go @@ -5,9 +5,11 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go.infratographer.com/permissions-api/pkg/permissions" + "go.infratographer.com/permissions-api/pkg/permissions/mockpermissions" "go.infratographer.com/x/gidx" ent "go.infratographer.com/load-balancer-api/internal/ent/generated" @@ -16,6 +18,10 @@ import ( func TestQueryPoolOrigin(t *testing.T) { ctx := context.Background() + perms := new(mockpermissions.MockPermissions) + perms.On("CreateAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + ctx = perms.ContextWithHandler(ctx) // Permit request ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) @@ -76,6 +82,10 @@ func TestQueryPoolOrigin(t *testing.T) { func TestMutate_OriginCreate(t *testing.T) { ctx := context.Background() + perms := new(mockpermissions.MockPermissions) + perms.On("CreateAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + ctx = perms.ContextWithHandler(ctx) // Permit request ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) @@ -186,6 +196,10 @@ func TestMutate_OriginCreate(t *testing.T) { func TestMutate_OriginUpdate(t *testing.T) { ctx := context.Background() + perms := new(mockpermissions.MockPermissions) + perms.On("CreateAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + ctx = perms.ContextWithHandler(ctx) // Permit request ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) @@ -272,6 +286,11 @@ func TestMutate_OriginUpdate(t *testing.T) { func TestMutate_OriginDelete(t *testing.T) { ctx := context.Background() + perms := new(mockpermissions.MockPermissions) + perms.On("CreateAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + perms.On("DeleteAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + ctx = perms.ContextWithHandler(ctx) // Permit request ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) diff --git a/internal/graphapi/owner_resolvers_test.go b/internal/graphapi/owner_resolvers_test.go index 1614d6bcb..fa37219b9 100644 --- a/internal/graphapi/owner_resolvers_test.go +++ b/internal/graphapi/owner_resolvers_test.go @@ -5,7 +5,10 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "go.infratographer.com/permissions-api/pkg/permissions" + "go.infratographer.com/permissions-api/pkg/permissions/mockpermissions" "go.infratographer.com/x/gidx" ent "go.infratographer.com/load-balancer-api/internal/ent/generated" @@ -14,6 +17,14 @@ import ( func TestOwnerLoadBalancersResolver(t *testing.T) { ctx := context.Background() + perms := new(mockpermissions.MockPermissions) + perms.On("CreateAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + ctx = perms.ContextWithHandler(ctx) + + // Permit request + ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) + ownerID := gidx.MustNewID("testtnt") lb1 := (&LoadBalancerBuilder{OwnerID: ownerID, LocationID: "testloc-CCCafdsaf", Name: "lb-a"}).MustNew(ctx) lb2 := (&LoadBalancerBuilder{OwnerID: ownerID, LocationID: "testloc-AAAfasdf", Name: "lb-c"}).MustNew(ctx) diff --git a/internal/graphapi/pool_test.go b/internal/graphapi/pool_test.go index 5a8aeadb3..fe75e365e 100644 --- a/internal/graphapi/pool_test.go +++ b/internal/graphapi/pool_test.go @@ -5,8 +5,10 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go.infratographer.com/permissions-api/pkg/permissions" + "go.infratographer.com/permissions-api/pkg/permissions/mockpermissions" "go.infratographer.com/x/gidx" ent "go.infratographer.com/load-balancer-api/internal/ent/generated" @@ -16,6 +18,10 @@ import ( func TestQueryPool(t *testing.T) { ctx := context.Background() + perms := new(mockpermissions.MockPermissions) + perms.On("CreateAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + ctx = perms.ContextWithHandler(ctx) // Permit request ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) @@ -72,12 +78,17 @@ func TestQueryPool(t *testing.T) { } func TestMutate_PoolCreate(t *testing.T) { - ownerID := gidx.MustNewID(ownerPrefix) ctx := context.Background() + perms := new(mockpermissions.MockPermissions) + perms.On("CreateAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + ctx = perms.ContextWithHandler(ctx) // Permit request ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) + ownerID := gidx.MustNewID(ownerPrefix) + testCases := []struct { TestName string Input graphclient.CreateLoadBalancerPoolInput @@ -154,6 +165,10 @@ func TestMutate_PoolCreate(t *testing.T) { func TestMutate_PoolUpdate(t *testing.T) { ctx := context.Background() + perms := new(mockpermissions.MockPermissions) + perms.On("CreateAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + ctx = perms.ContextWithHandler(ctx) // Permit request ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) @@ -226,6 +241,11 @@ func TestMutate_PoolUpdate(t *testing.T) { func TestMutate_PoolDelete(t *testing.T) { ctx := context.Background() + perms := new(mockpermissions.MockPermissions) + perms.On("CreateAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + perms.On("DeleteAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + ctx = perms.ContextWithHandler(ctx) // Permit request ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) diff --git a/internal/graphapi/port_test.go b/internal/graphapi/port_test.go index 689a53c1c..87f3d4ad0 100644 --- a/internal/graphapi/port_test.go +++ b/internal/graphapi/port_test.go @@ -6,9 +6,11 @@ import ( "github.com/brianvoe/gofakeit/v6" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go.infratographer.com/permissions-api/pkg/permissions" + "go.infratographer.com/permissions-api/pkg/permissions/mockpermissions" "go.infratographer.com/x/gidx" "go.infratographer.com/load-balancer-api/internal/graphclient" @@ -16,6 +18,10 @@ import ( func TestCreate_LoadbalancerPort(t *testing.T) { ctx := context.Background() + perms := new(mockpermissions.MockPermissions) + perms.On("CreateAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + ctx = perms.ContextWithHandler(ctx) // Permit request ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) @@ -119,6 +125,10 @@ func TestCreate_LoadbalancerPort(t *testing.T) { func TestUpdate_LoadbalancerPort(t *testing.T) { ctx := context.Background() + perms := new(mockpermissions.MockPermissions) + perms.On("CreateAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + ctx = perms.ContextWithHandler(ctx) // Permit request ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) @@ -210,6 +220,11 @@ func TestUpdate_LoadbalancerPort(t *testing.T) { func TestDelete_LoadbalancerPort(t *testing.T) { ctx := context.Background() + perms := new(mockpermissions.MockPermissions) + perms.On("CreateAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + perms.On("DeleteAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + ctx = perms.ContextWithHandler(ctx) // Permit request ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) @@ -265,6 +280,11 @@ func TestDelete_LoadbalancerPort(t *testing.T) { func TestFullLoadBalancerPortLifecycle(t *testing.T) { ctx := context.Background() + perms := new(mockpermissions.MockPermissions) + perms.On("CreateAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + perms.On("DeleteAuthRelationships", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + ctx = perms.ContextWithHandler(ctx) // Permit request ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) diff --git a/internal/manualhooks/hooks.go b/internal/manualhooks/hooks.go index e867f2c63..5ba2545d7 100644 --- a/internal/manualhooks/hooks.go +++ b/internal/manualhooks/hooks.go @@ -6,6 +6,10 @@ import ( "time" "entgo.io/ent" + "go.infratographer.com/permissions-api/pkg/permissions" + "go.infratographer.com/x/events" + "go.infratographer.com/x/gidx" + "golang.org/x/exp/slices" "go.infratographer.com/load-balancer-api/internal/ent/generated" "go.infratographer.com/load-balancer-api/internal/ent/generated/hook" @@ -13,11 +17,6 @@ import ( "go.infratographer.com/load-balancer-api/internal/ent/generated/origin" "go.infratographer.com/load-balancer-api/internal/ent/generated/pool" "go.infratographer.com/load-balancer-api/internal/ent/generated/port" - - "go.infratographer.com/x/events" - "go.infratographer.com/x/gidx" - "golang.org/x/exp/slices" - "go.infratographer.com/load-balancer-api/internal/ent/schema" ) @@ -26,29 +25,16 @@ func LoadBalancerHooks() []ent.Hook { hook.On( func(next ent.Mutator) ent.Mutator { return hook.LoadBalancerFunc(func(ctx context.Context, m *generated.LoadBalancerMutation) (ent.Value, error) { - // complete the mutation before we process the event - retValue, err := next.Mutate(ctx, m) - if err != nil { - return retValue, err - } - + var err error additionalSubjects := []gidx.PrefixedID{} + relationships := []events.AuthRelationshipRelation{} objID, ok := m.ID() if !ok { return nil, fmt.Errorf("object doesn't have an id %s", objID) } - addSubjPortLoadBalancerID, err := m.Client().Port.Query().Where(port.HasLoadBalancerWith(loadbalancer.IDEQ(objID))).Only(ctx) - if err == nil { - if !slices.Contains(additionalSubjects, addSubjPortLoadBalancerID.ID) && objID != addSubjPortLoadBalancerID.ID { - additionalSubjects = append(additionalSubjects, addSubjPortLoadBalancerID.ID) - } - - if !slices.Contains(additionalSubjects, addSubjPortLoadBalancerID.LoadBalancerID) { - additionalSubjects = append(additionalSubjects, addSubjPortLoadBalancerID.LoadBalancerID) - } - } + additionalSubjects = append(additionalSubjects, objID) changeset := []events.FieldChange{} cv_created_at := "" @@ -128,6 +114,11 @@ func LoadBalancerHooks() []ent.Hook { } additionalSubjects = append(additionalSubjects, owner_id) + relationships = append(relationships, events.AuthRelationshipRelation{ + Relation: "owner", + SubjectID: owner_id, + }) + if ok { cv_owner_id = fmt.Sprintf("%s", fmt.Sprint(owner_id)) pv_owner_id := "" @@ -149,14 +140,6 @@ func LoadBalancerHooks() []ent.Hook { cv_location_id := "" location_id, ok := m.LocationID() - if !ok && !m.Op().Is(ent.OpCreate) { - // since we are doing an update or delete and these fields didn't change, load the "old" value - location_id, err = m.OldLocationID(ctx) - if err != nil { - return nil, err - } - } - additionalSubjects = append(additionalSubjects, location_id) if ok { cv_location_id = fmt.Sprintf("%s", fmt.Sprint(location_id)) @@ -179,14 +162,6 @@ func LoadBalancerHooks() []ent.Hook { cv_provider_id := "" provider_id, ok := m.ProviderID() - if !ok && !m.Op().Is(ent.OpCreate) { - // since we are doing an update or delete and these fields didn't change, load the "old" value - provider_id, err = m.OldProviderID(ctx) - if err != nil { - return nil, err - } - } - additionalSubjects = append(additionalSubjects, provider_id) if ok { cv_provider_id = fmt.Sprintf("%s", fmt.Sprint(provider_id)) @@ -215,20 +190,40 @@ func LoadBalancerHooks() []ent.Hook { FieldChanges: changeset, } - lb_lookup := getLocation(ctx, objID, additionalSubjects) + // complete the mutation before we process the event + retValue, err := next.Mutate(ctx, m) + if err != nil { + return retValue, err + } + + addSubjPort, err := m.Client().Port.Query().Where(port.HasLoadBalancerWith(loadbalancer.IDEQ(objID))).Only(ctx) + if err == nil { + if !slices.Contains(msg.AdditionalSubjectIDs, addSubjPort.ID) { + msg.AdditionalSubjectIDs = append(msg.AdditionalSubjectIDs, addSubjPort.ID) + } + } + + lb_lookup := getLoadBalancerID(ctx, objID, msg.AdditionalSubjectIDs) if lb_lookup != "" { lb, err := m.Client().LoadBalancer.Get(ctx, lb_lookup) if err != nil { return nil, fmt.Errorf("unable to lookup location %s", lb_lookup) } - if !slices.Contains(additionalSubjects, lb.LocationID) { - additionalSubjects = append(additionalSubjects, lb.LocationID) - msg.AdditionalSubjectIDs = additionalSubjects + if !slices.Contains(msg.AdditionalSubjectIDs, lb.LocationID) { + msg.AdditionalSubjectIDs = append(msg.AdditionalSubjectIDs, lb.LocationID) + } + } + + if len(relationships) != 0 { + if err := permissions.CreateAuthRelationships(ctx, "load-balancer", objID, relationships...); err != nil { + return nil, fmt.Errorf("relationship request failed with error: %w", err) } } - m.EventsPublisher.PublishChange(ctx, eventSubject(objID), msg) + if _, err := m.EventsPublisher.PublishChange(ctx, "load-balancer", msg); err != nil { + return nil, fmt.Errorf("failed to publish change: %w", err) + } return retValue, nil }) @@ -241,6 +236,7 @@ func LoadBalancerHooks() []ent.Hook { func(next ent.Mutator) ent.Mutator { return hook.LoadBalancerFunc(func(ctx context.Context, m *generated.LoadBalancerMutation) (ent.Value, error) { additionalSubjects := []gidx.PrefixedID{} + relationships := []events.AuthRelationshipRelation{} objID, ok := m.ID() if !ok { @@ -249,23 +245,17 @@ func LoadBalancerHooks() []ent.Hook { dbObj, err := m.Client().LoadBalancer.Get(ctx, objID) if err != nil { - return nil, fmt.Errorf("failed to load object to get values for pubsub, err %w", err) + return nil, fmt.Errorf("failed to load object to get values for event, err %w", err) } additionalSubjects = append(additionalSubjects, dbObj.OwnerID) - additionalSubjects = append(additionalSubjects, dbObj.LocationID) - - additionalSubjects = append(additionalSubjects, dbObj.ProviderID) - - msg := events.ChangeMessage{ - EventType: eventType(m.Op()), - SubjectID: objID, - AdditionalSubjectIDs: additionalSubjects, - Timestamp: time.Now().UTC(), - } + relationships = append(relationships, events.AuthRelationshipRelation{ + Relation: "owner", + SubjectID: dbObj.OwnerID, + }) - lb_lookup := getLocation(ctx, objID, additionalSubjects) + lb_lookup := getLoadBalancerID(ctx, objID, additionalSubjects) if lb_lookup != "" { lb, err := m.Client().LoadBalancer.Get(ctx, lb_lookup) if err != nil { @@ -274,7 +264,6 @@ func LoadBalancerHooks() []ent.Hook { if !slices.Contains(additionalSubjects, lb.LocationID) { additionalSubjects = append(additionalSubjects, lb.LocationID) - msg.AdditionalSubjectIDs = additionalSubjects } } @@ -284,7 +273,22 @@ func LoadBalancerHooks() []ent.Hook { return retValue, err } - m.EventsPublisher.PublishChange(ctx, eventSubject(objID), msg) + if len(relationships) != 0 { + if err := permissions.DeleteAuthRelationships(ctx, "load-balancer", objID, relationships...); err != nil { + return nil, fmt.Errorf("relationship request failed with error: %w", err) + } + } + + msg := events.ChangeMessage{ + EventType: eventType(m.Op()), + SubjectID: objID, + AdditionalSubjectIDs: additionalSubjects, + Timestamp: time.Now().UTC(), + } + + if _, err := m.EventsPublisher.PublishChange(ctx, "load-balancer", msg); err != nil { + return nil, fmt.Errorf("failed to publish change: %w", err) + } return retValue, nil }) @@ -296,41 +300,17 @@ func LoadBalancerHooks() []ent.Hook { func OriginHooks() []ent.Hook { return []ent.Hook{ - // Create/Update hook hook.On( func(next ent.Mutator) ent.Mutator { return hook.OriginFunc(func(ctx context.Context, m *generated.OriginMutation) (ent.Value, error) { - // complete the mutation before we process the event - retValue, err := next.Mutate(ctx, m) - if err != nil { - return retValue, err - } - - // queueName := "" + var err error additionalSubjects := []gidx.PrefixedID{} + relationships := []events.AuthRelationshipRelation{} objID, ok := m.ID() if !ok { return nil, fmt.Errorf("object doesn't have an id %s", objID) } - // addSubjPool, err := m.Client().Pool.Get(ctx, objID) - addSubjPool, err := m.Client().Pool.Query().Where(pool.HasOriginsWith(origin.IDEQ(objID))).Only(ctx) - if err == nil { - if !slices.Contains(additionalSubjects, addSubjPool.ID) && objID != addSubjPool.ID { - additionalSubjects = append(additionalSubjects, addSubjPool.ID) - } - - if !slices.Contains(additionalSubjects, addSubjPool.OwnerID) { - additionalSubjects = append(additionalSubjects, addSubjPool.OwnerID) - } - } - - addSubjPort, err := m.Client().Port.Query().Where(port.HasPoolsWith(pool.HasOriginsWith(origin.IDEQ(objID)))).Only(ctx) - if err == nil { - if !slices.Contains(additionalSubjects, addSubjPort.LoadBalancerID) { - additionalSubjects = append(additionalSubjects, addSubjPort.LoadBalancerID) - } - } changeset := []events.FieldChange{} cv_created_at := "" @@ -474,6 +454,12 @@ func OriginHooks() []ent.Hook { return nil, err } } + additionalSubjects = append(additionalSubjects, pool_id) + + relationships = append(relationships, events.AuthRelationshipRelation{ + Relation: "pool", + SubjectID: pool_id, + }) if ok { cv_pool_id = fmt.Sprintf("%s", fmt.Sprint(pool_id)) @@ -502,20 +488,51 @@ func OriginHooks() []ent.Hook { FieldChanges: changeset, } - lb_lookup := getLocation(ctx, objID, additionalSubjects) + // complete the mutation before we process the event + retValue, err := next.Mutate(ctx, m) + if err != nil { + return retValue, err + } + + addSubjPool, err := m.Client().Pool.Query().Where(pool.HasOriginsWith(origin.IDEQ(objID))).Only(ctx) + if err == nil { + if !slices.Contains(msg.AdditionalSubjectIDs, addSubjPool.ID) && objID != addSubjPool.ID { + msg.AdditionalSubjectIDs = append(msg.AdditionalSubjectIDs, addSubjPool.ID) + } + + if !slices.Contains(msg.AdditionalSubjectIDs, addSubjPool.OwnerID) { + msg.AdditionalSubjectIDs = append(msg.AdditionalSubjectIDs, addSubjPool.OwnerID) + } + } + + addSubjPort, err := m.Client().Port.Query().Where(port.HasPoolsWith(pool.HasOriginsWith(origin.IDEQ(objID)))).Only(ctx) + if err == nil { + if !slices.Contains(msg.AdditionalSubjectIDs, addSubjPort.LoadBalancerID) { + msg.AdditionalSubjectIDs = append(msg.AdditionalSubjectIDs, addSubjPort.LoadBalancerID) + } + } + + lb_lookup := getLoadBalancerID(ctx, objID, msg.AdditionalSubjectIDs) if lb_lookup != "" { lb, err := m.Client().LoadBalancer.Get(ctx, lb_lookup) if err != nil { return nil, fmt.Errorf("unable to lookup location %s", lb_lookup) } - if !slices.Contains(additionalSubjects, lb.LocationID) { - additionalSubjects = append(additionalSubjects, lb.LocationID) - msg.AdditionalSubjectIDs = additionalSubjects + if !slices.Contains(msg.AdditionalSubjectIDs, lb.LocationID) { + msg.AdditionalSubjectIDs = append(msg.AdditionalSubjectIDs, lb.LocationID) } } - m.EventsPublisher.PublishChange(ctx, eventSubject(objID), msg) + if len(relationships) != 0 { + if err := permissions.CreateAuthRelationships(ctx, "load-balancer-origin", objID, relationships...); err != nil { + return nil, fmt.Errorf("relationship request failed with error: %w", err) + } + } + + if _, err := m.EventsPublisher.PublishChange(ctx, "load-balancer-origin", msg); err != nil { + return nil, fmt.Errorf("failed to publish change: %w", err) + } return retValue, nil }) @@ -528,6 +545,7 @@ func OriginHooks() []ent.Hook { func(next ent.Mutator) ent.Mutator { return hook.OriginFunc(func(ctx context.Context, m *generated.OriginMutation) (ent.Value, error) { additionalSubjects := []gidx.PrefixedID{} + relationships := []events.AuthRelationshipRelation{} objID, ok := m.ID() if !ok { @@ -536,26 +554,29 @@ func OriginHooks() []ent.Hook { dbObj, err := m.Client().Origin.Get(ctx, objID) if err != nil { - return nil, fmt.Errorf("failed to load object to get values for pubsub, err %w", err) + return nil, fmt.Errorf("failed to load object to get values for event, err %w", err) } additionalSubjects = append(additionalSubjects, dbObj.PoolID) - addSubjPort, err := m.Client().Port.Query().Where(port.HasPoolsWith(pool.HasOriginsWith(origin.IDEQ(objID)))).Only(ctx) - if err == nil { - if !slices.Contains(additionalSubjects, addSubjPort.LoadBalancerID) { - additionalSubjects = append(additionalSubjects, addSubjPort.LoadBalancerID) - } + relationships = append(relationships, events.AuthRelationshipRelation{ + Relation: "pool", + SubjectID: dbObj.PoolID, + }) + + // we have all the info we need, now complete the mutation before we process the event + retValue, err := next.Mutate(ctx, m) + if err != nil { + return retValue, err } - msg := events.ChangeMessage{ - EventType: eventType(m.Op()), - SubjectID: objID, - AdditionalSubjectIDs: additionalSubjects, - Timestamp: time.Now().UTC(), + if len(relationships) != 0 { + if err := permissions.DeleteAuthRelationships(ctx, "load-balancer-origin", objID, relationships...); err != nil { + return nil, fmt.Errorf("relationship request failed with error: %w", err) + } } - lb_lookup := getLocation(ctx, objID, additionalSubjects) + lb_lookup := getLoadBalancerID(ctx, objID, additionalSubjects) if lb_lookup != "" { lb, err := m.Client().LoadBalancer.Get(ctx, lb_lookup) if err != nil { @@ -564,17 +585,19 @@ func OriginHooks() []ent.Hook { if !slices.Contains(additionalSubjects, lb.LocationID) { additionalSubjects = append(additionalSubjects, lb.LocationID) - msg.AdditionalSubjectIDs = additionalSubjects } } - // we have all the info we need, now complete the mutation before we process the event - retValue, err := next.Mutate(ctx, m) - if err != nil { - return retValue, err + msg := events.ChangeMessage{ + EventType: eventType(m.Op()), + SubjectID: objID, + AdditionalSubjectIDs: additionalSubjects, + Timestamp: time.Now().UTC(), } - m.EventsPublisher.PublishChange(ctx, eventSubject(objID), msg) + if _, err := m.EventsPublisher.PublishChange(ctx, "load-balancer-origin", msg); err != nil { + return nil, fmt.Errorf("failed to publish change: %w", err) + } return retValue, nil }) @@ -586,43 +609,17 @@ func OriginHooks() []ent.Hook { func PoolHooks() []ent.Hook { return []ent.Hook{ - // Create/Update hook.On( func(next ent.Mutator) ent.Mutator { return hook.PoolFunc(func(ctx context.Context, m *generated.PoolMutation) (ent.Value, error) { - // complete the mutation before we process the event - retValue, err := next.Mutate(ctx, m) - if err != nil { - return retValue, err - } - + var err error additionalSubjects := []gidx.PrefixedID{} + relationships := []events.AuthRelationshipRelation{} objID, ok := m.ID() if !ok { return nil, fmt.Errorf("object doesn't have an id %s", objID) } - addSubjPort, err := m.Client().Port.Query().Where(port.HasPoolsWith(pool.IDEQ(objID))).Only(ctx) - if err == nil { - if !slices.Contains(additionalSubjects, addSubjPort.ID) && objID != addSubjPort.ID { - additionalSubjects = append(additionalSubjects, addSubjPort.ID) - } - - if !slices.Contains(additionalSubjects, addSubjPort.LoadBalancerID) { - additionalSubjects = append(additionalSubjects, addSubjPort.LoadBalancerID) - } - } - - addSubjOrigin, err := m.Client().Origin.Query().Where(origin.HasPoolWith(pool.IDEQ(objID))).Only(ctx) - if err == nil { - if !slices.Contains(additionalSubjects, addSubjOrigin.ID) && objID != addSubjOrigin.ID { - additionalSubjects = append(additionalSubjects, addSubjOrigin.ID) - } - - if !slices.Contains(additionalSubjects, addSubjOrigin.PoolID) { - additionalSubjects = append(additionalSubjects, addSubjOrigin.PoolID) - } - } changeset := []events.FieldChange{} cv_created_at := "" @@ -724,6 +721,11 @@ func PoolHooks() []ent.Hook { } additionalSubjects = append(additionalSubjects, owner_id) + relationships = append(relationships, events.AuthRelationshipRelation{ + Relation: "owner", + SubjectID: owner_id, + }) + if ok { cv_owner_id = fmt.Sprintf("%s", fmt.Sprint(owner_id)) pv_owner_id := "" @@ -751,20 +753,55 @@ func PoolHooks() []ent.Hook { FieldChanges: changeset, } - lb_lookup := getLocation(ctx, objID, additionalSubjects) + // complete the mutation before we process the event + retValue, err := next.Mutate(ctx, m) + if err != nil { + return retValue, err + } + + addSubjPort, err := m.Client().Port.Query().Where(port.HasPoolsWith(pool.IDEQ(objID))).Only(ctx) + if err == nil { + if !slices.Contains(msg.AdditionalSubjectIDs, addSubjPort.ID) && objID != addSubjPort.ID { + msg.AdditionalSubjectIDs = append(msg.AdditionalSubjectIDs, addSubjPort.ID) + } + + if !slices.Contains(msg.AdditionalSubjectIDs, addSubjPort.LoadBalancerID) { + msg.AdditionalSubjectIDs = append(msg.AdditionalSubjectIDs, addSubjPort.LoadBalancerID) + } + } + + addSubjOrigin, err := m.Client().Origin.Query().Where(origin.HasPoolWith(pool.IDEQ(objID))).Only(ctx) + if err == nil { + if !slices.Contains(msg.AdditionalSubjectIDs, addSubjOrigin.ID) && objID != addSubjOrigin.ID { + msg.AdditionalSubjectIDs = append(msg.AdditionalSubjectIDs, addSubjOrigin.ID) + } + + if !slices.Contains(msg.AdditionalSubjectIDs, addSubjOrigin.PoolID) { + msg.AdditionalSubjectIDs = append(msg.AdditionalSubjectIDs, addSubjOrigin.PoolID) + } + } + + lb_lookup := getLoadBalancerID(ctx, objID, msg.AdditionalSubjectIDs) if lb_lookup != "" { lb, err := m.Client().LoadBalancer.Get(ctx, lb_lookup) if err != nil { return nil, fmt.Errorf("unable to lookup location %s", lb_lookup) } - if !slices.Contains(additionalSubjects, lb.LocationID) { - additionalSubjects = append(additionalSubjects, lb.LocationID) - msg.AdditionalSubjectIDs = additionalSubjects + if !slices.Contains(msg.AdditionalSubjectIDs, lb.LocationID) { + msg.AdditionalSubjectIDs = append(msg.AdditionalSubjectIDs, lb.LocationID) } } - m.EventsPublisher.PublishChange(ctx, eventSubject(objID), msg) + if len(relationships) != 0 { + if err := permissions.CreateAuthRelationships(ctx, "load-balancer-pool", objID, relationships...); err != nil { + return nil, fmt.Errorf("relationship request failed with error: %w", err) + } + } + + if _, err := m.EventsPublisher.PublishChange(ctx, "load-balancer-pool", msg); err != nil { + return nil, fmt.Errorf("failed to publish change: %w", err) + } return retValue, nil }) @@ -777,6 +814,7 @@ func PoolHooks() []ent.Hook { func(next ent.Mutator) ent.Mutator { return hook.PoolFunc(func(ctx context.Context, m *generated.PoolMutation) (ent.Value, error) { additionalSubjects := []gidx.PrefixedID{} + relationships := []events.AuthRelationshipRelation{} objID, ok := m.ID() if !ok { @@ -785,11 +823,28 @@ func PoolHooks() []ent.Hook { dbObj, err := m.Client().Pool.Get(ctx, objID) if err != nil { - return nil, fmt.Errorf("failed to load object to get values for pubsub, err %w", err) + return nil, fmt.Errorf("failed to load object to get values for event, err %w", err) } additionalSubjects = append(additionalSubjects, dbObj.OwnerID) + relationships = append(relationships, events.AuthRelationshipRelation{ + Relation: "owner", + SubjectID: dbObj.OwnerID, + }) + + // we have all the info we need, now complete the mutation before we process the event + retValue, err := next.Mutate(ctx, m) + if err != nil { + return retValue, err + } + + if len(relationships) != 0 { + if err := permissions.DeleteAuthRelationships(ctx, "load-balancer-pool", objID, relationships...); err != nil { + return nil, fmt.Errorf("relationship request failed with error: %w", err) + } + } + addSubjPort, err := m.Client().Port.Query().Where(port.HasPoolsWith(pool.IDEQ(objID))).Only(ctx) if err == nil { if !slices.Contains(additionalSubjects, addSubjPort.LoadBalancerID) { @@ -797,14 +852,7 @@ func PoolHooks() []ent.Hook { } } - msg := events.ChangeMessage{ - EventType: eventType(m.Op()), - SubjectID: objID, - AdditionalSubjectIDs: additionalSubjects, - Timestamp: time.Now().UTC(), - } - - lb_lookup := getLocation(ctx, objID, additionalSubjects) + lb_lookup := getLoadBalancerID(ctx, objID, additionalSubjects) if lb_lookup != "" { lb, err := m.Client().LoadBalancer.Get(ctx, lb_lookup) if err != nil { @@ -813,17 +861,19 @@ func PoolHooks() []ent.Hook { if !slices.Contains(additionalSubjects, lb.LocationID) { additionalSubjects = append(additionalSubjects, lb.LocationID) - msg.AdditionalSubjectIDs = additionalSubjects } } - // we have all the info we need, now complete the mutation before we process the event - retValue, err := next.Mutate(ctx, m) - if err != nil { - return retValue, err + msg := events.ChangeMessage{ + EventType: eventType(m.Op()), + SubjectID: objID, + AdditionalSubjectIDs: additionalSubjects, + Timestamp: time.Now().UTC(), } - m.EventsPublisher.PublishChange(ctx, eventSubject(objID), msg) + if _, err := m.EventsPublisher.PublishChange(ctx, "load-balancer-pool", msg); err != nil { + return nil, fmt.Errorf("failed to publish change: %w", err) + } return retValue, nil }) @@ -835,50 +885,17 @@ func PoolHooks() []ent.Hook { func PortHooks() []ent.Hook { return []ent.Hook{ - // Create/Update hook.On( func(next ent.Mutator) ent.Mutator { return hook.PortFunc(func(ctx context.Context, m *generated.PortMutation) (ent.Value, error) { - // complete the mutation before we process the event - retValue, err := next.Mutate(ctx, m) - if err != nil { - return retValue, err - } - + var err error additionalSubjects := []gidx.PrefixedID{} + relationships := []events.AuthRelationshipRelation{} objID, ok := m.ID() if !ok { return nil, fmt.Errorf("object doesn't have an id %s", objID) } - addSubjPool, err := m.Client().Pool.Query().Where(pool.HasPortsWith(port.IDEQ(objID))).Only(ctx) - if err == nil { - if !slices.Contains(additionalSubjects, addSubjPool.ID) && objID != addSubjPool.ID { - additionalSubjects = append(additionalSubjects, addSubjPool.ID) - } - - if !slices.Contains(additionalSubjects, addSubjPool.OwnerID) { - additionalSubjects = append(additionalSubjects, addSubjPool.OwnerID) - } - } - addSubjLoadBalancer, err := m.Client().LoadBalancer.Query().Where(loadbalancer.HasPortsWith(port.IDEQ(objID))).Only(ctx) - if err == nil { - if !slices.Contains(additionalSubjects, addSubjLoadBalancer.ID) && objID != addSubjLoadBalancer.ID { - additionalSubjects = append(additionalSubjects, addSubjLoadBalancer.ID) - } - - if !slices.Contains(additionalSubjects, addSubjLoadBalancer.LocationID) { - additionalSubjects = append(additionalSubjects, addSubjLoadBalancer.LocationID) - } - - if !slices.Contains(additionalSubjects, addSubjLoadBalancer.OwnerID) { - additionalSubjects = append(additionalSubjects, addSubjLoadBalancer.OwnerID) - } - - if !slices.Contains(additionalSubjects, addSubjLoadBalancer.ProviderID) { - additionalSubjects = append(additionalSubjects, addSubjLoadBalancer.ProviderID) - } - } changeset := []events.FieldChange{} cv_created_at := "" @@ -971,18 +988,6 @@ func PortHooks() []ent.Hook { cv_load_balancer_id := "" load_balancer_id, ok := m.LoadBalancerID() - if !ok && !m.Op().Is(ent.OpCreate) { - // since we are doing an update or delete and these fields didn't change, load the "old" value - load_balancer_id, err = m.OldLoadBalancerID(ctx) - if err != nil { - return nil, err - } - } - - if !slices.Contains(additionalSubjects, load_balancer_id) { - additionalSubjects = append(additionalSubjects, load_balancer_id) - } - if ok { cv_load_balancer_id = fmt.Sprintf("%s", fmt.Sprint(load_balancer_id)) pv_load_balancer_id := "" @@ -1010,20 +1015,62 @@ func PortHooks() []ent.Hook { FieldChanges: changeset, } - lb_lookup := getLocation(ctx, objID, additionalSubjects) + // complete the mutation before we process the event + retValue, err := next.Mutate(ctx, m) + if err != nil { + return retValue, err + } + + addSubjPool, err := m.Client().Pool.Query().Where(pool.HasPortsWith(port.IDEQ(objID))).Only(ctx) + if err == nil { + if !slices.Contains(msg.AdditionalSubjectIDs, addSubjPool.ID) && objID != addSubjPool.ID { + msg.AdditionalSubjectIDs = append(msg.AdditionalSubjectIDs, addSubjPool.ID) + } + + if !slices.Contains(msg.AdditionalSubjectIDs, addSubjPool.OwnerID) { + msg.AdditionalSubjectIDs = append(msg.AdditionalSubjectIDs, addSubjPool.OwnerID) + } + } + addSubjLoadBalancer, err := m.Client().LoadBalancer.Query().Where(loadbalancer.HasPortsWith(port.IDEQ(objID))).Only(ctx) + if err == nil { + if !slices.Contains(msg.AdditionalSubjectIDs, addSubjLoadBalancer.ID) { + msg.AdditionalSubjectIDs = append(msg.AdditionalSubjectIDs, addSubjLoadBalancer.ID) + } + + if !slices.Contains(msg.AdditionalSubjectIDs, addSubjLoadBalancer.LocationID) { + msg.AdditionalSubjectIDs = append(msg.AdditionalSubjectIDs, addSubjLoadBalancer.LocationID) + } + + if !slices.Contains(msg.AdditionalSubjectIDs, addSubjLoadBalancer.OwnerID) { + msg.AdditionalSubjectIDs = append(msg.AdditionalSubjectIDs, addSubjLoadBalancer.OwnerID) + } + + if !slices.Contains(msg.AdditionalSubjectIDs, addSubjLoadBalancer.ProviderID) { + msg.AdditionalSubjectIDs = append(msg.AdditionalSubjectIDs, addSubjLoadBalancer.ProviderID) + } + } + + lb_lookup := getLoadBalancerID(ctx, objID, msg.AdditionalSubjectIDs) if lb_lookup != "" { lb, err := m.Client().LoadBalancer.Get(ctx, lb_lookup) if err != nil { return nil, fmt.Errorf("unable to lookup location %s", lb_lookup) } - if !slices.Contains(additionalSubjects, lb.LocationID) { - additionalSubjects = append(additionalSubjects, lb.LocationID) - msg.AdditionalSubjectIDs = additionalSubjects + if !slices.Contains(msg.AdditionalSubjectIDs, lb.LocationID) { + msg.AdditionalSubjectIDs = append(msg.AdditionalSubjectIDs, lb.LocationID) + } + } + + if len(relationships) != 0 { + if err := permissions.CreateAuthRelationships(ctx, "load-balancer-port", objID, relationships...); err != nil { + return nil, fmt.Errorf("relationship request failed with error: %w", err) } } - m.EventsPublisher.PublishChange(ctx, eventSubject(objID), msg) + if _, err := m.EventsPublisher.PublishChange(ctx, "load-balancer-port", msg); err != nil { + return nil, fmt.Errorf("failed to publish change: %w", err) + } return retValue, nil }) @@ -1036,6 +1083,7 @@ func PortHooks() []ent.Hook { func(next ent.Mutator) ent.Mutator { return hook.PortFunc(func(ctx context.Context, m *generated.PortMutation) (ent.Value, error) { additionalSubjects := []gidx.PrefixedID{} + relationships := []events.AuthRelationshipRelation{} objID, ok := m.ID() if !ok { @@ -1044,19 +1092,43 @@ func PortHooks() []ent.Hook { dbObj, err := m.Client().Port.Get(ctx, objID) if err != nil { - return nil, fmt.Errorf("failed to load object to get values for pubsub, err %w", err) + return nil, fmt.Errorf("failed to load object to get values for event, err %w", err) } additionalSubjects = append(additionalSubjects, dbObj.LoadBalancerID) - msg := events.ChangeMessage{ - EventType: eventType(m.Op()), - SubjectID: objID, - AdditionalSubjectIDs: additionalSubjects, - Timestamp: time.Now().UTC(), + // we have all the info we need, now complete the mutation before we process the event + retValue, err := next.Mutate(ctx, m) + if err != nil { + return retValue, err + } + + if len(relationships) != 0 { + if err := permissions.DeleteAuthRelationships(ctx, "load-balancer-port", objID, relationships...); err != nil { + return nil, fmt.Errorf("relationship request failed with error: %w", err) + } + } + + addSubjLoadBalancer, err := m.Client().LoadBalancer.Get(ctx, dbObj.LoadBalancerID) + if err != nil { + if !slices.Contains(additionalSubjects, addSubjLoadBalancer.ID) { + additionalSubjects = append(additionalSubjects, addSubjLoadBalancer.ID) + } + + if !slices.Contains(additionalSubjects, addSubjLoadBalancer.LocationID) { + additionalSubjects = append(additionalSubjects, addSubjLoadBalancer.LocationID) + } + + if !slices.Contains(additionalSubjects, addSubjLoadBalancer.OwnerID) { + additionalSubjects = append(additionalSubjects, addSubjLoadBalancer.OwnerID) + } + + if !slices.Contains(additionalSubjects, addSubjLoadBalancer.ProviderID) { + additionalSubjects = append(additionalSubjects, addSubjLoadBalancer.ProviderID) + } } - lb_lookup := getLocation(ctx, objID, additionalSubjects) + lb_lookup := getLoadBalancerID(ctx, objID, additionalSubjects) if lb_lookup != "" { lb, err := m.Client().LoadBalancer.Get(ctx, lb_lookup) if err != nil { @@ -1065,17 +1137,19 @@ func PortHooks() []ent.Hook { if !slices.Contains(additionalSubjects, lb.LocationID) { additionalSubjects = append(additionalSubjects, lb.LocationID) - msg.AdditionalSubjectIDs = additionalSubjects } } - // we have all the info we need, now complete the mutation before we process the event - retValue, err := next.Mutate(ctx, m) - if err != nil { - return retValue, err + msg := events.ChangeMessage{ + EventType: eventType(m.Op()), + SubjectID: objID, + AdditionalSubjectIDs: additionalSubjects, + Timestamp: time.Now().UTC(), } - m.EventsPublisher.PublishChange(ctx, eventSubject(objID), msg) + if _, err := m.EventsPublisher.PublishChange(ctx, "load-balancer-port", msg); err != nil { + return nil, fmt.Errorf("failed to publish change: %w", err) + } return retValue, nil }) @@ -1085,7 +1159,7 @@ func PortHooks() []ent.Hook { } } -// PubsubHooks bloops +// PubsubHooks registers our hooks with the ent client func PubsubHooks(c *generated.Client) { c.LoadBalancer.Use(LoadBalancerHooks()...) @@ -1099,32 +1173,17 @@ func PubsubHooks(c *generated.Client) { func eventType(op ent.Op) string { switch op { case ent.OpCreate: - return "create" + return string(events.CreateChangeType) case ent.OpUpdate, ent.OpUpdateOne: - return "update" + return string(events.UpdateChangeType) case ent.OpDelete, ent.OpDeleteOne: - return "delete" - default: - return "unknown" - } -} - -func eventSubject(objID gidx.PrefixedID) string { - switch objID.Prefix() { - case schema.LoadBalancerPrefix: - return "load-balancer" - case schema.PortPrefix: - return "load-balancer-port" - case schema.OriginPrefix: - return "load-balancer-origin" - case schema.PoolPrefix: - return "load-balancer-pool" + return string(events.DeleteChangeType) default: return "unknown" } } -func getLocation(ctx context.Context, id gidx.PrefixedID, addID []gidx.PrefixedID) gidx.PrefixedID { +func getLoadBalancerID(ctx context.Context, id gidx.PrefixedID, addID []gidx.PrefixedID) gidx.PrefixedID { if id.Prefix() == schema.LoadBalancerPrefix { return id } diff --git a/x/pubsubinfo/annotation.go b/x/pubsubinfo/annotation.go index 3c7f4cc48..d4113d3f2 100644 --- a/x/pubsubinfo/annotation.go +++ b/x/pubsubinfo/annotation.go @@ -1,22 +1,43 @@ +// Copyright 2023 The Infratographer Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package pubsubinfo -// AnnotationName is the value of the annotation when read during ent compilation -var AnnotationName = "INFRA9_PUBSUBHOOK" +// EventsHookAnnotationName is the value of the annotation when read during ent compilation +var EventsHookAnnotationName = "INFRA9_EVENTHOOKS" -// Annotation provides a ent.Annotaion spec -type Annotation struct { - QueueName string - IsAdditionalSubjectField bool +// EventsHookAnnotation provides a ent.Annotation spec. These shouldn't be set directly, you should use EventsHookAdditionalSubject() and EventsHookSubjectName instead +type EventsHookAnnotation struct { + SubjectName string + AdditionalSubjectRelation string } // Name implements the ent Annotation interface. -func (a Annotation) Name() string { - return AnnotationName +func (a EventsHookAnnotation) Name() string { + return EventsHookAnnotationName +} + +// EventsHookAdditionalSubject marks this field as a field to return as an additional subject +func EventsHookAdditionalSubject(relation string) *EventsHookAnnotation { + return &EventsHookAnnotation{ + AdditionalSubjectRelation: relation, + } } -// AdditionalSubject marks this field as a field to return as an additional subject -func AdditionalSubject() *Annotation { - return &Annotation{ - IsAdditionalSubjectField: true, +// EventsHookSubjectName sets the subject name that is where the messages for this object will be sent +func EventsHookSubjectName(s string) *EventsHookAnnotation { + return &EventsHookAnnotation{ + SubjectName: s, } } diff --git a/x/pubsubinfo/copyright.go b/x/pubsubinfo/copyright.go new file mode 100644 index 000000000..b9876ef71 --- /dev/null +++ b/x/pubsubinfo/copyright.go @@ -0,0 +1,19 @@ +package pubsubinfo + +const CopyrightHeader string = ` +// Copyright 2023 The Infratographer Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Code generated by entc, DO NOT EDIT. +` diff --git a/x/pubsubinfo/doc.go b/x/pubsubinfo/doc.go index 057507b8a..a5da12c41 100644 --- a/x/pubsubinfo/doc.go +++ b/x/pubsubinfo/doc.go @@ -1,2 +1,18 @@ -// Package pubsubinfo provides a mixin for ent that allows for adding annotations to fields required for message publishing +// Copyright 2023 The Infratographer Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package pubsubinfo is a package of tools for interacting with ent. Providing tools +// to make generating federated gql easier, working with JSON values in ent, and +// provided helpers for using idx as your ID on types. package pubsubinfo diff --git a/x/pubsubinfo/generator.go b/x/pubsubinfo/generator.go index d04b9df42..cba14107d 100644 --- a/x/pubsubinfo/generator.go +++ b/x/pubsubinfo/generator.go @@ -1,6 +1,21 @@ +// Copyright 2023 The Infratographer Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package pubsubinfo import ( + "entgo.io/contrib/entgql" "entgo.io/ent/entc" "entgo.io/ent/entc/gen" ) @@ -11,16 +26,47 @@ type Extension struct { entc.DefaultExtension templates []*gen.Template + + gqlSchemaHooks []entgql.SchemaHook } // ExtensionOption allow for control over the behavior of the generator type ExtensionOption func(*Extension) error +// WithFederation adds support for graphql federation by adding the Entity interface +// to all types, as well as removing the node() and nodes() query calls. +func WithFederation() ExtensionOption { + return func(ex *Extension) error { + ex.templates = append(ex.templates, FederationTemplate) + ex.gqlSchemaHooks = append(ex.gqlSchemaHooks, removeNodeGoModel, removeNodeQueries, setPageInfoShareable) + + return nil + } +} + +// WithJSONScalar adds the JSON scalar definition +func WithJSONScalar() ExtensionOption { + return func(ex *Extension) error { + ex.gqlSchemaHooks = append(ex.gqlSchemaHooks, addJSONScalar) + return nil + } +} + +// WithEventHooks adds the templates for generating event hooks +func WithEventHooks() ExtensionOption { + return func(ex *Extension) error { + ex.templates = append(ex.templates, EventHooksTemplate) + + return nil + } +} + // NewExtension returns an entc Extension that allows the entx package to generate // the schema changes and templates needed to function func NewExtension(opts ...ExtensionOption) (*Extension, error) { e := &Extension{ - templates: MixinTemplates, + templates: []*gen.Template{}, + gqlSchemaHooks: []entgql.SchemaHook{}, } for _, opt := range opts { @@ -37,4 +83,9 @@ func (e *Extension) Templates() []*gen.Template { return e.templates } +// GQLSchemaHooks of the extension to seamlessly edit the final gql interface. +func (e *Extension) GQLSchemaHooks() []entgql.SchemaHook { + return e.gqlSchemaHooks +} + var _ entc.Extension = (*Extension)(nil) diff --git a/x/pubsubinfo/gql_hooks.go b/x/pubsubinfo/gql_hooks.go new file mode 100644 index 000000000..7a0a6db49 --- /dev/null +++ b/x/pubsubinfo/gql_hooks.go @@ -0,0 +1,96 @@ +// Copyright 2023 The Infratographer Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pubsubinfo + +import ( + "errors" + + "entgo.io/ent/entc" + "entgo.io/ent/entc/gen" + "github.com/vektah/gqlparser/v2/ast" +) + +// Skipping err113 linting since these errors are returned during generation and not runtime +// +//nolint:goerr113 +var ( + removeNodeGoModel = func(g *gen.Graph, s *ast.Schema) error { + n, ok := s.Types["Node"] + if !ok { + return errors.New("failed to find node interface in schema") + } + + dirs := ast.DirectiveList{} + + for _, d := range n.Directives { + switch d.Name { + case "goModel": + continue + default: + dirs = append(dirs, d) + } + } + n.Directives = dirs + + return nil + } + + removeNodeQueries = func(g *gen.Graph, s *ast.Schema) error { + q, ok := s.Types["Query"] + if !ok { + return errors.New("failed to find query definition in schema") + } + + fields := ast.FieldList{} + + for _, f := range q.Fields { + switch f.Name { + case "node": + case "nodes": + continue + default: + fields = append(fields, f) + } + } + q.Fields = fields + + return nil + } + + setPageInfoShareable = func(g *gen.Graph, s *ast.Schema) error { + q, ok := s.Types["PageInfo"] + if !ok { + return nil + } + + q.Directives = append(q.Directives, &ast.Directive{Name: "shareable"}) + + return nil + } + + addJSONScalar = func(g *gen.Graph, s *ast.Schema) error { + s.Types["JSON"] = &ast.Definition{ + Kind: ast.Scalar, + Description: "A valid JSON string.", + Name: "JSON", + } + return nil + } +) + +// import string mutations from entc +var ( + _ entc.Extension = (*Extension)(nil) +) diff --git a/x/pubsubinfo/json.go b/x/pubsubinfo/json.go new file mode 100644 index 000000000..07bd1d8a0 --- /dev/null +++ b/x/pubsubinfo/json.go @@ -0,0 +1,55 @@ +// Copyright 2023 The Infratographer Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pubsubinfo + +import ( + "encoding/json" + "io" + + "github.com/99designs/gqlgen/graphql" +) + +// MarshalRawMessage provides a graphql.Marshaler for json.RawMessage +func MarshalRawMessage(t json.RawMessage) graphql.Marshaler { + return graphql.WriterFunc(func(w io.Writer) { + s, _ := t.MarshalJSON() + _, _ = io.WriteString(w, string(s)) + }) +} + +// UnmarshalRawMessage provides a graphql.Unmarshaler for json.RawMessage +func UnmarshalRawMessage(v interface{}) (json.RawMessage, error) { + switch j := v.(type) { + case string: + return UnmarshalRawMessage([]byte(j)) + case []byte: + return json.RawMessage(j), nil + case map[string]interface{}: + js, err := json.Marshal(v) + if err != nil { + return nil, err + } + + return json.RawMessage(js), nil + default: + // Attempt to cast it as a fall back but return an error if it fails + js, err := json.Marshal(v) + if err != nil { + return nil, err + } + + return json.RawMessage(js), nil + } +} diff --git a/x/pubsubinfo/key_directive.go b/x/pubsubinfo/key_directive.go new file mode 100644 index 000000000..af5ca0051 --- /dev/null +++ b/x/pubsubinfo/key_directive.go @@ -0,0 +1,41 @@ +// Copyright 2023 The Infratographer Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pubsubinfo + +import ( + "entgo.io/contrib/entgql" + "github.com/vektah/gqlparser/v2/ast" +) + +// GraphKeyDirective returns an entgql.Directive for setting the @key field on +// a graphql type +func GraphKeyDirective(fields string) entgql.Annotation { + return entgql.Directives(keyDirective(fields)) +} + +func keyDirective(fields string) entgql.Directive { + var args []*ast.Argument + if fields != "" { + args = append(args, &ast.Argument{ + Name: "fields", + Value: &ast.Value{ + Raw: fields, + Kind: ast.StringValue, + }, + }) + } + + return entgql.NewDirective("key", args...) +} diff --git a/x/pubsubinfo/template.go b/x/pubsubinfo/template.go index 2364399dc..3fcf9885a 100644 --- a/x/pubsubinfo/template.go +++ b/x/pubsubinfo/template.go @@ -1,3 +1,17 @@ +// Copyright 2023 The Infratographer Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package pubsubinfo import ( @@ -9,19 +23,17 @@ import ( ) var ( - // PubSubInfoTemplate adds support for generating pubsub fields - PubSubInfoTemplate = parseT("template/pubsub.tmpl") + // FederationTemplate adds support for generating the required output to support gql federation + FederationTemplate = parseT("template/gql_federation.tmpl") + + // EventHooksTemplate adds support for generating event hooks + EventHooksTemplate = parseT("template/event_hooks.tmpl") // TemplateFuncs contains the extra template functions used by entx. TemplateFuncs = template.FuncMap{ "contains": strings.Contains, } - // MixinTemplates includes all templates for extending ent to support entx mixins. - MixinTemplates = []*gen.Template{ - PubSubInfoTemplate, - } - //go:embed template/* _templates embed.FS ) diff --git a/x/pubsubinfo/template/event_hooks.tmpl b/x/pubsubinfo/template/event_hooks.tmpl new file mode 100644 index 000000000..c94b5871e --- /dev/null +++ b/x/pubsubinfo/template/event_hooks.tmpl @@ -0,0 +1,284 @@ +{{/* gotype: entgo.io/ent/entc/gen.Graph */}} + +{{ define "eventhooks/hooks" }} + {{ with extend $ "Package" "eventhooks" }} + {{ template "header" . }} + {{ end }} + + {{ $genPackage := base $.Config.Package }} + + import ( + "go.infratographer.com/permissions-api/pkg/permissions" + "golang.org/x/exp/slices" + ) + + {{- range $node := $.Nodes }} + {{- if $nodeAnnotation := $node.Annotations.INFRA9_EVENTHOOKS }} + {{- if ne $nodeAnnotation.SubjectName "" }} + func {{ $node.Name }}Hooks() []ent.Hook { + return []ent.Hook{ + hook.On( + func(next ent.Mutator) ent.Mutator { + return hook.{{ $node.Name }}Func(func(ctx context.Context, m *generated.{{ $node.Name }}Mutation) (ent.Value, error) { + var err error + additionalSubjects := []gidx.PrefixedID{} + relationships := []events.AuthRelationshipRelation{} + + objID, ok := m.{{ $node.ID.MutationGet }}() + if !ok { + return nil, fmt.Errorf("object doesn't have an id %s", objID) + } + + changeset := []events.FieldChange{} + + {{- range $f := $node.Fields }} + {{- if $f.Sensitive }} + // sensitive field, only return + _, ok = m.{{ $f.MutationGet }}() + if ok { + changeset = append(changeset, events.FieldChange{ + Field: "{{ $f.Name | camel }}", + PreviousValue: "", + CurrentValue: "", + }) + {{- else }} + {{- $currentValue := print "cv_" $f.Name }} + {{ $currentValue }} := "" + {{ $f.Name }}, ok := m.{{ $f.MutationGet }}() + {{- $annotation := $f.Annotations.INFRA9_EVENTHOOKS }} + {{- if $annotation.AdditionalSubjectRelation }} + if !ok && !m.Op().Is(ent.OpCreate) { + // since we are doing an update or delete and these fields didn't change, load the "old" value + {{ $f.Name }}, err = m.{{ $f.MutationGetOld }}(ctx) + if err != nil { + return nil, err + } + } + {{- if $f.Optional }} + if {{ $f.Name }} != gidx.NullPrefixedID { + additionalSubjects = append(additionalSubjects, {{ $f.Name }}) + + relationships = append(relationships, events.AuthRelationshipRelation{ + Relation: "{{ $annotation.AdditionalSubjectRelation }}", + SubjectID: {{ $f.Name }}, + }) + } + {{- else }} + additionalSubjects = append(additionalSubjects, {{ $f.Name }}) + + relationships = append(relationships, events.AuthRelationshipRelation{ + Relation: "{{ $annotation.AdditionalSubjectRelation }}", + SubjectID: {{ $f.Name }}, + }) + {{- end }} + {{ end }} + + if ok { + {{- if $f.Sensitive }} + changeset = append(changeset, events.FieldChange{ + Field: "{{ $f.Name | camel }}", + PreviousValue: "", + CurrentValue: "", + }) + {{- else }} + {{- if $f.IsTime }} + {{ $currentValue }} = {{ $f.Name }}.Format(time.RFC3339) + {{- else if $f.HasValueScanner }} + {{ $currentValue }} = {{ $f.Name }}.Value() + {{- else }} + {{ $currentValue }} = fmt.Sprintf("%s", fmt.Sprint({{ $f.Name }})) + {{- end }} + + {{- $prevVar := print "pv_" $f.Name }} + {{ $prevVar }} := "" + if !m.Op().Is(ent.OpCreate) { + ov, err := m.{{ $f.MutationGetOld }}(ctx) + if err != nil { + {{ $prevVar }} = "" + } else { + {{- if $f.IsTime }} + {{ $prevVar }} = ov.Format(time.RFC3339) + {{- else if $f.HasValueScanner }} + {{ $prevVar }} = ov.Value() + {{- else }} + {{ $prevVar }} = fmt.Sprintf("%s", fmt.Sprint(ov)) + {{- end }} + } + } + + changeset = append(changeset, events.FieldChange{ + Field: "{{ $f.Name }}", + PreviousValue: {{ $prevVar }}, + CurrentValue: {{ $currentValue }}, + }) + {{- end }} + } + {{ end }} + {{ end }} + + msg := events.ChangeMessage{ + EventType: eventType(m.Op()), + SubjectID: objID, + AdditionalSubjectIDs: additionalSubjects, + Timestamp: time.Now().UTC(), + FieldChanges: changeset, + } + + // complete the mutation before we process the event + retValue, err := next.Mutate(ctx, m) + if err != nil { + return retValue, err + } + + lb_lookup := getLoadBalancerID(ctx, objID, msg.AdditionalSubjectIDs) + if lb_lookup != "" { + lb, err := m.Client().LoadBalancer.Get(ctx, lb_lookup) + if err != nil { + return nil, fmt.Errorf("unable to lookup location %s", lb_lookup) + } + + if !slices.Contains(msg.AdditionalSubjectIDs, lb.LocationID) { + msg.AdditionalSubjectIDs = append(msg.AdditionalSubjectIDs, lb.LocationID) + } + } + + if len(relationships) != 0 { + if err := permissions.CreateAuthRelationships(ctx, "{{ $nodeAnnotation.SubjectName }}", objID, relationships...); err != nil { + return nil, fmt.Errorf("relationship request failed with error: %w", err) + } + } + + if _, err := m.EventsPublisher.PublishChange(ctx, "{{ $nodeAnnotation.SubjectName }}", msg); err != nil { + return nil, fmt.Errorf("failed to publish change: %w", err) + } + + return retValue, nil + })}, + ent.OpCreate|ent.OpUpdate|ent.OpUpdateOne, + ), + + // Delete Hook + hook.On( + func(next ent.Mutator) ent.Mutator { + return hook.{{ $node.Name }}Func(func(ctx context.Context, m *generated.{{ $node.Name }}Mutation) (ent.Value, error) { + additionalSubjects := []gidx.PrefixedID{} + relationships := []events.AuthRelationshipRelation{} + + objID, ok := m.{{ $node.ID.MutationGet }}() + if !ok { + return nil, fmt.Errorf("object doesn't have an id %s", objID) + } + + dbObj, err := m.Client().{{ $node.Name }}.Get(ctx, objID) + if err != nil { + return nil, fmt.Errorf("failed to load object to get values for event, err %w", err) + } + + {{- range $f := $node.Fields }} + {{- if not $f.Sensitive }} + {{- $annotation := $f.Annotations.INFRA9_EVENTHOOKS }} + {{- if $annotation.AdditionalSubjectRelation }} + {{- if $f.Optional }} + if dbObj.{{ $f.MutationGet }} != gidx.NullPrefixedID { + additionalSubjects = append(additionalSubjects, dbObj.{{ $f.MutationGet }}) + + relationships = append(relationships, events.AuthRelationshipRelation{ + Relation: "{{ $annotation.AdditionalSubjectRelation }}", + SubjectID: dbObj.{{ $f.MutationGet }}, + }) + } + {{- else }} + additionalSubjects = append(additionalSubjects, dbObj.{{ $f.MutationGet }}) + + relationships = append(relationships, events.AuthRelationshipRelation{ + Relation: "{{ $annotation.AdditionalSubjectRelation }}", + SubjectID: dbObj.{{ $f.MutationGet }}, + }) + {{- end }} + {{ end }} + {{ end }} + {{ end }} + + // we have all the info we need, now complete the mutation before we process the event + retValue, err := next.Mutate(ctx, m) + if err != nil { + return retValue, err + } + + if len(relationships) != 0 { + if err := permissions.DeleteAuthRelationships(ctx, "{{ $nodeAnnotation.SubjectName }}", objID, relationships...); err != nil { + return nil, fmt.Errorf("relationship request failed with error: %w", err) + } + } + + lb_lookup := getLoadBalancerID(ctx, objID, additionalSubjects) + if lb_lookup != "" { + lb, err := m.Client().LoadBalancer.Get(ctx, lb_lookup) + if err != nil { + return nil, fmt.Errorf("unable to lookup location %s", lb_lookup) + } + + if !slices.Contains(additionalSubjects, lb.LocationID) { + additionalSubjects = append(additionalSubjects, lb.LocationID) + } + } + + msg := events.ChangeMessage{ + EventType: eventType(m.Op()), + SubjectID: objID, + AdditionalSubjectIDs: additionalSubjects, + Timestamp: time.Now().UTC(), + } + + if _, err := m.EventsPublisher.PublishChange(ctx, "{{ $nodeAnnotation.SubjectName }}", msg); err != nil { + return nil, fmt.Errorf("failed to publish change: %w", err) + } + + return retValue, nil + })}, + ent.OpDelete|ent.OpDeleteOne, + ), + } + } + {{- end }} + {{- end }} + {{- end }} + + func EventHooks(c *{{ $genPackage }}.Client) { + {{- range $node := $.Nodes }} + {{- if $nodeAnnotation := $node.Annotations.INFRA9_EVENTHOOKS }} + {{- if ne $nodeAnnotation.SubjectName "" }} + c.{{ $node.Name }}.Use({{ $node.Name }}Hooks()...) + {{ end }} + {{ end }} + {{ end }} + } + + func eventType(op ent.Op) string { + switch op { + case ent.OpCreate: + return string(events.CreateChangeType) + case ent.OpUpdate, ent.OpUpdateOne: + return string(events.UpdateChangeType) + case ent.OpDelete, ent.OpDeleteOne: + return string(events.DeleteChangeType) + default: + return "unknown" + } + } + + func getLoadBalancerID(ctx context.Context, id gidx.PrefixedID, addID []gidx.PrefixedID) (gidx.PrefixedID) { + if id.Prefix() == schema.LoadBalancerPrefix { + return id + } + + for _, id := range addID { + if id.Prefix() == schema.LoadBalancerPrefix { + return id + } + } + + return "" + } + +{{ end }} diff --git a/x/pubsubinfo/template/gql_federation.tmpl b/x/pubsubinfo/template/gql_federation.tmpl new file mode 100644 index 000000000..dd452c8e1 --- /dev/null +++ b/x/pubsubinfo/template/gql_federation.tmpl @@ -0,0 +1,4 @@ +{{ define "model/additional/gql_federation" }} + // IsEntity implement fedruntime.Entity + func ({{ $.Receiver }} {{ $.Name }}) IsEntity() {} +{{ end }} diff --git a/x/pubsubinfo/template/pubsub.tmpl b/x/pubsubinfo/template/pubsub.tmpl deleted file mode 100644 index 6f6ce53b0..000000000 --- a/x/pubsubinfo/template/pubsub.tmpl +++ /dev/null @@ -1,291 +0,0 @@ -{{/* gotype: entgo.io/ent/entc/gen.Graph */}} - -{{ define "model/additional/entx_pubsub_config" }} - {{- range $f := $.Fields }} - {{- if $annotation := $f.Annotations.INFRA9_PUBSUBHOOK }} - {{- if $annotation.IsNamespacedDataJSONField }} - {{ $arg := "v" }} - {{ $func := print $f.StructField "HasKey" }} - // {{ $func }} checks if {{$f.StructField}} contains given value - func {{ $func }}({{ $arg}} string) predicate.{{ $.Name }} { - return predicate.{{ $.Name }}(func(s *sql.Selector) { - s.Where(sqljson.HasKey(s.C({{ $f.Constant }}), sqljson.DotPath({{ $arg }}))) - }) - } - {{ end }} - {{ end }} - {{ end }} -{{ end }} - - -{{ define "pubsubhooks/pubsub" }} -{{ with extend $ "Package" "pubsubhooks" }} - {{ template "header" . }} -{{ end }} - -{{ $genPackage := base $.Config.Package }} - -{{- range $node := $.Nodes }} - {{- if $nodeAnnotation := $node.Annotations.INFRA9_PUBSUBHOOK }} - func {{ $node.Name }}Hooks() []ent.Hook { - return []ent.Hook{ - hook.On( - func(next ent.Mutator) ent.Mutator { - return hook.{{ $node.Name }}Func(func(ctx context.Context, m *generated.{{ $node.Name }}Mutation) (ent.Value, error) { - // complete the mutation before we process the event - retValue, err := next.Mutate(ctx, m) - if err != nil { - return retValue, err - } - - // queueName := "{{ $nodeAnnotation.QueueName }}" - additionalSubjects := []gidx.PrefixedID{} - - objID, ok := m.{{ $node.ID.MutationGet }}() - if !ok { - return nil, fmt.Errorf("object doesn't have an id %s", objID) - } - - changeset := []events.FieldChange{} - - {{- range $f := $node.Fields }} - {{- if $f.Sensitive }} - // sensitive field, only return - _, ok = m.{{ $f.MutationGet }}() - if ok { - changeset = append(changeset, events.FieldChange{ - Field: "{{ $f.Name | camel }}", - PreviousValue: "", - CurrentValue: "", - }) - {{- else }} - {{- $currentValue := print "cv_" $f.Name }} - {{ $currentValue }} := "" - {{ $f.Name }}, ok := m.{{ $f.MutationGet }}() - {{- $annotation := $f.Annotations.INFRA9_PUBSUBHOOK }} - {{- if or $annotation.IsAdditionalSubjectField (contains $nodeAnnotation.QueueName $f.Name) }} - if !ok && !m.Op().Is(ent.OpCreate) { - // since we are doing an update or delete and these fields didn't change, load the "old" value - {{ $f.Name }}, err = m.{{ $f.MutationGetOld }}(ctx) - if err != nil { - return nil, err - } - } - - {{- if or ($annotation.IsAdditionalSubjectField) }} - additionalSubjects = append(additionalSubjects, {{ $f.Name }}) - {{- end }} - - {{- if contains $nodeAnnotation.QueueName $f.Name }} - {{- if $f.IsTime }} - {{ $currentValue }} = {{ $f.Name }}.Format(time.RFC3339) - {{- else if $f.HasValueScanner }} - {{ $currentValue }} = {{ $f.Name }}.Value() - {{- else }} - {{ $currentValue }} = fmt.Sprintf("%s", fmt.Sprint({{ $f.Name }})) - {{- end }} - // queueName = strings.ReplaceAll(queueName, "%{{ $f.Name }}%", {{ $currentValue }}) - {{ end }} - {{ end }} - - if ok { - {{- if $f.Sensitive }} - changeset = append(changeset, events.FieldChange{ - Field: "{{ $f.Name | camel }}", - PreviousValue: "", - CurrentValue: "", - }) - {{- else }} - {{- if not (contains $nodeAnnotation.QueueName $f.Name) }} - {{- if $f.IsTime }} - {{ $currentValue }} = {{ $f.Name }}.Format(time.RFC3339) - {{- else if $f.HasValueScanner }} - {{ $currentValue }} = {{ $f.Name }}.Value() - {{- else }} - {{ $currentValue }} = fmt.Sprintf("%s", fmt.Sprint({{ $f.Name }})) - {{- end }} - {{- end }} - - {{- $prevVar := print "pv_" $f.Name }} - {{ $prevVar }} := "" - if !m.Op().Is(ent.OpCreate) { - ov, err := m.{{ $f.MutationGetOld }}(ctx) - if err != nil { - {{ $prevVar }} = "" - } else { - {{- if $f.IsTime }} - {{ $prevVar }} = ov.Format(time.RFC3339) - {{- else if $f.HasValueScanner }} - {{ $prevVar }} = ov.Value() - {{- else }} - {{ $prevVar }} = fmt.Sprintf("%s", fmt.Sprint(ov)) - {{- end }} - } - } - - changeset = append(changeset, events.FieldChange{ - Field: "{{ $f.Name }}", - PreviousValue: {{ $prevVar }}, - CurrentValue: {{ $currentValue }}, - }) - {{- end }} - } - {{ end }} - {{ end }} - - msg := events.ChangeMessage{ - EventType: eventType(m.Op()), - SubjectID: objID, - AdditionalSubjectIDs: additionalSubjects, - Timestamp: time.Now().UTC(), - FieldChanges: changeset, - } - - lb_lookup := getLocation(ctx, objID, additionalSubjects) - if lb_lookup != "" { - lb, err := m.Client().LoadBalancer.Get(ctx, lb_lookup) - if err != nil { - return nil, fmt.Errorf("unable to lookup location %s", lb_lookup) - } - - if !slices.Contains(additionalSubjects, lb.LocationID) { - additionalSubjects = append(additionalSubjects, lb.LocationID) - msg.AdditionalSubjectIDs = additionalSubjects - } - } - - m.EventsPublisher.PublishChange(ctx, eventSubject(objID),msg) - - return retValue, nil - })}, - ent.OpCreate|ent.OpUpdate|ent.OpUpdateOne, - ), - - // Delete Hook - hook.On( - func(next ent.Mutator) ent.Mutator { - return hook.{{ $node.Name }}Func(func(ctx context.Context, m *generated.{{ $node.Name }}Mutation) (ent.Value, error) { - // queueName := "{{ $nodeAnnotation.QueueName }}" - additionalSubjects := []gidx.PrefixedID{} - - objID, ok := m.{{ $node.ID.MutationGet }}() - if !ok { - return nil, fmt.Errorf("object doesn't have an id %s", objID) - } - - dbObj, err := m.Client().{{ $node.Name }}.Get(ctx, objID) - if err != nil { - return nil, fmt.Errorf("failed to load object to get values for pubsub, err %w", err) - } - - {{- range $f := $node.Fields }} - {{- if not $f.Sensitive }} - {{- $annotation := $f.Annotations.INFRA9_PUBSUBHOOK }} - {{- if or $annotation.IsAdditionalSubjectField (contains $nodeAnnotation.QueueName $f.Name) }} - {{- if $annotation.IsAdditionalSubjectField }} - additionalSubjects = append(additionalSubjects, dbObj.{{ $f.MutationGet }}) - {{- end }} - {{- if contains $nodeAnnotation.QueueName $f.Name }} - {{- $currentValue := print "value_" $f.Name }} - {{- if $f.IsTime }} - {{ $currentValue }} := dbObj.{{ $f.MutationGet }}.Format(time.RFC3339) - {{- else if $f.HasValueScanner }} - {{ $currentValue }} := dbObj.{{ $f.MutationGet }}.Value() - {{- else }} - {{ $currentValue }} := fmt.Sprintf("%s", dbObj.{{ $f.MutationGet }}) - {{- end }} - // queueName = strings.ReplaceAll(queueName, "%{{ $f.Name }}%", {{ $currentValue }}) - {{ end }} - {{ end }} - {{ end }} - {{ end }} - - msg := events.ChangeMessage{ - EventType: eventType(m.Op()), - SubjectID: objID, - AdditionalSubjectIDs: additionalSubjects, - Timestamp: time.Now().UTC(), - } - - lb_lookup := getLocation(ctx, objID, additionalSubjects) - if lb_lookup != "" { - lb, err := m.Client().LoadBalancer.Get(ctx, lb_lookup) - if err != nil { - return nil, fmt.Errorf("unable to lookup location %s", lb_lookup) - } - - if !slices.Contains(additionalSubjects, lb.LocationID) { - additionalSubjects = append(additionalSubjects, lb.LocationID) - msg.AdditionalSubjectIDs = additionalSubjects - } - } - - // we have all the info we need, now complete the mutation before we process the event - retValue, err := next.Mutate(ctx, m) - if err != nil { - return retValue, err - } - - m.EventsPublisher.PublishChange(ctx, eventSubject(objID),msg) - - return retValue, nil - })}, - ent.OpDelete|ent.OpDeleteOne, - ), - } - } - {{- end }} -{{- end }} - - -func PubsubHooks(c *{{ $genPackage }}.Client) { - {{- range $node := $.Nodes }} - {{- if $nodeAnnotation := $node.Annotations.INFRA9_PUBSUBHOOK }} - c.{{ $node.Name }}.Use({{ $node.Name }}Hooks()...) - {{ end }} - {{ end }} -} - -func eventType(op ent.Op) string { - switch op { - case ent.OpCreate: - return "create" - case ent.OpUpdate, ent.OpUpdateOne: - return "update" - case ent.OpDelete, ent.OpDeleteOne: - return "delete" - default: - return "unknown" - } -} - -func eventSubject(objID gidx.PrefixedID) string { - switch objID.Prefix(){ - case schema.LoadBalancerPrefix: - return "load-balancer" - case schema.PortPrefix: - return "load-balancer-port" - case schema.OriginPrefix: - return "load-balancer-origin" - case schema.PoolPrefix: - return "load-balancer-pool" - default: - return "unknown" - } -} - -func getLocation(ctx context.Context, id gidx.PrefixedID, addID []gidx.PrefixedID) (gidx.PrefixedID) { - if id.Prefix() == schema.LoadBalancerPrefix { - return id - } - - for _, id := range addID { - if id.Prefix() == schema.LoadBalancerPrefix { - return id - } - } - - return "" -} - -{{ end }} diff --git a/x/pubsubinfo/timestamps.go b/x/pubsubinfo/timestamps.go new file mode 100644 index 000000000..d9d712330 --- /dev/null +++ b/x/pubsubinfo/timestamps.go @@ -0,0 +1,86 @@ +// Copyright 2023 The Infratographer Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pubsubinfo + +import ( + "time" + + "entgo.io/contrib/entgql" + "entgo.io/ent" + "entgo.io/ent/schema" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" + "entgo.io/ent/schema/mixin" +) + +// TimestampsMixin defines an interface of a Mixin that provides the created_at and updated_at timestamp fields +type TimestampsMixin interface { + ent.Mixin + CreatedAtAnnotations(...schema.Annotation) TimestampsMixin + UpdatedAtAnnotations(...schema.Annotation) TimestampsMixin +} + +type timestampsMixin struct { + mixin.Schema + createdAnnotations []schema.Annotation + updatedAnnotations []schema.Annotation +} + +// NewTimestampMixin returns a Mixin that provides the created_at and updated_at timestamp fields +func NewTimestampMixin() TimestampsMixin { + return timestampsMixin{ + createdAnnotations: []schema.Annotation{ + entgql.Skip(entgql.SkipMutationCreateInput, entgql.SkipMutationUpdateInput), + entgql.OrderField("CREATED_AT"), + }, + updatedAnnotations: []schema.Annotation{ + entgql.Skip(entgql.SkipMutationCreateInput, entgql.SkipMutationUpdateInput), + entgql.OrderField("UPDATED_AT"), + }, + } +} + +func (m timestampsMixin) CreatedAtAnnotations(ants ...schema.Annotation) TimestampsMixin { + m.createdAnnotations = ants + return m +} + +func (m timestampsMixin) UpdatedAtAnnotations(ants ...schema.Annotation) TimestampsMixin { + m.updatedAnnotations = ants + return m +} + +// Fields provides the created_at and updated_at fields +func (m timestampsMixin) Fields() []ent.Field { + return []ent.Field{ + field.Time("created_at"). + Default(time.Now). + Immutable(). + Annotations(m.createdAnnotations...), + field.Time("updated_at"). + Default(time.Now). + UpdateDefault(time.Now). + Immutable(). + Annotations(m.updatedAnnotations...), + } +} + +// Indexes provides indexes on both created_at and updated_at fields +func (timestampsMixin) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("created_at"), + index.Fields("updated_at"), + } +}